web-dev-qa-db-fra.com

Ligne aléatoire de Linq à Sql

Quel est le meilleur (et le plus rapide) moyen de récupérer une ligne aléatoire en utilisant Linq to SQL lorsque j'ai une condition, par ex. un domaine doit être vrai?

110
Julien Poulin

Vous pouvez le faire dans 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(); 
     }
}

Alors juste order by ctx.Random(); cela fera un ordre aléatoire à la courtoisie de SQL-Server 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 cela ne convient qu'aux tables de petite à moyenne taille; pour les tables volumineuses, cela aura un impact sur les performances du serveur et il sera plus efficace de trouver le nombre de lignes (Count), puis d'en choisir une 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
167
Marc Gravell

Un autre exemple pour Entity Framework:

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

Cela ne fonctionne pas avec LINQ to SQL. OrderBy est simplement en cours de suppression.

58
Konstantin Tarkus

EDIT: Je viens de remarquer que c'est LINQ to SQL, pas LINQ to Objects. Utilisez le code de Marc pour que la base de données le fasse pour vous. J'ai laissé cette réponse ici comme un point d'intérêt potentiel pour LINQ to Objects.

Curieusement, vous n'avez pas vraiment besoin de compter. Cependant, vous devez récupérer chaque élément sauf si vous obtenez le nombre.

Ce que vous pouvez faire, c'est garder l'idée d'une valeur "actuelle" et le nombre actuel. Lorsque vous récupérez la valeur suivante, prenez un nombre aléatoire et remplacez le "courant" par "nouveau" avec une probabilité de 1/n où n est le nombre.

Ainsi, lorsque vous lisez la première valeur, vous toujours faites-en la valeur "actuelle". Lorsque vous lisez la deuxième valeur, vous pourrait faire que la valeur actuelle (probabilité 1/2). Lorsque vous lisez la troisième valeur, vous pourrait faire en sorte que la valeur actuelle (probabilité 1/3) etc. Lorsque vous n'avez plus de données, la valeur actuelle est aléatoire parmi toutes celles vous lisez, avec une probabilité uniforme.

Pour appliquer cela avec une condition, ignorez 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. Je pense ça va ...

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;
}
31
Jon Skeet

Une façon de réaliser efficacement est d'ajouter une colonne à vos données Shuffle qui est remplie avec un int 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);

Cela fait une opération XOR dans la base de données et trie par les résultats de ce XOR.

Avantages: -

  1. Efficace: SQL gère la commande, pas besoin de récupérer toute la table
  2. Répétable: (bon pour les tests) - peut utiliser la même graine aléatoire pour générer le même ordre aléatoire

C'est l'approche utilisée par mon système domotique pour randomiser les listes de lecture. Il sélectionne une nouvelle graine chaque jour en donnant un ordre cohérent pendant la journée (permettant des capacités de pause/reprise faciles) mais un nouveau regard sur chaque playlist chaque nouveau jour.

19
Ian Mercer

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

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

ici j'ai utilisé E.F, et le tableau est un Dbset

7
Artur Keyan

Si le but d'obtenir des lignes aléatoires est l'échantillonnage, j'ai parlé très brièvement ici d'une belle approche de Larson et al., Équipe de recherche Microsoft où ils ont développé un cadre d'échantillonnage pour Sql Server en utilisant des vues matérialisées. Il existe également un lien vers le document.

1
naiemk
List<string> lst = new List<string>();
lst.Add("Apple"); 
lst.Add("Guva");
lst.Add("Graps"); 
lst.Add("PineApple");
lst.Add("Orange"); 
lst.Add("Mango");

var customers = lst.OrderBy(c => Guid.NewGuid()).FirstOrDefault();

Explication: En insérant le guid (qui est aléatoire), l'ordre avec orderby serait aléatoire.

1
Nayeem Mansoori

j'utilise cette méthode pour prendre des nouvelles aléatoires et son travail très bien;)

    public string LoadRandomNews(int maxNews)
    {
        string temp = "";

        using (var db = new DataClassesDataContext())
        {
            var newsCount = (from p in db.Tbl_DynamicContents
                             where p.TimeFoPublish.Value.Date <= DateTime.Now
                             select p).Count();
            int i;
            if (newsCount < maxNews)
                i = newsCount;
            else i = maxNews;
            var r = new Random();
            var lastNumber = new List<int>();
            for (; i > 0; i--)
            {
                int currentNumber = r.Next(0, newsCount);
                if (!lastNumber.Contains(currentNumber))
                { lastNumber.Add(currentNumber); }
                else
                {
                    while (true)
                    {
                        currentNumber = r.Next(0, newsCount);
                        if (!lastNumber.Contains(currentNumber))
                        {
                            lastNumber.Add(currentNumber);
                            break;
                        }
                    }
                }
                if (currentNumber == newsCount)
                    currentNumber--;
                var news = (from p in db.Tbl_DynamicContents
                            orderby p.ID descending
                            where p.TimeFoPublish.Value.Date <= DateTime.Now
                            select p).Skip(currentNumber).Take(1).Single();
                temp +=
                    string.Format("<div class=\"divRandomNews\"><img src=\"files/1364193007_news.png\" class=\"randomNewsImg\" />" +
                                  "<a class=\"randomNews\" href=\"News.aspx?id={0}\" target=\"_blank\">{1}</a></div>",
                                  news.ID, news.Title);
            }
        }
        return temp;
    }
0
sadati

Si vous utilisez LINQPad, passez en mode programme C # et procédez comme suit:

void Main()
{
    YourTable.OrderBy(v => Random()).FirstOrDefault.Dump();
}

[Function(Name = "NEWID", IsComposable = true)]
public Guid Random()
{
    throw new NotImplementedException();
}
0
alexey
var cust = (from c in ctx.CUSTOMERs.ToList() select c).OrderBy(x => x.Guid.NewGuid()).Taket(2);

Sélectionnez au hasard 2 rangées

0
Bahadır ATASOY

A ajouter à la solution de Marc Gravell. Si vous ne travaillez pas avec la classe de datacontext elle-même (parce que vous la procurez en quelque sorte, par exemple pour simuler le datacontext à des fins de test), vous ne pouvez pas utiliser directement l'UDF défini: il ne sera pas compilé en SQL parce que vous ne l'utilisez pas dans un sous-classe ou classe partielle de votre classe de contexte de données réelles.

Une solution de contournement à ce problème consiste à créer une fonction Randomize dans votre proxy, en l'alimentant avec la requête que vous souhaitez randomiser:

public class DataContextProxy : IDataContext
{
    private readonly DataContext _context;

    public DataContextProxy(DataContext context)
    {
        _context = context;
    }

    // Snipped irrelevant code

    public IOrderedQueryable<T> Randomize<T>(IQueryable<T> query)
    {
        return query.OrderBy(x => _context.Random());
    }
}

Voici comment vous l'utiliseriez dans votre code:

var query = _dc.Repository<SomeEntity>();
query = _dc.Randomize(query);

Pour être complet, voici comment implémenter cela dans le datacontext FAKE (qui utilise des entités en mémoire):

public IOrderedQueryable<T> Randomize<T>(IQueryable<T> query)
{
    return query.OrderBy(x => Guid.NewGuid());
}
0
Dave de Jong

Je suis venu ici en me demandant comment obtenir quelques pages aléatoires à partir d'un petit nombre d'entre elles, de sorte que chaque utilisateur obtienne 3 pages aléatoires différentes.

Ceci est ma solution finale, en travaillant avec LINQ sur une liste de pages dans Sharepoint 2010. C'est en Visual Basic, désolé: p

Dim Aleatorio As New Random()

Dim Paginas = From a As SPListItem In Sitio.RootWeb.Lists("Páginas") Order By Aleatorio.Next Take 3

Devrait probablement obtenir un profilage avant d'interroger un grand nombre de résultats, mais c'est parfait pour mon objectif

0
Fran

Utilisation de LINQ to SQL dans LINQPad comme les instructions C # ressemblent

IEnumerable<Customer> customers = this.ExecuteQuery<Customer>(@"SELECT top 10 * from [Customers] order by newid()");
customers.Dump();

Le SQL généré est

SELECT top 10 * from [Customers] order by newid()
0
JCO

J'ai une requête de fonction aléatoire contre DataTables:

var result = (from result in dt.AsEnumerable()
              order by Guid.NewGuid()
              select result).Take(3); 
0
midhun sankar

L'exemple ci-dessous appellera la source pour récupérer un compte, puis appliquera une expression de saut sur la source avec un nombre compris entre 0 et n. La deuxième méthode appliquera l'ordre en utilisant l'objet aléatoire (qui ordonnera tout en mémoire) et sélectionnera le nombre passé dans l'appel de méthode.

public static class IEnumerable
{
    static Random rng = new Random((int)DateTime.Now.Ticks);

    public static T RandomElement<T>(this IEnumerable<T> source)
    {
        T current = default(T);
        int c = source.Count();
        int r = rng.Next(c);
        current = source.Skip(r).First();
        return current;
    }

    public static IEnumerable<T> RandomElements<T>(this IEnumerable<T> source, int number)
    {
        return source.OrderBy(r => rng.Next()).Take(number);
    }
}
0
user1619860