Случайный ряд из Линк в Sql
Каков наилучший (и самый быстрый) способ получения случайной строки с использованием Linq to SQL, когда у меня есть условие, например, какое-то поле должно быть истинным?
15 ответов
Вы можете сделать это в базе данных, используя поддельный 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;
}
Один из способов добиться этого - добавить столбец к вашим данным. Shuffle
это заполнено случайным int (поскольку каждая запись создана).
Частичный запрос для доступа к таблице в случайном порядке...
Random random = new Random();
int seed = random.Next();
result = result.OrderBy(s => (~(s.Shuffle & seed)) & (s.Shuffle | seed)); // ^ seed);
Это делает операцию XOR в базе данных и упорядочивает результаты этого XOR.
Преимущества:-
- Эффективно: SQL обрабатывает порядок, нет необходимости извлекать всю таблицу
- Repeatable: (подходит для тестирования) - может использовать одно и то же случайное начальное число для генерации того же случайного порядка
Этот подход используется моей системой домашней автоматизации для рандомизации списков воспроизведения. Он выбирает новое начальное число каждый день, давая последовательный порядок в течение дня (позволяя легко приостанавливать / возобновлять возможности), но каждый новый взгляд на каждый плейлист по-новому.
Если вы хотите получить, например, var count = 16
случайные строки из таблицы, вы можете написать
var rows = Table.OrderBy(t => Guid.NewGuid())
.Take(count);
здесь я использовал EF, а таблица - это Dbset
Пришел сюда, задаваясь вопросом, как получить несколько случайных страниц из небольшого их числа, чтобы каждый пользователь получил несколько разных случайных 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
Вероятно, нужно получить некоторое профилирование, прежде чем запрашивать большое количество результатов, но это идеально подходит для моей цели
Если целью получения случайных строк является выборка, я очень кратко говорил здесь о хорошем подходе от Ларсона и др., Команды Microsoft Research, где они разработали структуру выборки для 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 будет случайным.
Если вы используете LINQPad, переключитесь в режим программирования C# и сделайте так:
void Main()
{
YourTable.OrderBy(v => Random()).FirstOrDefault.Dump();
}
[Function(Name = "NEWID", IsComposable = true)]
public Guid Random()
{
throw new NotImplementedException();
}
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);
Чтобы завершить, вот как это реализовать в текстовом тексте FAKE (который используется в объектах памяти):
public IOrderedQueryable<T> Randomize<T>(IQueryable<T> query)
{
return query.OrderBy(x => Guid.NewGuid());
}
Использование LINQ to SQL в LINQPad в качестве операторов C# выглядит
IEnumerable<Customer> customers = this.ExecuteQuery<Customer>(@"SELECT top 10 * from [Customers] order by newid()");
customers.Dump();
Сгенерированный SQL
SELECT top 10 * from [Customers] order by newid()
У меня есть случайный запрос функции против 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;
}