¿Cuál es la mejor (y más rápida) forma de recuperar una fila aleatoria usando Linq to SQL cuando tengo una condición, por ejemplo, algún campo debe ser verdadero?
¿Cuál es la mejor (y más rápida) forma de recuperar una fila aleatoria usando Linq to SQL cuando tengo una condición, por ejemplo, algún campo debe ser verdadero?
Respuestas:
Puede hacer esto en la base de datos, utilizando una UDF falsa; en una clase parcial, agregue un método al contexto de datos:
partial class MyDataContext {
[Function(Name="NEWID", IsComposable=true)]
public Guid Random()
{ // to prove not used by our C# code...
throw new NotImplementedException();
}
}
Entonces solo order by ctx.Random()
; esto hará un pedido aleatorio en el SQL-Server cortesía de NEWID()
. es decir
var cust = (from row in ctx.Customers
where row.IsActive // your filter
orderby ctx.Random()
select row).FirstOrDefault();
Tenga en cuenta que esto solo es adecuado para mesas de tamaño pequeño a mediano; para tablas grandes, tendrá un impacto en el rendimiento en el servidor y será más eficiente encontrar el número de filas ( Count
) y luego elegir una al azar ( Skip/First
).
para el enfoque de conteo:
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
Otra muestra de Entity Framework:
var customers = db.Customers
.Where(c => c.IsActive)
.OrderBy(c => Guid.NewGuid())
.FirstOrDefault();
Esto no funciona con LINQ to SQL. El OrderBy
simplemente se está eliminando.
EDITAR: Acabo de notar que esto es LINQ to SQL, no LINQ to Objects. Utilice el código de Marc para que la base de datos haga esto por usted. Dejé esta respuesta aquí como un posible punto de interés para LINQ to Objects.
Por extraño que parezca, en realidad no es necesario contarlo. Sin embargo, necesita buscar todos los elementos a menos que obtenga el recuento.
Lo que puede hacer es mantener la idea de un valor "actual" y el recuento actual. Cuando obtenga el siguiente valor, tome un número aleatorio y reemplace el "actual" con "nuevo" con una probabilidad de 1 / n donde n es el recuento.
Entonces, cuando lee el primer valor, siempre lo convierte en el valor "actual". Cuando lea el segundo valor, puede convertirlo en el valor actual (probabilidad 1/2). Cuando lea el tercer valor, es posible que convertirlo en el valor actual (probabilidad 1/3), etc. Cuando se haya quedado sin datos, el valor actual es aleatorio de todos los que leyó, con probabilidad uniforme.
Para aplicar eso con una condición, simplemente ignore todo lo que no cumpla con la condición. La forma más sencilla de hacerlo es considerar solo la secuencia de "coincidencia" para empezar, aplicando primero una cláusula Where.
Aquí hay una implementación rápida. Yo creo que está bien ...
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
será siempre estar configurado en el primer elemento. En la segunda iteración, hay un cambio del 50% que se establecerá en el segundo elemento. En la tercera iteración, hay un 33% de posibilidades de que se establezca en el tercer elemento. Agregar una declaración de interrupción significaría que siempre saldría después de leer el primer elemento, por lo que no es aleatorio en absoluto.
Una forma de lograrlo de manera eficiente es agregar una columna a sus datos Shuffle
que se completa con un int aleatorio (a medida que se crea cada registro).
La consulta parcial para acceder a la tabla en orden aleatorio es ...
Random random = new Random();
int seed = random.Next();
result = result.OrderBy(s => (~(s.Shuffle & seed)) & (s.Shuffle | seed)); // ^ seed);
Esto hace una operación XOR en la base de datos y ordena los resultados de ese XOR.
Ventajas: -
Este es el enfoque utilizado por mi sistema de automatización del hogar para aleatorizar listas de reproducción. Recoge una nueva semilla cada día dando un orden constante durante el día (lo que permite una fácil pausa / reanudación) pero una nueva mirada a cada lista de reproducción cada nuevo día.
result = result.OrderBy(s => s.Shuffle ^ seed);
(es decir, no es necesario implementar el XOR a través de los operadores ~, & y |).
si desea obtener, por ejemplo, var count = 16
filas aleatorias de la tabla, puede escribir
var rows = Table.OrderBy(t => Guid.NewGuid())
.Take(count);
aquí usé EF, y la tabla es un Dbset
Si el propósito de obtener filas aleatorias es el muestreo, he hablado muy brevemente aquí sobre un buen enfoque de Larson et al., El equipo de investigación de Microsoft, donde han desarrollado un marco de muestreo para Sql Server utilizando vistas materializadas. También hay un enlace al documento real.
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();
Explicación: Al insertar el guid (que es aleatorio), el orden con orderby sería aleatorio.
Vine aquí preguntándome cómo obtener algunas páginas aleatorias de un pequeño número de ellas, para que cada usuario obtenga 3 páginas aleatorias diferentes.
Esta es mi solución final, trabajando consultando con LINQ contra una lista de páginas en Sharepoint 2010. Está en Visual Basic, lo siento: p
Dim Aleatorio As New Random()
Dim Paginas = From a As SPListItem In Sitio.RootWeb.Lists("Páginas") Order By Aleatorio.Next Take 3
Probablemente debería obtener algunos perfiles antes de consultar una gran cantidad de resultados, pero es perfecto para mi propósito.
Tengo una consulta de función aleatoria contra DataTable
s:
var result = (from result in dt.AsEnumerable()
order by Guid.NewGuid()
select result).Take(3);
El siguiente ejemplo llamará a la fuente para recuperar un recuento y luego aplicará una expresión de omisión en la fuente con un número entre 0 y n. El segundo método aplicará orden utilizando el objeto aleatorio (que ordenará todo en la memoria) y seleccionará el número pasado a la llamada al método.
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);
}
}
utilizo este método para tomar noticias aleatorias y funciona 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);
Seleccionar 2 filas al azar
Para agregar a la solución de Marc Gravell. Si no está trabajando con la clase de contexto de datos en sí (porque lo utiliza como proxy de alguna manera, por ejemplo, para falsificar el contexto de datos con fines de prueba), no puede usar la UDF definida directamente: no se compilará en SQL porque no la está usando en un subclase o clase parcial de su clase de contexto de datos reales.
Una solución para este problema es crear una función Randomize en su proxy, alimentándola con la consulta que desea que sea aleatoria:
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());
}
}
Así es como lo usaría en su código:
var query = _dc.Repository<SomeEntity>();
query = _dc.Randomize(query);
Para ser completo, así es como implementar esto en el contexto de datos FALSO (que usa en entidades de memoria):
public IOrderedQueryable<T> Randomize<T>(IQueryable<T> query)
{
return query.OrderBy(x => Guid.NewGuid());
}