web-dev-qa-db-ja.com

エンティティへのLinq、ランダムな順序

一致するエンティティをランダムな順序で返すにはどうすればよいですか?
明確にする必要があるのは、これがEntity Frameworkのものと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();
36
NikolaiDante

簡単な解決策は、配列(または_List<T>_)を作成し、そのインデックスをランダム化することです。

編集:

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

編集:個人的に、私はジョン・スキートの答えがよりエレガントだと思います:

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

そして確かに、Guid.NewGuid()の代わりに乱数ジェネレータを使用できます。

26
Michael Damatov

これを行う簡単な方法は、Guid.NewGuid()で順序付けすることですが、順序付けはクライアント側で行われます。サーバー側でランダムに何かを行うようにEFを説得できるかもしれませんが、それは必ずしも単純ではありません-「ランダムな順序による順序付け」を使用してそれを行う 明らかに壊れている

EFではなく.NET側で順序付けを行うには、AsEnumerableが必要です。

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

unorderedバージョンをリストで取得し、それをシャッフルすることをお勧めします。

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インスタンスを取得する方法の詳細については、私の ランダム性に関する記事 を参照してください。 Stack Overflowで利用可能なFisher-Yatesシャッフル実装はたくさんあります。

52
Jon Skeet

ジョンの答えは役に立ちますが、実際にはcanGuidとLinq to Entitiesを使用してDBに順序付けを行わせます(少なくとも、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 for everyの行をテーブルに生成したのか)はわかりません。どちらの方法でも、これらすべての行を私のプロセスに取り込み、そこで並べ替えを試みます。

37
Drew Noakes

サーバー側で並べ替えるNewGuidハックは、結合(または熱心なフェッチインクルード)の場合に、残念ながらエンティティを複製させます。

この問題について この質問 を参照してください。

この問題を克服するには、サーバー側でNewGuidの代わりにsql checksumを使用し、クライアント側でランダムシードを計算してランダム化します。以前にリンクされた質問について my answer を参照してください。

3
Frédéric

ここで提供されるソリューションはクライアントで実行されます。サーバー上で実行するものが必要な場合、 これはLINQ to SQLのソリューションです エンティティフレームワークに変換できます。

2
Fabrice

トロの答えは私が使うものですが、むしろ次のようになります:

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

(クロスポスト EFコードを最初に:ランダムな行を取得する方法

2つのオプションの比較:


スキップ(ランダムな行数)

方法

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 で確認されています)。どちらも「SQL内」メソッドですが、Guidバージョンの方が速いと思いますか?スキップするためにそれらを並べ替える必要がなく、スキップする量を合理的に推測できる場合は、Skipメソッドの方が良いでしょう。

0
drzaus

これはこれを行うための素晴らしい方法です(主にグーグルの人々のために)。

末尾に.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");
0
Jamie

これはどう:


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

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に感謝します。

0
Mythlandia

理論的に言えば(まだ実際に試したことはありません)、次の方法でうまくいくはずです。

部分クラスをコンテキストクラスに追加します。

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