Подтвердить что ты не робот

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

Ответ 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();
}
  • Принимает 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 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();