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