Was ist der beste (und schnellste) Weg, um eine zufällige Zeile mit Linq to SQL abzurufen, wenn ich eine Bedingung habe, z. B. muss ein Feld wahr sein?
Was ist der beste (und schnellste) Weg, um eine zufällige Zeile mit Linq to SQL abzurufen, wenn ich eine Bedingung habe, z. B. muss ein Feld wahr sein?
Antworten:
Sie können dies in der Datenbank tun, indem Sie eine gefälschte UDF verwenden. Fügen Sie in einer Teilklasse dem Datenkontext eine Methode hinzu:
partial class MyDataContext {
[Function(Name="NEWID", IsComposable=true)]
public Guid Random()
{ // to prove not used by our C# code...
throw new NotImplementedException();
}
}
Dann einfach order by ctx.Random()
; Dies führt eine zufällige Bestellung auf dem SQL-Server mit freundlicher Genehmigung von NEWID()
. dh
var cust = (from row in ctx.Customers
where row.IsActive // your filter
orderby ctx.Random()
select row).FirstOrDefault();
Beachten Sie, dass dies nur für kleine bis mittelgroße Tische geeignet ist. Bei großen Tabellen wirkt sich dies auf die Leistung des Servers aus, und es ist effizienter, die Anzahl der Zeilen zu ermitteln ( Count
) und dann eine zufällig auszuwählen ( Skip/First
).
für den Zählansatz:
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
Ein weiteres Beispiel für Entity Framework:
var customers = db.Customers
.Where(c => c.IsActive)
.OrderBy(c => Guid.NewGuid())
.FirstOrDefault();
Dies funktioniert nicht mit LINQ to SQL. Das OrderBy
wird einfach fallen gelassen.
EDIT: Ich habe gerade erst bemerkt, dass dies LINQ to SQL ist, nicht LINQ to Objects. Verwenden Sie Marc's Code, um die Datenbank dazu zu bringen, dies für Sie zu tun. Ich habe diese Antwort hier als potenziellen Punkt von Interesse für LINQ to Objects hinterlassen.
Seltsamerweise müssen Sie nicht wirklich zählen. Sie müssen jedoch jedes Element abrufen, es sei denn, Sie erhalten die Anzahl.
Was Sie tun können, ist die Idee eines "aktuellen" Werts und der aktuellen Anzahl beizubehalten. Wenn Sie den nächsten Wert abrufen, nehmen Sie eine Zufallszahl und ersetzen Sie die "aktuelle" durch "neue" mit einer Wahrscheinlichkeit von 1 / n, wobei n die Anzahl ist.
Wenn Sie also den ersten Wert lesen, machen Sie diesen immer zum "aktuellen" Wert. Wenn Sie den zweiten Wert lesen, Sie könnten , dass der aktuelle Wert (Wahrscheinlichkeit 1/2) machen. Wenn Sie den dritten Wert zu lesen, Sie können machen , dass der aktuelle Wert (Wahrscheinlichkeit 1/3) usw. Wenn Sie von Daten abgelaufen haben, ist der aktuelle Wert ein gelegentlich man aus allen denen , die Sie mit einheitlicher Wahrscheinlichkeit lesen.
Um dies mit einer Bedingung anzuwenden, ignorieren Sie einfach alles, was die Bedingung nicht erfüllt. Der einfachste Weg, dies zu tun, besteht darin, zunächst nur die "übereinstimmende" Sequenz zu berücksichtigen, indem Sie zuerst eine Where-Klausel anwenden.
Hier ist eine schnelle Implementierung. Ich denke es ist okay ...
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
Wird bei der ersten Iteration immer auf das erste Element gesetzt. Bei der zweiten Iteration wird eine Änderung von 50% vorgenommen, die auf das zweite Element gesetzt wird. Bei der dritten Iteration besteht eine Wahrscheinlichkeit von 33%, dass das dritte Element verwendet wird. Das Hinzufügen einer break-Anweisung würde bedeuten, dass Sie nach dem Lesen des ersten Elements immer beenden, sodass es überhaupt nicht zufällig ist.
Eine Möglichkeit, dies effizient zu erreichen, besteht darin, Ihren Daten eine Spalte hinzuzufügen Shuffle
, die mit einem zufälligen int gefüllt ist (während jeder Datensatz erstellt wird).
Die Teilabfrage für den Zugriff auf die Tabelle in zufälliger Reihenfolge lautet ...
Random random = new Random();
int seed = random.Next();
result = result.OrderBy(s => (~(s.Shuffle & seed)) & (s.Shuffle | seed)); // ^ seed);
Dies führt eine XOR-Operation in der Datenbank durch und ordnet nach den Ergebnissen dieses XOR.
Vorteile: -
Dies ist der Ansatz, den mein Hausautomationssystem verwendet, um Wiedergabelisten nach dem Zufallsprinzip zu sortieren. Es wählt jeden Tag einen neuen Startwert aus und gibt tagsüber eine einheitliche Reihenfolge (was einfache Pausen- / Wiederaufnahmefunktionen ermöglicht), aber jeden neuen Tag einen neuen Blick auf jede Wiedergabeliste.
result = result.OrderBy(s => s.Shuffle ^ seed);
(dh das XOR muss nicht über die Operatoren ~, & und | implementiert werden).
Wenn Sie zB var count = 16
zufällige Zeilen aus der Tabelle erhalten möchten , können Sie schreiben
var rows = Table.OrderBy(t => Guid.NewGuid())
.Take(count);
hier habe ich EF verwendet, und die Tabelle ist ein Dbset
Wenn der Zweck des Erhaltens Zufallsreihen abtastet, ich habe sehr kurz gesprochen hier über einen netten Ansatz von Larson et al., Microsoft Research Team , in dem sie einen Stichprobenrahmen für SQL Server materialisierte Ansichten entwickelt haben. Es gibt auch einen Link zum eigentlichen Artikel.
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();
Erläuterung: Durch Einfügen der Guid (die zufällig ist) wäre die Reihenfolge mit orderby zufällig.
Kam hierher und fragte mich, wie man ein paar zufällige Seiten von einer kleinen Anzahl von ihnen bekommt, so dass jeder Benutzer einige andere zufällige 3 Seiten bekommt.
Dies ist meine endgültige Lösung, bei der ich mit LINQ eine Liste von Seiten in Sharepoint 2010 abfrage. Es ist in Visual Basic, sorry: p
Dim Aleatorio As New Random()
Dim Paginas = From a As SPListItem In Sitio.RootWeb.Lists("Páginas") Order By Aleatorio.Next Take 3
Wahrscheinlich sollte ein Profil erstellt werden, bevor eine große Anzahl von Ergebnissen abgefragt wird, aber es ist perfekt für meinen Zweck
Ich habe zufällige Funktionsabfrage gegen DataTable
s:
var result = (from result in dt.AsEnumerable()
order by Guid.NewGuid()
select result).Take(3);
Im folgenden Beispiel wird die Quelle aufgerufen, um eine Zählung abzurufen, und anschließend ein Sprungausdruck auf die Quelle mit einer Zahl zwischen 0 und n angewendet. Die zweite Methode wendet die Reihenfolge unter Verwendung des zufälligen Objekts an (das alles im Speicher ordnet) und wählt die Nummer aus, die an den Methodenaufruf übergeben wird.
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);
}
}
Ich benutze diese Methode für zufällige Nachrichten und ihre Arbeit gut;)
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;
}
Die Verwendung von LINQ to SQL in LINQPad als C # -Anweisungen sieht folgendermaßen aus
IEnumerable<Customer> customers = this.ExecuteQuery<Customer>(@"SELECT top 10 * from [Customers] order by newid()");
customers.Dump();
Das generierte SQL ist
SELECT top 10 * from [Customers] order by newid()
var cust = (from c in ctx.CUSTOMERs.ToList() select c).OrderBy(x => x.Guid.NewGuid()).Taket(2);
Wählen Sie zufällige 2 Zeilen
Zur Lösung von Marc Gravell hinzufügen. Wenn Sie nicht mit der Datenkontextklasse selbst arbeiten (weil Sie sie irgendwie als Proxy verwenden, z. B. um den Datenkontext zu Testzwecken zu fälschen), können Sie die definierte UDF nicht direkt verwenden: Sie wird nicht in SQL kompiliert, da Sie sie nicht in a verwenden Unterklasse oder Teilklasse Ihrer realen Datenkontextklasse.
Eine Problemumgehung für dieses Problem besteht darin, eine Zufallsfunktion in Ihrem Proxy zu erstellen und diese mit der Abfrage zu versorgen, die Sie zufällig auswählen möchten:
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());
}
}
So würden Sie es in Ihrem Code verwenden:
var query = _dc.Repository<SomeEntity>();
query = _dc.Randomize(query);
Um vollständig zu sein, wird dies folgendermaßen im FAKE-Datenkontext implementiert (der in Speicherentitäten verwendet wird):
public IOrderedQueryable<T> Randomize<T>(IQueryable<T> query)
{
return query.OrderBy(x => Guid.NewGuid());
}