Apa cara terbaik (dan tercepat) untuk mengambil baris acak menggunakan LINQ ke SQL ketika saya memiliki kondisi, misalnya beberapa bidang harus benar?
Apa cara terbaik (dan tercepat) untuk mengambil baris acak menggunakan LINQ ke SQL ketika saya memiliki kondisi, misalnya beberapa bidang harus benar?
Jawaban:
Anda dapat melakukan ini di database, dengan menggunakan UDF palsu; di kelas parsial, tambahkan metode ke konteks data:
partial class MyDataContext {
[Function(Name="NEWID", IsComposable=true)]
public Guid Random()
{ // to prove not used by our C# code...
throw new NotImplementedException();
}
}
Kemudian hanya order by ctx.Random()
; ini akan melakukan pengurutan acak di SQL-Server milik NEWID()
. yaitu
var cust = (from row in ctx.Customers
where row.IsActive // your filter
orderby ctx.Random()
select row).FirstOrDefault();
Perhatikan bahwa ini hanya cocok untuk tabel ukuran kecil hingga menengah; untuk tabel yang besar, ini akan berdampak pada kinerja server, dan akan lebih efisien untuk menemukan jumlah baris ( Count
), lalu memilih satu secara acak ( Skip/First
).
untuk pendekatan hitungan:
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
Contoh lain untuk Entity Framework:
var customers = db.Customers
.Where(c => c.IsActive)
.OrderBy(c => Guid.NewGuid())
.FirstOrDefault();
Ini tidak bekerja dengan LINQ ke SQL. Itu OrderBy
hanya dijatuhkan.
EDIT: Saya baru saja menyadari ini LINQ ke SQL, bukan LINQ ke Objek. Gunakan kode Marc untuk mendapatkan database untuk melakukan ini untuk Anda. Saya meninggalkan jawaban ini di sini sebagai tempat menarik potensial untuk LINQ ke Objek.
Anehnya, Anda sebenarnya tidak perlu menghitungnya. Namun, Anda perlu mengambil setiap elemen kecuali Anda mendapatkan hitungannya.
Apa yang dapat Anda lakukan adalah mempertahankan gagasan tentang nilai "saat ini" dan jumlah saat ini. Saat Anda mengambil nilai berikutnya, ambil nomor acak dan ganti "saat ini" dengan "baru" dengan probabilitas 1 / n di mana n adalah hitungannya.
Jadi, saat Anda membaca nilai pertama, Anda selalu menjadikannya sebagai nilai "saat ini". Saat Anda membaca nilai kedua, Anda mungkin membuat nilai saat ini (probabilitas 1/2). Ketika Anda membaca nilai ketiga, Anda mungkin membuat nilai saat ini (probabilitas 1/3) dll. Ketika Anda kehabisan data, nilai saat ini adalah nilai acak dari semua yang Anda baca, dengan probabilitas seragam.
Untuk menerapkannya dengan syarat, abaikan saja apa pun yang tidak memenuhi syarat. Cara termudah untuk melakukannya adalah dengan hanya mempertimbangkan urutan "pencocokan" untuk memulai, dengan menerapkan klausa Where terlebih dahulu.
Berikut implementasi cepatnya. Saya pikir tidak apa-apa ...
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
akan selalu disetel ke elemen pertama. Pada iterasi kedua, ada perubahan 50% yang akan disetel ke elemen kedua. Pada iterasi ketiga, ada 33% kemungkinan itu akan disetel ke elemen ketiga. Menambahkan pernyataan break berarti Anda akan selalu keluar setelah membaca elemen pertama, membuatnya tidak acak sama sekali.
Salah satu cara untuk mencapai secara efisien adalah dengan menambahkan kolom ke data Anda Shuffle
yang diisi dengan int acak (karena setiap rekaman dibuat).
Permintaan parsial untuk mengakses tabel secara acak adalah ...
Random random = new Random();
int seed = random.Next();
result = result.OrderBy(s => (~(s.Shuffle & seed)) & (s.Shuffle | seed)); // ^ seed);
Ini melakukan operasi XOR dalam database dan perintah berdasarkan hasil XOR tersebut.
Keuntungan:-
Ini adalah pendekatan yang digunakan oleh sistem otomasi rumah saya untuk mengacak daftar putar. Itu mengambil benih baru setiap hari memberikan urutan yang konsisten sepanjang hari (memungkinkan kemampuan jeda / melanjutkan yang mudah) tetapi tampilan baru di setiap daftar putar setiap hari baru.
result = result.OrderBy(s => s.Shuffle ^ seed);
(mis. Tidak perlu mengimplementasikan XOR melalui operator ~, & dan |).
jika Anda ingin mendapatkan misalnya var count = 16
baris acak dari tabel, Anda dapat menulis
var rows = Table.OrderBy(t => Guid.NewGuid())
.Take(count);
di sini saya menggunakan EF, dan Tabel adalah Dbset
Jika tujuan mendapatkan baris acak adalah pengambilan sampel, saya telah berbicara sangat singkat di sini tentang pendekatan yang bagus dari Larson et al., Tim Riset Microsoft di mana mereka telah mengembangkan kerangka kerja pengambilan sampel untuk Server Sql menggunakan tampilan terwujud. Ada juga link ke kertas yang sebenarnya.
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();
Penjelasan: Dengan memasukkan panduan (yang acak) urutan dengan orderby akan menjadi acak.
Datang ke sini bertanya-tanya bagaimana cara mendapatkan beberapa halaman acak dari sejumlah kecil, sehingga setiap pengguna mendapatkan 3 halaman acak yang berbeda.
Ini adalah solusi terakhir saya, mengerjakan kueri dengan LINQ terhadap daftar halaman di Sharepoint 2010. Ada dalam Visual Basic, maaf: p
Dim Aleatorio As New Random()
Dim Paginas = From a As SPListItem In Sitio.RootWeb.Lists("Páginas") Order By Aleatorio.Next Take 3
Mungkin harus mendapatkan beberapa profil sebelum menanyakan sejumlah besar hasil, tetapi itu sempurna untuk tujuan saya
Saya memiliki kueri fungsi acak terhadap DataTable
s:
var result = (from result in dt.AsEnumerable()
order by Guid.NewGuid()
select result).Take(3);
Contoh di bawah ini akan memanggil sumber untuk mengambil hitungan dan kemudian menerapkan ekspresi lewati pada sumber dengan angka antara 0 dan n. Metode kedua akan menerapkan urutan dengan menggunakan objek acak (yang akan mengurutkan semua yang ada di memori) dan memilih nomor yang diteruskan ke pemanggilan metode.
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);
}
}
Saya menggunakan metode ini untuk mengambil berita acak dan berfungsi dengan baik;)
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);
Pilih 2 baris acak
Untuk menambah solusi Marc Gravell. Jika Anda tidak bekerja dengan kelas datacontext itu sendiri (karena Anda mem-proxy-nya misalnya untuk memalsukan datacontext untuk tujuan pengujian), Anda tidak dapat menggunakan UDF yang ditentukan secara langsung: itu tidak akan dikompilasi ke SQL karena Anda tidak menggunakannya dalam subclass atau kelas parsial dari kelas konteks data nyata Anda.
Solusi untuk masalah ini adalah dengan membuat fungsi Acak di proxy Anda, memberinya makan dengan kueri yang ingin Anda acak:
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());
}
}
Berikut adalah cara Anda menggunakannya dalam kode Anda:
var query = _dc.Repository<SomeEntity>();
query = _dc.Randomize(query);
Untuk menjadi lengkap, berikut adalah cara mengimplementasikannya di datacontext FAKE (yang digunakan dalam entitas memori):
public IOrderedQueryable<T> Randomize<T>(IQueryable<T> query)
{
return query.OrderBy(x => Guid.NewGuid());
}