web-dev-qa-db-de.com

Linq zu Entitäten, zufällige Reihenfolge

Wie gebe ich passende Objekte in zufälliger Reihenfolge zurück?
Nur um klar zu sein, das ist Entity Framework und LINQ to Entities.

(Luftcode)

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

Vielen Dank

Bearbeiten:
Ich habe versucht, dies dem Kontext hinzuzufügen: 

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

Und mit dieser Abfrage:

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

Aber ich habe diesen Fehler bekommen:

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

Bearbeiten (aktueller Code):  

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

Die einfache Lösung wäre, ein Array (oder einen List<T>) zu erstellen und dann seine Indizes zu randomisieren.

BEARBEITEN:

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

EDIT: Ich persönlich finde die Antwort von Jon Skeet eleganter:

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

Natürlich können Sie anstelle von Guid.NewGuid() einen Zufallszahlengenerator verwenden.

26
Michael Damatov

Ein einfacher Weg, dies zu tun, ist die Bestellung über Guid.NewGuid(), aber dann erfolgt die Bestellung auf der Clientseite. Möglicherweise können Sie EF dazu bewegen, auf dem Server etwas Zufälliges zu tun, aber das ist nicht unbedingt einfach - und zwar mit "Reihenfolge nach Zufallszahl" ist anscheinend defekt .

Damit die Bestellung auf der .NET-Seite statt in EF ausgeführt wird, benötigen Sie AsEnumerable:

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

Es wäre besser, die ungeordnete -Version in eine Liste aufzunehmen und diese dann zu mischen.

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>

Das Mischen ist effizienter als das Sortieren, abgesehen von allem anderen. In meinem Artikel über Zufälligkeit finden Sie jedoch Informationen zum Erwerb einer geeigneten Random-Instanz. Es gibt viele Fisher-Yates-Shuffle-Implementierungen für Stack Overflow.

48
Jon Skeet

Jons Antwort ist hilfreich, aber eigentlich können Sie can die Reihenfolge mit Hilfe der Variablen Guid und Linq to Entities veranlassen (zumindest in EF4):

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

Dies erzeugt SQL, das wie folgt aussieht:

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

Bei meinem Test mit Take(10) für die resultierende Abfrage (konvertiert in TOP 10 in SQL) wurde die Abfrage konsistent zwischen 0,42 und 0,46 Sekunden gegen eine Tabelle mit 1.794.785 Zeilen ausgeführt. Keine Ahnung, ob der SQL Server irgendeine Art von Optimierung in dieser Hinsicht durchführt oder ob er eine GUID für every Zeile in dieser Tabelle generiert hat. Auf jeden Fall wäre das wesentlich schneller, als all diese Reihen in meinen Prozess zu bringen und zu versuchen, sie dort zu sortieren.

37
Drew Noakes

Der NewGuid-Hack für das Sortieren der Serverseite führt leider dazu, dass Entitäten im Fall von Joins (oder eifrigen Abruf-Includes) dupliziert werden.

Siehe diese Frage zu diesem Problem.

Um dieses Problem zu umgehen, können Sie anstelle von NewGuid eine SQL-Variable checksum auf einem bestimmten, vom Server berechneten Wert verwenden, wobei ein zufälliger Startwert einmal vom Client berechnet wird, um ihn zufällig zu sortieren. Siehe meine Antwort auf die zuvor verknüpfte Frage.

3
Frédéric

Die hier bereitgestellten Lösungen werden auf dem Client ausgeführt. Wenn Sie etwas möchten, das auf dem Server ausgeführt wird, ist hier eine Lösung für LINQ to SQL , die Sie in Entity Framework konvertieren können.

2
Fabrice

Toros Antwort ist die, die ich verwenden würde, aber eher so:

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

(Crossposting from EF Code First: Wie bekomme ich zufällige Zeilen )

Zwei Optionen vergleichen:


Überspringen (zufällige Anzahl von Zeilen)

Methode

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();
}
  • Nimmt 2 Anfragen an

Generierte 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

Methode

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

Generierte 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

In neueren EF können Sie also wieder sehen, dass NewGuid in SQL übersetzt wird (wie von @DrewNoakes https://stackoverflow.com/a/4120132/1037948 bestätigt). Obwohl beide Methoden "in-sql" sind, schätze ich, dass die Guid-Version schneller ist? Wenn Sie sie nicht sortieren müssen, um zu überspringen, und Sie könnten den Betrag, den Sie überspringen möchten, einigermaßen erraten, wäre die Skip-Methode möglicherweise besser.

0
drzaus

Hier ist ein schöner Weg, dies zu tun (hauptsächlich für Leute, die googeln).

Sie können auch .Take (n) am Ende hinzufügen, um nur eine festgelegte Nummer abzurufen.

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

Wie wäre es damit:


    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 hat eine wirklich saubere, einfache und generische Lösung. Sie müssen den Code nur in eine separate statische Klasse einfügen, damit er funktioniert.

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

Dann verwenden Sie den Code einfach:

var randomizeQuery = Query.Randomize();

So einfach! Vielen Dank, lolo_house.

0
Mythlandia

Theoretisch (ich habe es noch nicht ausprobiert), sollte Folgendes ausreichen: 

Fügen Sie Ihrer Kontextklasse eine Teilklasse hinzu:

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

implementierung : 

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