Каков наилучший (и самый быстрый) способ получить случайную строку с помощью Linq to SQL, когда у меня есть условие, например, какое-то поле должно быть истинным?
Каков наилучший (и самый быстрый) способ получить случайную строку с помощью Linq to SQL, когда у меня есть условие, например, какое-то поле должно быть истинным?
Ответы:
Вы можете сделать это в базе данных, используя поддельный UDF; в частичном классе добавьте метод в контекст данных:
partial class MyDataContext {
[Function(Name="NEWID", IsComposable=true)]
public Guid Random()
{ // to prove not used by our C# code...
throw new NotImplementedException();
}
}
Тогда просто order by ctx.Random()
; это сделает случайный заказ на SQL-сервере NEWID()
. т.е.
var cust = (from row in ctx.Customers
where row.IsActive // your filter
orderby ctx.Random()
select row).FirstOrDefault();
Обратите внимание, что это подходит только для столов малого и среднего размера; для огромных таблиц это повлияет на производительность сервера, и будет более эффективно найти количество строк ( Count
), а затем выбрать одну случайным образом ( Skip/First
).
для счетного подхода:
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
Еще один пример для Entity Framework:
var customers = db.Customers
.Where(c => c.IsActive)
.OrderBy(c => Guid.NewGuid())
.FirstOrDefault();
Это не работает с LINQ to SQL. OrderBy
Просто при падении.
РЕДАКТИРОВАТЬ: Я только что заметил, что это LINQ to SQL, а не LINQ to Objects. Используйте код Марка, чтобы база данных сделала это за вас. Я оставил этот ответ здесь как потенциальный интерес для LINQ to Objects.
Как ни странно, на самом деле вам не нужно подсчитывать. Однако вам нужно получить каждый элемент, если вы не получите счетчик.
Что вы можете сделать, так это сохранить представление о «текущем» значении и текущем счетчике. Когда вы выбираете следующее значение, возьмите случайное число и замените «текущее» на «новое» с вероятностью 1 / n, где n - количество.
Поэтому, когда вы читаете первое значение, вы всегда делаете его «текущим» значением. Когда вы читаете второе значение, вы можете сделать это текущее значение (вероятность 1/2). Когда вы читаете третье значение, вы можете сделать это текущее значение (вероятность 1/3) и т. Д. Когда у вас закончились данные, текущее значение является случайным из всех считанных вами значений с равномерной вероятностью.
Чтобы применить это с условием, просто игнорируйте все, что не соответствует условию. Самый простой способ сделать это - рассмотреть для начала только «совпадающую» последовательность, применив сначала предложение Where.
Вот быстрая реализация. Я думаю , что это нормально ...
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
будет всегда быть установлен на первый элемент. На второй итерации происходит изменение на 50%, которое будет установлено для второго элемента. На третьей итерации с вероятностью 33% будет установлен третий элемент. Добавление оператора break будет означать, что вы всегда будете выходить после прочтения первого элемента, что делает его совсем не случайным.
Один из способов добиться эффективного - добавить к вашим данным столбец, Shuffle
который заполняется случайным int (по мере создания каждой записи).
Частичный запрос для доступа к таблице в случайном порядке ...
Random random = new Random();
int seed = random.Next();
result = result.OrderBy(s => (~(s.Shuffle & seed)) & (s.Shuffle | seed)); // ^ seed);
Это выполняет операцию XOR в базе данных и упорядочивает результаты этого XOR.
Преимущества: -
Это подход, используемый моей системой домашней автоматизации для рандомизации списков воспроизведения. Он выбирает новое начальное число каждый день, давая постоянный порядок в течение дня (позволяя легко приостанавливать / возобновлять воспроизведение), но каждый день свежим взглядом на каждый список воспроизведения.
result = result.OrderBy(s => s.Shuffle ^ seed);
(т.е. нет необходимости реализовывать XOR с помощью операторов ~, & и |).
если вы хотите получить, например, var count = 16
случайные строки из таблицы, вы можете написать
var rows = Table.OrderBy(t => Guid.NewGuid())
.Take(count);
здесь я использовал EF, а таблица - это Dbset
Если целью получения случайных строк является выборка, я очень кратко рассказал здесь о хорошем подходе Ларсона и др., Исследовательской группы Microsoft, где они разработали структуру выборки для Sql Server с использованием материализованных представлений. Также есть ссылка на сам документ.
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();
Объяснение: При вставке guid (который является случайным) порядок с помощью orderby будет случайным.
Пришел сюда, задаваясь вопросом, как получить несколько случайных страниц из небольшого их количества, чтобы каждый пользователь получил несколько разных случайных 3 страниц.
Это мое последнее решение: работа с запросами с LINQ по списку страниц в Sharepoint 2010. Это в Visual Basic, извините: p
Dim Aleatorio As New Random()
Dim Paginas = From a As SPListItem In Sitio.RootWeb.Lists("Páginas") Order By Aleatorio.Next Take 3
Вероятно, следует провести профилирование, прежде чем запрашивать большое количество результатов, но это идеально подходит для моей цели
У меня есть случайный запрос функции к DataTable
s:
var result = (from result in dt.AsEnumerable()
order by Guid.NewGuid()
select result).Take(3);
В приведенном ниже примере вызывается источник для получения счетчика, а затем применяется выражение пропуска к источнику с числом от 0 до n. Второй метод применяет порядок, используя случайный объект (который упорядочит все в памяти) и выбирает число, переданное в вызов метода.
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);
}
}
Я использую этот метод для получения случайных новостей, и он отлично работает;)
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);
Выбрать случайные 2 строки
Чтобы добавить к раствору Марка Гравелла. Если вы не работаете с самим классом datacontext (потому что вы каким-то образом проксируете его, например, чтобы подделать текст данных в целях тестирования), вы не можете напрямую использовать определенный UDF: он не будет скомпилирован в SQL, потому что вы не используете его в подкласс или частичный класс вашего реального класса контекста данных.
Обходной путь для этой проблемы - создать функцию Randomize в вашем прокси-сервере, передав ей запрос, который вы хотите рандомизировать:
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());
}
}
Вот как вы бы использовали это в своем коде:
var query = _dc.Repository<SomeEntity>();
query = _dc.Randomize(query);
Чтобы завершить, вот как это реализовать в поддельном тексте данных (который используется в объектах памяти):
public IOrderedQueryable<T> Randomize<T>(IQueryable<T> query)
{
return query.OrderBy(x => Guid.NewGuid());
}