【问题标题】:Linq to Entities, random orderLinq to Entity,随机顺序
【发布时间】:2010-10-13 21:13:48
【问题描述】:

如何以随机顺序返回匹配的实体?
需要明确的是,这是实体框架的东西和 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();

【问题讨论】:

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


    【解决方案1】:

    执行此操作的一种简单方法是通过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>
    

    除其他外,洗牌比排序更有效。 请参阅我的article on randomness,了解有关获取适当Random 实例的详细信息。 Stack Overflow 上有很多 Fisher-Yates shuffle 实现。

    【讨论】:

    • 嗨,乔恩,我试过了,但无法正常工作 - 请参阅我的编辑。谢谢内斯
    • 我没有看到 .ToEnumerable() 我错过了命名空间吗?
    • 嗨乔恩,我仍然得到与上述更改相同的错误。还有 .OrderBy(context => context.Random());行不起作用,因为我收到有关重新定义上下文的错误。我会用我最新的查询更新我的 q。谢谢
    • 我很抱歉 - 这就是试图在一点点空闲时间回答的结果。 AsEnumerable() 应该没问题,但是 lambda 表达式被破坏了。立即尝试。
    • 也许它是 EF4 中的新功能,但您可以在 DB 中执行此操作:stackoverflow.com/questions/654906/…
    【解决方案2】:

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

    编辑:

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

    【讨论】:

    • 您好 toro,抱歉,我没有看到在 List 上使用什么方法,您能详细说明一下吗?谢谢。
    • 没有框架方法可以做到这一点。我建议en.wikipedia.org/wiki/Fisher-Yates_shuffle
    • 谢谢 mquander,这就是我所追求的。
    • 我想对数百万行执行此操作,因此实际上无法将所有这些实体带入我的上下文并开始对其进行排序。您可以使用我在下面发布的 Jon 答案的变体在数据库中执行此操作:stackoverflow.com/questions/654906/…
    • 通过不稳定的排名函数排序看起来不安全。根据底层的排序算法,它可能会导致错误。请参阅this blog 了解详细信息,this question 了解由此产生的错误案例。
    【解决方案3】:

    这个怎么样:

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

    【讨论】:

    • 我在编辑中遇到与 Guid 方法类似的错误:LINQ to Entities 无法识别方法 'Int32 Next()' 方法,并且此方法无法转换为存储表达式..
    • Jon 的回答中发布的 AsEnumerable 运算符应该可以解决问题。
    • 以下代码适用于我的环境: var randomizer = new Random(); var result = context.MyEntity .Where(en => en.type == myTypeVar) .AsEnumerable() .OrderBy(en => randomizer.Next());
    • 是的,它似乎是这样工作的,但不是在 from en in context... 格式中。
    【解决方案4】:

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

    【讨论】:

    • 无需创建两个列表 - 您可以以随机播放的方式交换列表中的元素。这需要小心翼翼地完成,但它比无缘无故地创建另一个副本更好 (IMO)。
    • 可以,但会降低代码的可读性。恕我直言,这种方式更好,因为它更清晰。请记住,我们主要对引用进行操作,而不是值,因此除了列表本身之外没有太多内存成本。
    【解决方案5】:

    此处提供的解决方案在客户端上执行。如果你想要在服务器上执行的东西,here is a solution for LINQ to SQL 你可以转换成实体框架。

    【讨论】:

    • 此解决方案可能会导致在服务器上执行,但它还要求您有权访问 SQL 框本身的设置函数和视图。
    【解决方案6】:

    这是一个很好的方法(主要用于谷歌搜索)。

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

    【讨论】:

      【解决方案7】:

      Jon 的回答很有帮助,但实际上您可以让 DB 使用 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。无论哪种方式,这都比将所有这些行带入我的流程并尝试在那里对其进行排序要快得多。

      【讨论】:

      • 注意:如果您在 LinqPad 中运行此代码,但它不起作用,这可能会有所帮助:stackoverflow.com/a/20953863/740639
      • this question,不幸的是它坏了。看起来OrderBy 假设排名函数是稳定的,而随机生成器并非如此。 Linq to entity 将其转换为 sql 查询,该查询可能会为同一实体获得不同的排名(只要您的查询使用 Include)。然后它会导致实体在结果列表中重复。
      【解决方案8】:

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

      将部分类添加到您的上下文类:

      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; 
      

      【讨论】:

      • 看起来很有趣,但我找不到 FunctionAttribute 的参考资料。你的意思是EdmFunction 还是DbFunction
      【解决方案9】:

      我认为最好不要在类中添加属性。更好地使用位置:

      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 结果 = IEnumerable.Where(..).Randomize();

      【讨论】:

        【解决方案10】:

        (来自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;
        

        指南

        方法

        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 方法可能会更好。

        【讨论】:

          【解决方案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】:

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

            请参阅this question 了解此问题。

            为了克服这个问题,您可以在某些唯一值计算的服务器端使用 sql checksum 代替 NewGuid,并在客户端计算一次随机种子以随机化它。有关先前链接的问题,请参阅 my answer

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2015-08-20
              • 2012-11-15
              • 2011-04-28
              • 2011-06-26
              相关资源
              最近更新 更多