Linq to Entities,随机顺序

时间:2009-03-17 16:06:20

标签: c# entity-framework linq-to-entities

如何以随机顺序返回匹配的实体?
要明确这是实体框架的东西和LINQ to Entities。

(航空代码)

IEnumerable<MyEntity> results = from en in context.MyEntity
                                where en.type == myTypeVar
                                orderby ?????
                                select en;

由于

修改
我尝试将其添加到上下文中:

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 个答案:

答案 0 :(得分:48)

执行此操作的一种简单方法是按Guid.NewGuid()排序,但随后在客户端进行排序。您可以说服EF在服务器端随机执行某些操作,但这并不一定简单 - 使用“按随机数排序”is apparently broken进行操作。

要在.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实例的详细信息,请参阅我的article on randomness。 Stack Overflow上有许多Fisher-Yates shuffle实现。

答案 1 :(得分:37)

Jon的回答很有帮助,但实际上你可以让数据库使用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)(在SQL中转换为TOP 10),查询对于具有1,794,785行的表一直在0.42和0.46秒之间运行。不知道SQL Server是否对此进行了任何类型的优化,或者是否为该表中的每个行生成了GUID。无论哪种方式,这比将所有这些行都放入我的进程并尝试在那里排序要快得多。

答案 2 :(得分:26)

简单的解决方案是创建一个数组(或List<T>),然后将其索引随机化。

编辑:

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

编辑:就个人而言,我发现Jon Skeet的答案更优雅:

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

当然,您可以使用随机数生成器而不是Guid.NewGuid()

答案 3 :(得分:3)

不幸的是,NewGuid hack用于对服务器端进行排序会导致实体在连接(或者急切获取包括)的情况下被复制。

有关此问题,请参阅this question

要解决此问题,您可以在某个唯一值计算服务器端使用NewGuid sql checksum,而在客户端计算随机种子以随机化它。有关之前关联的问题,请参阅my answer

答案 4 :(得分:2)

此处提供的解决方案在客户端上执行。如果您想在服务器上执行某些操作,可以将here is a solution for LINQ to SQL转换为Entity Framework。

答案 5 :(得分:0)

这个怎么样:


    var randomizer = new Random();
    var results = from en in context.MyEntity
                  where en.type == myTypeVar
                  let rand = randomizer.Next()
                  orderby rand
                  select en;

答案 6 :(得分:0)

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;
}

答案 7 :(得分:0)

这是一种很好的方法(主要是针对谷歌人)。

您还可以在末尾添加.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");

答案 8 :(得分:0)

从理论上讲(我还没有尝试过),以下应该可以解决问题:

在您的上下文类中添加一个分部类:

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; 

答案 9 :(得分:0)

(从EF Code First: How to get random rows交叉发布)

比较两个选项:


跳过(随机行数)

方法

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版本更快?如果您不必为了跳过而对它们进行排序,并且您可以合理地猜测要跳过的数量,那么Skip方法可能会更好。

答案 10 :(得分:0)

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。

答案 11 :(得分:-1)

我认为最好不要在课堂上添加属性。最好使用这个职位:

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