Quelle est la meilleure façon (et la plus rapide) de récupérer une ligne aléatoire en utilisant Linq to SQL lorsque j'ai une condition, par exemple un champ doit être vrai ?
Réponses
Trop de publicités?Vous pouvez le faire au niveau de la base de données, en utilisant un faux UDF ; dans une classe partielle, ajoutez une méthode au contexte de données :
partial class MyDataContext {
[Function(Name="NEWID", IsComposable=true)]
public Guid Random()
{ // to prove not used by our C# code...
throw new NotImplementedException();
}
}
Ensuite, juste order by ctx.Random()
ce qui permettra d'effectuer un classement aléatoire au niveau du serveur SQL avec l'aimable autorisation de NEWID()
. c'est-à-dire
var cust = (from row in ctx.Customers
where row.IsActive // your filter
orderby ctx.Random()
select row).FirstOrDefault();
Notez que cette méthode ne convient qu'aux tables de taille petite à moyenne ; pour les grandes tables, elle aura un impact sur les performances du serveur, et il sera plus efficace de trouver le nombre de lignes ( Count
), puis en choisir un au hasard ( Skip/First
).
pour l'approche par comptage :
var qry = from row in ctx.Customers
where row.IsActive
select row;
int count = qry.Count(); // 1st round-trip
int index = new Random().Next(count);
Customer cust = qry.Skip(index).FirstOrDefault(); // 2nd round-trip
EDIT : Je viens juste de remarquer que c'est LINQ to SQL, pas LINQ to Objects. Utilisez le code de Marc pour que la base de données fasse cela pour vous. J'ai laissé cette réponse ici comme un point d'intérêt potentiel pour LINQ to Objects.
Bizarrement, vous n'avez pas besoin d'obtenir le compte. En revanche, vous devez récupérer chaque élément, sauf si vous obtenez le nombre.
Ce que vous pouvez faire, c'est conserver l'idée d'une valeur "courante" et du compte courant. Lorsque vous récupérez la valeur suivante, prenez un nombre aléatoire et remplacez la valeur "actuelle" par "nouvelle" avec une probabilité de 1/n où n est le compte.
Donc quand vous lisez la première valeur, vous toujours ce qui en fait la valeur "actuelle". Lorsque vous lisez la deuxième valeur, vous pourrait en faire la valeur actuelle (probabilité 1/2). Lorsque vous lisez la troisième valeur, vous pourrait en faire la valeur actuelle (probabilité 1/3), etc. Lorsque vous n'avez plus de données, la valeur actuelle est une valeur aléatoire parmi toutes celles que vous avez lues, avec une probabilité uniforme.
Pour appliquer cela avec une condition, il suffit d'ignorer tout ce qui ne remplit pas la condition. La façon la plus simple de le faire est de ne considérer que la séquence "correspondante" pour commencer, en appliquant d'abord une clause Where.
Voici une mise en œuvre rapide. I piense en C'est bon...
public static T RandomElement<T>(this IEnumerable<T> source,
Random rng)
{
T current = default(T);
int count = 0;
foreach (T element in source)
{
count++;
if (rng.Next(count) == 0)
{
current = element;
}
}
if (count == 0)
{
throw new InvalidOperationException("Sequence was empty");
}
return current;
}
Une façon d'y parvenir efficacement est d'ajouter une colonne à vos données Shuffle
qui est rempli d'un nombre entier aléatoire (à mesure que chaque enregistrement est créé).
La requête partielle pour accéder à la table dans un ordre aléatoire est ...
Random random = new Random();
int seed = random.Next();
result = result.OrderBy(s => (~(s.Shuffle & seed)) & (s.Shuffle | seed)); // ^ seed);
Cette opération effectue une opération XOR dans la base de données et commande par les résultats de cette opération XOR.
Avantages:-
- Efficace : SQL s'occupe de la l'ordre, il n'est pas nécessaire d'aller chercher toute la table
- Répétable : (bon pour les tests) - on peut utiliser la même graine aléatoire pour générer le même ordre ordre aléatoire
C'est l'approche utilisée par mon système domotique pour randomiser les listes de lecture. Il choisit une nouvelle graine chaque jour, ce qui permet d'avoir un ordre cohérent au cours de la journée (ce qui permet de faire des pauses et de reprendre facilement), mais un regard neuf sur chaque liste de lecture chaque jour.