110 votes

Ligne aléatoire de Linq à Sql

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 ?

167voto

Marc Gravell Points 482669

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

59voto

Konstantin Tarkus Points 16862

Un autre échantillon :

var customers = db.Customers
                  .Where(c => c.IsActive)
                  .OrderBy(c => Guid.NewGuid())
                  .FirstOrDefault();

31voto

Jon Skeet Points 692016

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

19voto

Ian Mercer Points 19271

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:-

  1. Efficace : SQL s'occupe de la l'ordre, il n'est pas nécessaire d'aller chercher toute la table
  2. 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.

7voto

Artur Keyan Points 2527

Si vous voulez obtenir par exemple var count = 16 des lignes aléatoires du tableau, vous pouvez écrire

var rows = Table.OrderBy(t => Guid.NewGuid())
                        .Take(count);

ici j'ai utilisé E.F, et la Table est un Dbset

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X