简体   繁体   English

Linq 到实体,随机顺序

[英]Linq to Entities, random order

How do i return matching entities in a random order?如何以随机顺序返回匹配的实体?
Just to be clear this is Entity Framework stuff and LINQ to Entities.只是要清楚这是实体框架的东西和 LINQ 到实体。

(air code) (航空代码)

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

Thanks谢谢

Edit:编辑:
I tried adding this to the context:我尝试将其添加到上下文中:

public Guid Random()
{
    return new Guid();
}

And using this query:并使用此查询:

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

But i got this error:但我得到了这个错误:

System.NotSupportedException: LINQ to Entities does not recognize the method 'System.Guid Random()' method, and this method cannot be translated into a store expression..

Edit (Current code):编辑(当前代码):

IEnumerable<MyEntity> results = (from en in context.MyEntity
                                 where en.type == myTypeVar
                                 orderby context.Random()
                                 select en).AsEnumerable();

A simple way of doing this is to order by Guid.NewGuid() but then the ordering happens on the client side.一种简单的方法是通过Guid.NewGuid()进行排序,但随后排序发生在客户端。 You may be able to persuade EF to do something random on the server side, but that's not necessarily simple - and doing it using "order by random number" is apparently broken .您也许可以说服 EF 在服务器端做一些随机的事情,但这并不一定简单——而且使用“按随机数排序”显然是有问题的。

To make the ordering happen on the .NET side instead of in EF, you need AsEnumerable :要在 .NET 端而不是在 EF 端进行排序,您需要AsEnumerable

IEnumerable<MyEntity> results = context.MyEntity
                                       .Where(en => en.type == myTypeVar)
                                       .AsEnumerable()
                                       .OrderBy(en => context.Random());

It would be better to get the unordered version in a list and then shuffle that though.最好在列表中获取无序版本,然后再随机播放。

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>

Shuffling is more efficient than sorting, aside from anything else.洗牌比排序更有效,除此之外。 See my article on randomness for details about acquiring an appropriate Random instance though.有关获取适当Random实例的详细信息,请参阅我关于随机性的文章 There are lots of Fisher-Yates shuffle implementations available on Stack Overflow. Stack Overflow 上有很多 Fisher-Yates shuffle 实现。

Jon's answer is helpful, but actually you can have the DB do the ordering using Guid and Linq to Entities (at least, you can in EF4): Jon 的回答很有帮助,但实际上你可以让数据库使用Guid和 Linq 对实体进行排序(至少,你可以在 EF4 中):

from e in MyEntities
orderby Guid.NewGuid()
select e

This generates SQL resembling:这会生成 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

In my testing, using Take(10) on the resulting query (converts to TOP 10 in SQL), the query ran consistently between 0.42 and 0.46 sec against a table with 1,794,785 rows.在我的测试中,在生成的查询上使用Take(10) (在 SQL 中转换为TOP 10 ),该查询针对具有 1,794,785 行的表始终在 0.42 到 0.46 秒之间运行。 No idea whether SQL Server does any kind of optimisation on this or whether it generated a GUID for every row in that table.不知道 SQL 服务器是否对此进行了任何优化,或者是否为该表中的每一行生成了 GUID。 Either way, that would be considerably faster than bringing all those rows into my process and trying to sort them there.无论哪种方式,这都比将所有这些行带入我的流程并尝试在那里对其进行排序要快得多。

The simple solution would be creating an array (or a List<T> ) and than randomize its indexes.简单的解决方案是创建一个数组(或List<T> ),然后随机化它的索引。

EDIT:编辑:

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

EDIT: Personally, I find the answer of Jon Skeet is more elegant:编辑:就个人而言,我发现 Jon Skeet 的答案更优雅:

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

And sure, you can take a random number generator instead of Guid.NewGuid() .当然,您可以使用随机数生成器代替Guid.NewGuid()

The NewGuid hack for sorting it server side unfortunately causes entities to get duplicated in case of joins (or eager fetching includes).不幸的是,用于对其服务器端进行排序的NewGuid hack 会导致实体在连接(或急切获取包含)的情况下重复。

See this question about this issue.请参阅有关此问题的此问题。

To overcome this issue, you may use instead of NewGuid a sql checksum on some unique value computed server side, with a random seed computed once client side to randomize it.为了克服这个问题,您可以在一些唯一值计算的服务器端使用 sql checksum代替NewGuid ,并在客户端计算一次随机种子以对其进行随机化。 See my answer on previously linked question.请参阅对先前链接问题的回答。

The solutions provided here execute on the client.此处提供的解决方案在客户端上执行。 If you want something that executes on the server, here is a solution for LINQ to SQL that you can convert to Entity Framework.如果您想要在服务器上执行的东西, 这里是 LINQ 到 SQL 的解决方案,您可以将其转换为实体框架。

Theoretically speaking (I haven't actually tried it yet), the following should do the trick:从理论上讲(我还没有真正尝试过),以下应该可以解决问题:

Add a partial class to your context class:将部分 class 添加到您的上下文 class 中:

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

implementation:执行:

from t in context.MyTable
orderby  context.Random()
select t; 

(cross-posting from EF Code First: How to get random rows ) (来自EF Code First 的交叉发布:如何获取随机行

Comparing two options:比较两个选项:


Skip(random number of rows)跳过(随机行数)

Method方法

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();
}
  • Takes 2 queries需要 2 个查询

Generated SQL生成 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指导

Method方法

private T getRandomEntityInPlace<T>(IGenericRepository<T> repo) {
    return repo.Items.OrderBy(o => Guid.NewGuid()).First();
}

Generated SQL生成 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

So in newer EF, you can again see that NewGuid is translated into SQL (as confirmed by @DrewNoakes https://stackoverflow.com/a/4120132/1037948 ).所以在较新的 EF 中,您可以再次看到NewGuid被翻译成 SQL(由@DrewNoakes https://stackoverflow.com/a/4120132/1037948确认)。 Even though both are "in-sql" methods, I'm guessing the Guid version is faster?即使两者都是“in-sql”方法,我猜 Guid 版本更快? If you didn't have to sort them in order to skip, and you could reasonably guess the amount to skip, then maybe the Skip method would be better.如果您不必为了跳过而对它们进行排序,并且您可以合理地猜测要跳过的数量,那么 Skip 方法可能会更好。

lolo_house has a really neat, simple and generic solution. lolo_house 有一个非常简洁、简单和通用的解决方案。 You just need to put the code in a separate static class to make it work.您只需将代码放入单独的 static class 即可使其工作。

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

Then to use the code just do:然后使用代码只是做:

var randomizeQuery = Query.Randomize();

So simple.很简单。 Thank you lolo_house.谢谢lolo_house。

Here is a nice way of doing this (mainly for people Googling).这是一个很好的方法(主要用于谷歌搜索)。

You can also add.Take(n) on the end to only retrieve a set number.您还可以在末尾添加.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");

How about this:这个怎么样:


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

Toro's answer is the one I would use, but rather like this: 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;
}

I think it's better not to add properties to the class.我认为最好不要向 class 添加属性。 Better to use the position:最好使用 position:

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

And the call will (as toList() or toArray()):并且调用将(如 toList() 或 toArray()):

var result = IEnumerable.Where(..).Randomize(); var 结果 = IEnumerable.Where(..).Randomize();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM