Linq to Entities, случайный порядок
Как вернуть совпадающие объекты в случайном порядке?
Просто для того, чтобы быть ясным, это материал Entity Framework и LINQ to Entities.
(воздушный код)
IEnumerable<MyEntity> results = from en in context.MyEntity
where en.type == myTypeVar
orderby ?????
select en;
Спасибо
Edit:
Я попытался добавить это в контекст:
public Guid Random()
{
return new Guid();
}
И используя этот запрос:
IEnumerable<MyEntity> results = from en in context.MyEntity
where en.type == myTypeVar
orderby context.Random()
select en;
Но я получил эту ошибку:
System.NotSupportedException: LINQ to Entities does not recognize the method 'System.Guid Random()' method, and this method cannot be translated into a store expression..
Изменить (текущий код):
IEnumerable<MyEntity> results = (from en in context.MyEntity
where en.type == myTypeVar
orderby context.Random()
select en).AsEnumerable();
Ответы
Ответ 1
Простым решением будет создание массива (или List<T>
) и рандомизация его индексов.
EDIT:
static IEnumerable<T> Randomize<T>(this IEnumerable<T> source) {
var array = source.ToArray();
// randomize indexes (several approaches are possible)
return array;
}
EDIT: Лично я считаю, что ответ Джона Скита более изящный:
var results = from ... in ... where ... orderby Guid.NewGuid() select ...
И конечно, вы можете взять генератор случайных чисел вместо Guid.NewGuid()
.
Ответ 2
Простым способом сделать это является заказ Guid.NewGuid()
, но затем заказ выполняется на стороне клиента. Возможно, вы сможете убедить EF сделать что-то случайное на стороне сервера, но это не обязательно простое - и выполнение этого с использованием "порядка по случайному числу" по-видимому, нарушено.
Чтобы сделать упорядочение на стороне .NET, а не в EF, вам нужно AsEnumerable
:
IEnumerable<MyEntity> results = context.MyEntity
.Where(en => en.type == myTypeVar)
.AsEnumerable()
.OrderBy(en => context.Random());
Было бы лучше получить неупорядоченную версию в списке, а затем перетасовать ее.
Random rnd = ...; // Assume a suitable Random instance
List<MyEntity> results = context.MyEntity
.Where(en => en.type == myTypeVar)
.ToList();
results.Shuffle(rnd); // Assuming an extension method on List<T>
Перетасовка более эффективна, чем сортировка, кроме всего остального.
См. Статью о случайности для получения подробной информации о приобретении соответствующего экземпляра Random
. В переполнении стека есть множество реализаций Shuffle от Fisher-Yates.
Ответ 3
Ответ на Джона полезен, но на самом деле вы можете сделать БД упорядочением, используя Guid
и Linq to Entities (по крайней мере, вы можете в EF4):
from e in MyEntities
orderby Guid.NewGuid()
select e
Это генерирует SQL, похожий на:
SELECT
[Project1].[Id] AS [Id],
[Project1].[Column1] AS [Column1]
FROM ( SELECT
NEWID() AS [C1], -- Guid created here
[Extent1].[Id] AS [Id],
[Extent1].[Column1] AS [Column1],
FROM [dbo].[MyEntities] AS [Extent1]
) AS [Project1]
ORDER BY [Project1].[C1] ASC -- Used for sorting here
В моем тестировании, используя Take(10)
в результирующем запросе (преобразуется в TOP 10
в SQL), запрос выполнялся последовательно между 0,42 и 0,46 сек против таблицы с 1 784 785 строк. Не знаю, делает ли SQL Server какую-либо оптимизацию на этом или генерирует ли GUID для каждой строки в этой таблице. В любом случае это будет значительно быстрее, чем перенос всех этих строк в мой процесс и их сортировка там.
Ответ 4
Представленные здесь решения выполняются на клиенте. Если вы хотите что-то, что выполняется на сервере, вот решение для LINQ to SQL, которое вы можете преобразовать в Entity Framework.
Ответ 5
Разрушение NewGuid
для сортировки его на стороне сервера, к сожалению, заставляет сущности дублироваться в случае объединения (или включает в себя желание).
См. этот вопрос об этой проблеме.
Чтобы преодолеть эту проблему, вы можете использовать вместо NewGuid
sql checksum
на некоторой стороне с уникальным значением, вычисляемой сервером, со случайным посевом, вычисленным на стороне клиента, для рандомизации. См. мой ответ по ранее связанному вопросу.
Ответ 6
Как насчет этого:
var randomizer = new Random();
var results = from en in context.MyEntity
where en.type == myTypeVar
let rand = randomizer.Next()
orderby rand
select en;
Ответ 7
Ответ Toro - это тот, который я бы использовал, но скорее вот так:
static IEnumerable<T> Randomize<T>(this IEnumerable<T> source)
{
var list = source.ToList();
var newList = new List<T>();
while (source.Count > 0)
{
//choose random one and MOVE it from list to newList
}
return newList;
}
Ответ 8
Вот хороший способ сделать это (в основном для людей Googling).
Вы также можете добавить .Take(n) в конец, чтобы получить только заданное число.
model.CreateQuery<MyEntity>(
@"select value source.entity
from (select entity, SqlServer.NewID() as rand
from Products as entity
where entity.type == myTypeVar) as source
order by source.rand");
Ответ 9
Теоретически (я еще не пробовал это делать), следующее должно сделать трюк:
Добавьте в класс контекста частичный класс:
public partial class MyDataContext{
[Function(Name = "NEWID", IsComposable = true)]
public Guid Random()
{
// you can put anything you want here, it makes no difference
throw new NotImplementedException();
}
}
реализация:
from t in context.MyTable
orderby context.Random()
select t;
Ответ 10
(перекрестная отправка из EF Code First: как получить случайные строки)
Сравнение двух параметров:
Пропустить (случайное число строк)
Метод
private T getRandomEntity<T>(IGenericRepository<T> repo) where T : EntityWithPk<Guid> {
var skip = (int)(rand.NextDouble() * repo.Items.Count());
return repo.Items.OrderBy(o => o.ID).Skip(skip).Take(1).First();
}
Сгенерированный SQL
SELECT [GroupBy1].[A1] AS [C1]
FROM (SELECT COUNT(1) AS [A1]
FROM [dbo].[People] AS [Extent1]) AS [GroupBy1];
SELECT TOP (1) [Extent1].[ID] AS [ID],
[Extent1].[Name] AS [Name],
[Extent1].[Age] AS [Age],
[Extent1].[FavoriteColor] AS [FavoriteColor]
FROM (SELECT [Extent1].[ID] AS [ID],
[Extent1].[Name] AS [Name],
[Extent1].[Age] AS [Age],
[Extent1].[FavoriteColor] AS [FavoriteColor],
row_number() OVER (ORDER BY [Extent1].[ID] ASC) AS [row_number]
FROM [dbo].[People] AS [Extent1]) AS [Extent1]
WHERE [Extent1].[row_number] > 15
ORDER BY [Extent1].[ID] ASC;
Guid
Метод
private T getRandomEntityInPlace<T>(IGenericRepository<T> repo) {
return repo.Items.OrderBy(o => Guid.NewGuid()).First();
}
Сгенерированный SQL
SELECT TOP (1) [Project1].[ID] AS [ID],
[Project1].[Name] AS [Name],
[Project1].[Age] AS [Age],
[Project1].[FavoriteColor] AS [FavoriteColor]
FROM (SELECT NEWID() AS [C1],
[Extent1].[ID] AS [ID],
[Extent1].[Name] AS [Name],
[Extent1].[Age] AS [Age],
[Extent1].[FavoriteColor] AS [FavoriteColor]
FROM [dbo].[People] AS [Extent1]) AS [Project1]
ORDER BY [Project1].[C1] ASC
Итак, в новом EF вы можете снова увидеть, что NewGuid
переведен в SQL (как подтверждено @DrewNoakes fooobar.com/questions/53881/...). Хотя оба метода "in-sql", я предполагаю, что версия Guid быстрее? Если вам не нужно сортировать их, чтобы пропустить, и вы могли бы разумно угадать сумму, которую нужно пропустить, тогда, возможно, метод Skip был бы лучше.
Ответ 11
lolo_house имеет действительно опрятное, простое и универсальное решение. Вам просто нужно поместить код в отдельный статический класс, чтобы он работал.
using System;
using System.Collections.Generic;
using System.Linq;
namespace SpanishDrills.Utilities
{
public static class LinqHelper
{
public static IEnumerable<T> Randomize<T>(this IEnumerable<T> pCol)
{
List<T> lResultado = new List<T>();
List<T> lLista = pCol.ToList();
Random lRandom = new Random();
int lintPos = 0;
while (lLista.Count > 0)
{
lintPos = lRandom.Next(lLista.Count);
lResultado.Add(lLista[lintPos]);
lLista.RemoveAt(lintPos);
}
return lResultado;
}
}
}
Затем для использования кода просто выполните:
var randomizeQuery = Query.Randomize();
Так просто! Спасибо lolo_house.
Ответ 12
Я думаю, что лучше не добавлять свойства к классу. Лучше использовать позицию:
public static IEnumerable<T> Randomize<T>(this IEnumerable<T> pCol)
{
List<T> lResultado = new List<T>();
List<T> lLista = pCol.ToList();
Random lRandom = new Random();
int lintPos = 0;
while (lLista.Count > 0)
{
lintPos = lRandom.Next(lLista.Count);
lResultado.Add(lLista[lintPos]);
lLista.RemoveAt(lintPos);
}
return lResultado;
}
И вызов будет (как toList() или toArray()):
var result = IEnumerable.Where(..). Randomize();