web-dev-qa-db-fra.com

Linq to Entities, ordre aléatoire

Comment puis-je retourner des entités correspondantes dans un ordre aléatoire?
Juste pour être clair, il s’agit de trucs Entity Framework et LINQ to Entities.

(code aérien)

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

Merci

Modifier:
J'ai essayé d'ajouter ceci au contexte: 

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

Et en utilisant cette requête:

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

Mais j'ai eu cette erreur:

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

Modifier (Code actuel):  

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

La solution simple serait de créer un tableau (ou un List<T>) et d’aléatoire ses index.

MODIFIER:

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

EDIT: Personnellement, je trouve la réponse de Jon Skeet plus élégante:

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

Et bien sûr, vous pouvez utiliser un générateur de nombres aléatoires au lieu de Guid.NewGuid().

26
Michael Damatov

Une façon simple de procéder consiste à commander par Guid.NewGuid() mais ensuite, la commande s’effectue côté client. Vous pourrez peut-être persuader EF de faire quelque chose d’aléatoire sur le serveur, mais ce n’est pas forcément simple - et le faire en utilisant "ordre par nombre aléatoire" est apparemment cassé .

Pour que la commande se passe du côté .NET au lieu de EF, vous avez besoin de AsEnumerable:

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

Il serait préférable d’obtenir la version unordered dans une liste, puis de la mélanger.

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>

Le brassage est plus efficace que le tri, mis à part quoi que ce soit d'autre ... Voir mon article sur le caractère aléatoire pour plus de détails sur l'acquisition d'une instance Random appropriée. De nombreuses implémentations de mélange de Fisher-Yates sont disponibles sur Stack Overflow.

48
Jon Skeet

La réponse de Jon est utile, mais en réalité vous pouvez demandez à la base de données de faire la commande en utilisant Guid et Linq to Entities (au moins, vous pouvez en EF4):

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

Cela génère du SQL ressemblant à:

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

Lors de mes tests, en utilisant Take(10) sur la requête résultante (convertie en TOP 10 en SQL), la requête a été exécutée de façon cohérente entre 0,42 et 0,46 s sur une table comportant 1 794 785 lignes. Aucune idée si SQL Server effectue une optimisation à ce sujet ou si elle a généré un GUID pour every row dans cette table. Quoi qu'il en soit, ce serait beaucoup plus rapide que de faire entrer toutes ces lignes dans mon processus et d'essayer de les trier.

37
Drew Noakes

Le hack NewGuid pour le trier côté serveur provoque malheureusement la duplication des entités en cas de jointure (ou d'extraction rapide).

Voir cette question à propos de cette question.

Pour résoudre ce problème, vous pouvez utiliser au lieu de NewGuid une sql checksum sur une valeur unique calculée côté serveur, avec une valeur de départ aléatoire calculée une fois côté client pour la randomiser. Voir ma réponse sur une question déjà liée.

3
Frédéric

Les solutions fournies ici s'exécutent sur le client. Si vous voulez quelque chose qui s'exécute sur le serveur, voici une solution pour LINQ to SQL que vous pouvez convertir en Entity Framework.

2
Fabrice

La réponse de Toro est celle que j'utiliserais, mais plutôt comme ceci:

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

(affichage croisé à partir de Code EF en premier: Comment obtenir des lignes aléatoires )

Comparer deux options:


Ignorer (nombre aléatoire de lignes)

Méthode

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();
}
  • Prend 2 requêtes

SQL généré

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

Méthode

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

SQL généré

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

Ainsi, dans les nouveaux EF, vous pouvez à nouveau voir que NewGuid est traduit en SQL (comme confirmé par @DrewNoakes https://stackoverflow.com/a/4120132/1037948 ). Même si les deux méthodes sont "in-sql", je suppose que la version Guid est plus rapide? Si vous n'avez pas à les trier pour sauter, et que vous pouvez raisonnablement deviner le montant à sauter, alors peut-être que la méthode Ignorer serait meilleure.

0
drzaus

Voici une bonne façon de faire cela (principalement pour les gens googler).

Vous pouvez également ajouter .Take (n) à la fin pour ne récupérer qu'un numéro défini.

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

Que dis-tu de ça:


    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 a une solution très soignée, simple et générique. Il vous suffit de placer le code dans une classe statique séparée pour le faire fonctionner.

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

Ensuite, pour utiliser le code, il suffit de faire:

var randomizeQuery = Query.Randomize();

Si simple! Merci lolo_house.

0
Mythlandia

Théoriquement (je ne l'ai pas encore essayé), voici ce qui devrait marcher: 

Ajoutez une classe partielle à votre classe de contexte:

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

la mise en oeuvre : 

from t in context.MyTable
orderby  context.Random()
select t; 
0
Segev -CJ- Shmueli