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();
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()
.
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.
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.
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.
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.
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;
}
(affichage croisé à partir de Code EF en premier: Comment obtenir des lignes aléatoires )
Comparer deux options:
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
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.
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");
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;
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.
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;