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