Linq以实体,随机顺序

如何以随机顺序返回匹配实体?
只是要清楚这是entity framework的东西和LINQ到实体。

(空码)

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

简单的解决scheme是创build一个数组(或一个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()

这样做的一个简单的方法是通过Guid.NewGuid()进行sorting,然后在客户端进行sorting。 你可能会说服EF在服务器端做一些随机的事情,但这不一定是简单的 – 用“按随机数sorting”这个做法显然已经失效了 。

为了在.NET中而不是在EF中进行sorting,您需要使用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> 

除了其他任何东西外,洗牌比分类更有效率。 尽pipe如此,请参阅我的关于获取适当的Random实例的细节的随机性文章 。 Stack Overflow上有许多Fisher-Yates shuffle实现。

乔恩的答案是有帮助的,但实际上你可以让数据库使用Guid和Linq实体进行sorting(至less,你可以在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 

在我的testing中,对结果查询使用Take(10) (在SQL中转换为TOP 10 ),查询在包含1,794,785行的表的0.42和0.46秒之间运行一致。 不知道SQL Server是否对此进行了任何优化,或者是否为该表中的每一行生成了一个GUID。 无论哪种方式,这将比将所有这些行放入我的过程并尝试在那里sorting要快得多。

这里提供的解决scheme在客户端执行。 如果你想要在服务器上执行的东西, 这里是一个LINQ to SQL的解决scheme ,你可以转换成entity framework。

NewGuid hack对服务器端进行sorting不幸的是导致实体在连接的情况下被重复(或者渴望获取包括)。

看到这个问题关于这个问题。

为了克服这个问题,你可以使用一个随机的种子来代替NewGuid在一些唯一值计算服务器端进行一个sql checksum ,一个客户端计算一个随机种子来随机化它。 看到我以前的链接问题的答案 。

这个怎么样:

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

托罗的答案是我会使用的答案,而是这样的:

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

这是一个很好的做法(主要是为人们谷歌search)。

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

从理论上说(我还没有真正尝试过),下面应该做的诀窍:

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

 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; 

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

lolo_house有一个非常简洁,通用的解决scheme。 你只需要把代码放在一个单独的静态类中就可以工作。

 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。

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

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