Linq to Entities, случайный порядок - PullRequest
36 голосов
/ 17 марта 2009

Как вернуть совпадающие объекты в случайном порядке?
Просто чтобы прояснить это вещи 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();

Ответы [ 12 ]

48 голосов
/ 17 марта 2009

Простой способ сделать это - заказать по 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 экземпляра. В переполнении стека доступно множество реализаций случайных чисел Фишера-Йейтса.

37 голосов
/ 08 ноября 2010

Ответ Джона полезен, но на самом деле вы можете сделать так, чтобы БД выполняла упорядочение, используя 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 с для таблицы с 1794 785 строками. Не знаю, выполняет ли SQL Server какую-либо оптимизацию или генерирует GUID для каждой строки в этой таблице. В любом случае, это было бы значительно быстрее, чем вводить все эти строки в мой процесс и пытаться отсортировать их там.

26 голосов
/ 17 марта 2009

Простым решением было бы создать массив (или List<T>) и затем рандомизировать его индексы.

EDIT:

static IEnumerable<T> Randomize<T>(this IEnumerable<T> source) {
  var array = source.ToArray();
  // randomize indexes (several approaches are possible)
  return array;
}

РЕДАКТИРОВАТЬ: Лично я нахожу ответ Джона Скита более элегантным:

var results = from ... in ... where ... orderby Guid.NewGuid() select ...

И, конечно же, вы можете взять генератор случайных чисел вместо Guid.NewGuid().

3 голосов
/ 06 июля 2015

Хак NewGuid для сортировки на стороне сервера, к сожалению, приводит к дублированию сущностей в случае объединений (или включает в себя активную выборку).

См. этот вопрос об этой проблеме.

Чтобы преодолеть эту проблему, вы можете использовать вместо NewGuid sql checksum на стороне сервера, вычисляемой по уникальному значению, со случайным начальным числом, вычисляемым однажды на стороне клиента, для его рандомизации. См. мой ответ по ранее связанному вопросу.

2 голосов
/ 24 марта 2009

Решения, представленные здесь, выполняются на клиенте. Если вы хотите что-то, что выполняется на сервере, вот решение для LINQ to SQL , которое вы можете преобразовать в Entity Framework.

0 голосов
/ 26 июня 2014

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.

0 голосов
/ 14 июня 2013

(перекрестная публикация из 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();
}
  • Занимает 2 запроса

Сгенерированный 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 https://stackoverflow.com/a/4120132/1037948). Несмотря на то, что оба метода "in-sql", я предполагаю версию Guid быстрее? Если вам не нужно было сортировать их, чтобы пропустить, и вы могли бы разумно угадать количество пропущенных, то, возможно, метод пропуска будет лучше.

0 голосов
/ 03 апреля 2013

Я думаю, что лучше не добавлять свойства в класс. Лучше использовать позицию:

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 ();

0 голосов
/ 01 января 2013

Теоретически (на самом деле я еще не пробовал), следующее должно помочь:

Добавьте частичный класс в свой контекстный класс:

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; 
0 голосов
/ 07 октября 2010

Вот хороший способ сделать это (в основном для людей, гуглящих).

Вы также можете добавить .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");
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...