Quelle est la meilleure (et la plus rapide) façon 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?
Quelle est la meilleure (et la plus rapide) façon 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:
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 sur le serveur SQL avec la permission 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 que pour les 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 de 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
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. Le OrderBy
est simplement abandonné.
EDIT: Je viens de remarquer que c'est LINQ to SQL, pas LINQ to Objects. Utilisez le code de Marc pour obtenir la base de données pour faire cela 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 besoin d'obtenir le décompte. Cependant, vous devez récupérer chaque élément à moins que vous n'obteniez le nombre.
Ce que vous pouvez faire, c'est conserver l'idée d'une valeur «actuelle» et du décompte 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 en faites toujours la valeur "courante". Lorsque vous lisez la deuxième valeur, vous pouvez en faire la valeur actuelle (probabilité 1/2). Lorsque vous lisez la troisième valeur, vous pouvez faire la valeur actuelle (probabilité 1/3), etc. Lorsque vous êtes à court de données, la valeur actuelle est aléatoire parmi toutes celles que vous lisez, avec une probabilité uniforme.
Pour appliquer cela avec une condition, ignorez simplement tout ce qui ne remplit pas la condition. La manière 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 que ç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;
}
current
sera toujours défini sur le premier élément. Lors de la deuxième itération, il y a un changement de 50% selon lequel il sera défini sur le deuxième élément. À la troisième itération, il y a 33% de chances qu'il soit défini sur le troisième élément. L'ajout d'une instruction break signifierait que vous quitteriez toujours après avoir lu le premier élément, ce qui ne le rendrait pas du tout aléatoire.
Un moyen efficace consiste à ajouter une colonne à vos données Shuffle
qui est remplie avec un int aléatoire (lorsque 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 effectue une opération XOR dans la base de données et trie les résultats de ce XOR.
Avantages: -
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, donnant un ordre cohérent au cours de la journée (permettant des capacités de pause / reprise faciles), mais un regard neuf sur chaque playlist chaque nouveau jour.
result = result.OrderBy(s => s.Shuffle ^ seed);
(c'est-à-dire pas besoin d'implémenter le XOR via les opérateurs ~, & et |).
si vous voulez obtenir par exemple var count = 16
des lignes aléatoires de la table, vous pouvez écrire
var rows = Table.OrderBy(t => Guid.NewGuid())
.Take(count);
ici j'ai utilisé EF, et la table est un Dbset
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 Microsoft Research 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 actuel.
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.
Je suis venu ici en me demandant comment obtenir quelques pages aléatoires à partir d'un petit nombre d'entre elles, afin que chaque utilisateur obtienne 3 pages aléatoires différentes.
Ceci est ma solution finale, travaillant avec des requêtes avec LINQ sur une liste de pages dans Sharepoint 2010. C'est dans 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
J'ai une requête de fonction aléatoire contre DataTable
s:
var result = (from result in dt.AsEnumerable()
order by Guid.NewGuid()
select result).Take(3);
L'exemple ci-dessous appellera la source pour récupérer un dé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 numéro 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);
}
}
J'utilise cette méthode pour prendre des nouvelles aléatoires et ça fonctionne 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;
}
var cust = (from c in ctx.CUSTOMERs.ToList() select c).OrderBy(x => x.Guid.NewGuid()).Taket(2);
Sélectionnez 2 lignes aléatoires
A ajouter à la solution de Marc Gravell. Si vous ne travaillez pas avec la classe datacontext elle-même (parce que vous la procurez par proxy, 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 car vous ne l'utilisez pas dans un sous-classe ou classe partielle de votre classe de contexte de données réel.
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 contexte de données FAKE (qui utilise en mémoire des entités):
public IOrderedQueryable<T> Randomize<T>(IQueryable<T> query)
{
return query.OrderBy(x => Guid.NewGuid());
}