Qual è il modo migliore (e più veloce) per recuperare una riga casuale utilizzando Linq in SQL quando ho una condizione, ad esempio un campo deve essere vero?
Qual è il modo migliore (e più veloce) per recuperare una riga casuale utilizzando Linq in SQL quando ho una condizione, ad esempio un campo deve essere vero?
Risposte:
Puoi farlo nel database, usando una falsa UDF; in una classe parziale, aggiungi un metodo al contesto dati:
partial class MyDataContext {
[Function(Name="NEWID", IsComposable=true)]
public Guid Random()
{ // to prove not used by our C# code...
throw new NotImplementedException();
}
}
Quindi solo order by ctx.Random()
; questo eseguirà un ordine casuale per gentile concessione di SQL Server NEWID()
. vale a dire
var cust = (from row in ctx.Customers
where row.IsActive // your filter
orderby ctx.Random()
select row).FirstOrDefault();
Notare che questo è adatto solo per tavoli di piccole e medie dimensioni; per tabelle enormi, avrà un impatto sulle prestazioni sul server e sarà più efficiente trovare il numero di righe ( Count
), quindi sceglierne uno a caso ( Skip/First
).
per approccio conteggio:
var qry = from row in ctx.Customers
where row.IsActive
select row;
int count = qry.Count(); // 1st round-trip
int index = new Random().Next(count);
Customer cust = qry.Skip(index).FirstOrDefault(); // 2nd round-trip
Un altro esempio per Entity Framework:
var customers = db.Customers
.Where(c => c.IsActive)
.OrderBy(c => Guid.NewGuid())
.FirstOrDefault();
Questo non funziona con LINQ to SQL. Sta OrderBy
semplicemente cadendo.
EDIT: Ho appena notato che questo è LINQ to SQL, non LINQ to Objects. Usa il codice di Marc per fare in modo che il database lo faccia per te. Ho lasciato questa risposta qui come potenziale punto di interesse per LINQ to Objects.
Stranamente, non hai davvero bisogno di ottenere il conteggio. Tuttavia, è necessario recuperare ogni elemento a meno che non si ottenga il conteggio.
Quello che puoi fare è mantenere l'idea di un valore "corrente" e il conteggio corrente. Quando prendi il valore successivo, prendi un numero casuale e sostituisci "corrente" con "nuovo" con una probabilità di 1 / n dove n è il conteggio.
Quindi, quando leggi il primo valore, lo rendi sempre il valore "corrente". Quando leggi il secondo valore, potresti renderlo il valore corrente (probabilità 1/2). Quando leggi il terzo valore, potresti renderlo il valore corrente (probabilità 1/3) ecc. Quando hai esaurito i dati, il valore corrente è casuale tra tutti quelli che leggi, con probabilità uniforme.
Per applicarlo con una condizione, ignora semplicemente tutto ciò che non soddisfa la condizione. Il modo più semplice per farlo è considerare solo la sequenza di "corrispondenza" con cui iniziare, applicando prima una clausola Where.
Ecco una rapida implementazione. Io penso che va bene ...
public static T RandomElement<T>(this IEnumerable<T> source,
Random rng)
{
T current = default(T);
int count = 0;
foreach (T element in source)
{
count++;
if (rng.Next(count) == 0)
{
current = element;
}
}
if (count == 0)
{
throw new InvalidOperationException("Sequence was empty");
}
return current;
}
current
sarà sempre impostato sul primo elemento. Nella seconda iterazione, c'è una modifica del 50% che verrà impostata sul secondo elemento. Alla terza iterazione, c'è una probabilità del 33% che venga impostato sul terzo elemento. L'aggiunta di un'istruzione break significherebbe uscire sempre dopo aver letto il primo elemento, rendendolo non casuale.
Un modo per ottenere risultati efficienti è aggiungere una colonna ai dati Shuffle
che viene popolata con un int casuale (man mano che viene creato ogni record).
La query parziale per accedere alla tabella in ordine casuale è ...
Random random = new Random();
int seed = random.Next();
result = result.OrderBy(s => (~(s.Shuffle & seed)) & (s.Shuffle | seed)); // ^ seed);
Questo esegue un'operazione XOR nel database e ordina in base ai risultati di tale XOR.
Vantaggi: -
Questo è l'approccio utilizzato dal mio sistema domotico per randomizzare le playlist. Sceglie un nuovo seme ogni giorno dando un ordine coerente durante il giorno (consentendo facili funzionalità di pausa / ripresa) ma un nuovo sguardo a ogni playlist ogni nuovo giorno.
result = result.OrderBy(s => s.Shuffle ^ seed);
(cioè non è necessario implementare XOR tramite gli operatori ~, & e |).
se vuoi ottenere ad esempio var count = 16
righe casuali dalla tabella, puoi scrivere
var rows = Table.OrderBy(t => Guid.NewGuid())
.Take(count);
qui ho usato EF e la tabella è un Dbset
Se lo scopo di ottenere file casuali è campionamento, ho parlato molto brevemente qui di un approccio di bello da squadra Larson et al., Microsoft Research in cui hanno messo a punto un quadro di campionamento per SQL Server utilizzando viste materializzate. C'è anche un collegamento al documento effettivo.
List<string> lst = new List<string>();
lst.Add("Apple");
lst.Add("Guva");
lst.Add("Graps");
lst.Add("PineApple");
lst.Add("Orange");
lst.Add("Mango");
var customers = lst.OrderBy(c => Guid.NewGuid()).FirstOrDefault();
Spiegazione: Inserendo il guid (che è casuale) l'ordine con orderby sarebbe casuale.
Sono venuto qui chiedendomi come ottenere alcune pagine casuali da un piccolo numero di esse, quindi ogni utente ottiene alcune 3 pagine casuali diverse.
Questa è la mia soluzione finale, lavorando su query con LINQ su un elenco di pagine in Sharepoint 2010. È in Visual Basic, mi dispiace: p
Dim Aleatorio As New Random()
Dim Paginas = From a As SPListItem In Sitio.RootWeb.Lists("Páginas") Order By Aleatorio.Next Take 3
Probabilmente dovrei ottenere alcuni profili prima di interrogare un gran numero di risultati, ma è perfetto per il mio scopo
Ho una query di funzione casuale contro DataTable
s:
var result = (from result in dt.AsEnumerable()
order by Guid.NewGuid()
select result).Take(3);
L'esempio seguente chiamerà la sorgente per recuperare un conteggio e quindi applicherà un'espressione di salto alla sorgente con un numero compreso tra 0 e n. Il secondo metodo applicherà l'ordine utilizzando l'oggetto casuale (che ordinerà tutto in memoria) e selezionerà il numero passato alla chiamata al metodo.
public static class IEnumerable
{
static Random rng = new Random((int)DateTime.Now.Ticks);
public static T RandomElement<T>(this IEnumerable<T> source)
{
T current = default(T);
int c = source.Count();
int r = rng.Next(c);
current = source.Skip(r).First();
return current;
}
public static IEnumerable<T> RandomElements<T>(this IEnumerable<T> source, int number)
{
return source.OrderBy(r => rng.Next()).Take(number);
}
}
Uso questo metodo per prendere notizie casuali e funziona bene;)
public string LoadRandomNews(int maxNews)
{
string temp = "";
using (var db = new DataClassesDataContext())
{
var newsCount = (from p in db.Tbl_DynamicContents
where p.TimeFoPublish.Value.Date <= DateTime.Now
select p).Count();
int i;
if (newsCount < maxNews)
i = newsCount;
else i = maxNews;
var r = new Random();
var lastNumber = new List<int>();
for (; i > 0; i--)
{
int currentNumber = r.Next(0, newsCount);
if (!lastNumber.Contains(currentNumber))
{ lastNumber.Add(currentNumber); }
else
{
while (true)
{
currentNumber = r.Next(0, newsCount);
if (!lastNumber.Contains(currentNumber))
{
lastNumber.Add(currentNumber);
break;
}
}
}
if (currentNumber == newsCount)
currentNumber--;
var news = (from p in db.Tbl_DynamicContents
orderby p.ID descending
where p.TimeFoPublish.Value.Date <= DateTime.Now
select p).Skip(currentNumber).Take(1).Single();
temp +=
string.Format("<div class=\"divRandomNews\"><img src=\"files/1364193007_news.png\" class=\"randomNewsImg\" />" +
"<a class=\"randomNews\" href=\"News.aspx?id={0}\" target=\"_blank\">{1}</a></div>",
news.ID, news.Title);
}
}
return temp;
}
var cust = (from c in ctx.CUSTOMERs.ToList() select c).OrderBy(x => x.Guid.NewGuid()).Taket(2);
Seleziona 2 righe casuali
Da aggiungere alla soluzione di Marc Gravell. Se non stai lavorando con la classe datacontext stessa (perché in qualche modo lo fai tramite proxy, ad esempio per falsificare il datacontext a scopo di test), non puoi usare direttamente l'UDF definito: non sarà compilato in SQL perché non lo stai usando in un sottoclasse o classe parziale della classe del contesto dati reale.
Una soluzione alternativa a questo problema è creare una funzione Randomize nel proxy, alimentandola con la query che desideri venga randomizzata:
public class DataContextProxy : IDataContext
{
private readonly DataContext _context;
public DataContextProxy(DataContext context)
{
_context = context;
}
// Snipped irrelevant code
public IOrderedQueryable<T> Randomize<T>(IQueryable<T> query)
{
return query.OrderBy(x => _context.Random());
}
}
Ecco come lo useresti nel tuo codice:
var query = _dc.Repository<SomeEntity>();
query = _dc.Randomize(query);
Per completare, ecco come implementarlo nel FAKE datacontext (che utilizza nelle entità di memoria):
public IOrderedQueryable<T> Randomize<T>(IQueryable<T> query)
{
return query.OrderBy(x => Guid.NewGuid());
}