Jaki jest najlepszy (i najszybszy) sposób na pobranie losowego wiersza za pomocą Linq do SQL, gdy mam warunek, np. Jakieś pole musi być prawdziwe?
Jaki jest najlepszy (i najszybszy) sposób na pobranie losowego wiersza za pomocą Linq do SQL, gdy mam warunek, np. Jakieś pole musi być prawdziwe?
Odpowiedzi:
Możesz to zrobić w bazie danych, używając fałszywego UDF; w klasie częściowej dodaj metodę do kontekstu danych:
partial class MyDataContext {
[Function(Name="NEWID", IsComposable=true)]
public Guid Random()
{ // to prove not used by our C# code...
throw new NotImplementedException();
}
}
Wtedy po prostu order by ctx.Random()
; spowoduje to losowe uporządkowanie w SQL-Server dzięki uprzejmości NEWID()
. to znaczy
var cust = (from row in ctx.Customers
where row.IsActive // your filter
orderby ctx.Random()
select row).FirstOrDefault();
Zauważ, że jest to odpowiednie tylko dla małych i średnich tabel; w przypadku dużych tabel będzie to miało wpływ na wydajność serwera i efektywniejsze będzie znalezienie liczby wierszy ( Count
), a następnie wybranie jednego losowo ( Skip/First
).
dla podejścia liczącego:
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
Kolejny przykład dla Entity Framework:
var customers = db.Customers
.Where(c => c.IsActive)
.OrderBy(c => Guid.NewGuid())
.FirstOrDefault();
To nie działa z LINQ to SQL. OrderBy
Jest po prostu przed upadkiem.
EDYCJA: Właśnie zauważyłem, że jest to LINQ to SQL, a nie LINQ to Objects. Użyj kodu Marca, aby uzyskać bazę danych, która zrobi to za Ciebie. Zostawiłem tę odpowiedź tutaj jako potencjalny punkt zainteresowania dla LINQ to Objects.
O dziwo, tak naprawdę nie musisz liczyć. Musisz jednak pobrać każdy element, chyba że otrzymasz liczbę.
Możesz zachować ideę „bieżącej” wartości i aktualnej liczby. Kiedy pobierasz następną wartość, weź liczbę losową i zamień „bieżącą” na „nową” z prawdopodobieństwem 1 / n, gdzie n jest liczbą.
Więc kiedy czytasz pierwszą wartość, zawsze ustawiasz ją jako „bieżącą”. Kiedy czytasz drugą wartość, możesz ustawić ją jako wartość bieżącą (prawdopodobieństwo 1/2). Kiedy czytasz trzecią wartość, możesz uznać, że jest to wartość bieżąca (prawdopodobieństwo 1/3) itd. Kiedy skończą się dane, bieżąca wartość jest losową spośród wszystkich, które czytasz, z jednakowym prawdopodobieństwem.
Aby zastosować to z warunkiem, po prostu zignoruj wszystko, co nie spełnia warunku. Najłatwiejszym sposobem na to jest rozważenie na początku tylko „pasującej” sekwencji, stosując najpierw klauzulę Where.
Oto szybka implementacja. Myślę, że to w porządku ...
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
będzie zawsze być ustawiony na pierwszym elemencie. W drugiej iteracji następuje 50% zmiana, że zostanie ustawiony na drugi element. W trzeciej iteracji istnieje 33% szansa, że zostanie ustawiony na trzeci element. Dodanie instrukcji break oznaczałoby, że zawsze wychodzisz po przeczytaniu pierwszego elementu, dzięki czemu nie jest on wcale przypadkowy.
Jednym ze sposobów na efektywne osiągnięcie tego jest dodanie kolumny do danych Shuffle
która jest wypełniona losową liczbą int (podczas tworzenia każdego rekordu).
Częściowe zapytanie o dostęp do tabeli w kolejności losowej to ...
Random random = new Random();
int seed = random.Next();
result = result.OrderBy(s => (~(s.Shuffle & seed)) & (s.Shuffle | seed)); // ^ seed);
Wykonuje operację XOR w bazie danych i porządkuje wyniki tego XOR.
Zalety:-
To jest podejście używane przez mój system automatyki domowej do losowego tworzenia list odtwarzania. Każdego dnia wybiera nowe ziarno, zapewniając stałą kolejność w ciągu dnia (umożliwiając łatwe wstrzymywanie / wznawianie), ale każdego nowego dnia zapewnia świeże spojrzenie na każdą listę odtwarzania.
result = result.OrderBy(s => s.Shuffle ^ seed);
(tj. Nie ma potrzeby implementowania XOR za pośrednictwem operatorów ~, & i |).
jeśli chcesz uzyskać np. var count = 16
losowe wiersze z tabeli, możesz pisać
var rows = Table.OrderBy(t => Guid.NewGuid())
.Take(count);
tutaj użyłem EF, a Table to Dbset
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();
Objaśnienie: Po wstawieniu guid (który jest losowy) kolejność z orderby będzie losowa.
Przyszedłem tutaj, zastanawiając się, jak uzyskać kilka losowych stron z niewielkiej liczby z nich, aby każdy użytkownik otrzymał różne losowe 3 strony.
To jest moje ostateczne rozwiązanie, pracujące z LINQ na liście stron w Sharepoint 2010. Jest w Visual Basic, przepraszam: p
Dim Aleatorio As New Random()
Dim Paginas = From a As SPListItem In Sitio.RootWeb.Lists("Páginas") Order By Aleatorio.Next Take 3
Prawdopodobnie powinienem uzyskać pewne profilowanie przed zapytaniem o dużą liczbę wyników, ale jest to idealne do moich celów
Mam losowe zapytanie funkcji przeciwko DataTable
s:
var result = (from result in dt.AsEnumerable()
order by Guid.NewGuid()
select result).Take(3);
Poniższy przykład wywoła źródło w celu pobrania liczby, a następnie zastosuje wyrażenie pomijania do źródła o liczbie od 0 do n. Druga metoda zastosuje kolejność przy użyciu losowego obiektu (który uporządkuje wszystko w pamięci) i wybierze liczbę przekazaną do wywołania metody.
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);
}
}
używam tej metody do pobierania losowych wiadomości i działa dobrze;)
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);
Wybierz losowo 2 wiersze
Aby dodać do rozwiązania Marca Gravella. Jeśli nie pracujesz z samą klasą datacontext (ponieważ w jakiś sposób proxy ją proxy, np. Aby sfałszować kontekst danych do celów testowych), nie możesz bezpośrednio użyć zdefiniowanego UDF: nie zostanie on skompilowany do SQL, ponieważ nie używasz go w podklasa lub część klasy twojej prawdziwej klasy kontekstu danych.
Obejściem tego problemu jest utworzenie funkcji Randomize w serwerze proxy i przesłanie do niej zapytania, które ma być losowe:
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());
}
}
Oto jak użyjesz go w swoim kodzie:
var query = _dc.Repository<SomeEntity>();
query = _dc.Randomize(query);
Aby być kompletnym, oto jak zaimplementować to w FAKE datacontext (który używa w jednostkach pamięci):
public IOrderedQueryable<T> Randomize<T>(IQueryable<T> query)
{
return query.OrderBy(x => Guid.NewGuid());
}