Linq a entidades, orden aleatoria

¿Cómo devuelvo las entidades coincidentes en un orden aleatorio?
Para que quede claro, esto es material de Entity Framework y LINQ to Entities.

(código de air)

IEnumerable results = from en in context.MyEntity where en.type == myTypeVar orderby ????? select en; 

Gracias

Editar:
Traté de agregar esto al contexto:

 public Guid Random() { return new Guid(); } 

Y usando esta consulta:

 IEnumerable results = from en in context.MyEntity where en.type == myTypeVar orderby context.Random() select en; 

Pero tengo este error:

 System.NotSupportedException: LINQ to Entities does not recognize the method 'System.Guid Random()' method, and this method cannot be translated into a store expression.. 

Editar (código actual):

 IEnumerable results = (from en in context.MyEntity where en.type == myTypeVar orderby context.Random() select en).AsEnumerable(); 

La solución simple sería crear una matriz (o una List ) y aleatorizar sus índices.

EDITAR:

 static IEnumerable Randomize(this IEnumerable source) { var array = source.ToArray(); // randomize indexes (several approaches are possible) return array; } 

EDITAR: Personalmente, encuentro que la respuesta de Jon Skeet es más elegante:

 var results = from ... in ... where ... orderby Guid.NewGuid() select ... 

Y claro, puede tomar un generador de números aleatorios en lugar de Guid.NewGuid() .

Una forma simple de hacer esto es ordenar por Guid.NewGuid() pero luego el pedido ocurre en el lado del cliente. Puede convencer a EF para que haga algo al azar en el lado del servidor, pero eso no es necesariamente simple, y hacerlo con “ordenar por número aleatorio” aparentemente no funciona .

Para hacer que el pedido ocurra en el lado de .NET en lugar de en EF, necesita AsEnumerable :

 IEnumerable results = context.MyEntity .Where(en => en.type == myTypeVar) .AsEnumerable() .OrderBy(en => context.Random()); 

Sería mejor obtener la versión desordenada en una lista y luego barajar eso.

 Random rnd = ...; // Assume a suitable Random instance List results = context.MyEntity .Where(en => en.type == myTypeVar) .ToList(); results.Shuffle(rnd); // Assuming an extension method on List 

El barajado es más eficiente que ordenar, aparte de cualquier otra cosa. Sin embargo, consulte mi artículo sobre la aleatoriedad para obtener detalles sobre la adquisición de una instancia Random apropiada. Hay muchas implementaciones de mezcla de Fisher-Yates disponibles en Stack Overflow.

La respuesta de Jon es útil, pero en realidad puede hacer que el DB haga el pedido usando Guid y Linq a Entidades (al menos, puede hacerlo en EF4):

 from e in MyEntities orderby Guid.NewGuid() select e 

Esto genera SQL que se asemeja a:

 SELECT [Project1].[Id] AS [Id], [Project1].[Column1] AS [Column1] FROM ( SELECT NEWID() AS [C1], -- Guid created here [Extent1].[Id] AS [Id], [Extent1].[Column1] AS [Column1], FROM [dbo].[MyEntities] AS [Extent1] ) AS [Project1] ORDER BY [Project1].[C1] ASC -- Used for sorting here 

En mi prueba, al utilizar Take(10) en la consulta resultante (se convierte en TOP 10 en SQL), la consulta se ejecutó consistentemente entre 0,42 y 0,46 segundos frente a una tabla con 1,794,785 filas. No se sabe si SQL Server hace algún tipo de optimización en esto o si generó un GUID para cada fila en esa tabla. De cualquier manera, eso sería considerablemente más rápido que poner todas esas filas en mi proceso y tratar de ordenarlas allí.

Las soluciones proporcionadas aquí se ejecutan en el cliente. Si desea algo que se ejecute en el servidor, aquí hay una solución para LINQ to SQL que puede convertir a Entity Framework.

El hack de NewGuid para clasificar su lado del servidor desafortunadamente hace que las entidades se dupliquen en el caso de las uniones (o que la búsqueda con ganas incluya).

Vea esta pregunta sobre este tema.

Para solucionar este problema, puede utilizar en lugar de NewGuid una checksum de checksum sql en algún valor único del lado del servidor calculado, con una semilla aleatoria calculada una vez en el lado del cliente para aleatorizarla. Ver mi respuesta en una pregunta previamente vinculada.

Qué tal esto:

var randomizer = new Random(); var results = from en in context.MyEntity where en.type == myTypeVar let rand = randomizer.Next() orderby rand select en;
var randomizer = new Random(); var results = from en in context.MyEntity where en.type == myTypeVar let rand = randomizer.Next() orderby rand select en; 

La respuesta de Toro es la que usaría, pero más o menos así:

 static IEnumerable Randomize(this IEnumerable source) { var list = source.ToList(); var newList = new List(); while (source.Count > 0) { //choose random one and MOVE it from list to newList } return newList; } 

Aquí hay una buena forma de hacerlo (principalmente para personas que usan Google).

También puede agregar .Take (n) en el extremo para recuperar solo un número establecido.

 model.CreateQuery( @"select value source.entity from (select entity, SqlServer.NewID() as rand from Products as entity where entity.type == myTypeVar) as source order by source.rand"); 

Teóricamente hablando (aún no lo he probado), lo siguiente debería ser el truco:

Agregue una clase parcial a su clase de contexto:

 public partial class MyDataContext{ [Function(Name = "NEWID", IsComposable = true)] public Guid Random() { // you can put anything you want here, it makes no difference throw new NotImplementedException(); } } 

implementación:

 from t in context.MyTable orderby context.Random() select t; 

(publicación cruzada desde el código EF primero: cómo obtener filas al azar )

Comparando dos opciones:


Omitir (número aleatorio de filas)

Método

 private T getRandomEntity(IGenericRepository repo) where T : EntityWithPk { var skip = (int)(rand.NextDouble() * repo.Items.Count()); return repo.Items.OrderBy(o => o.ID).Skip(skip).Take(1).First(); } 
  • Toma 2 consultas

SQL generado

 SELECT [GroupBy1].[A1] AS [C1] FROM (SELECT COUNT(1) AS [A1] FROM [dbo].[People] AS [Extent1]) AS [GroupBy1]; SELECT TOP (1) [Extent1].[ID] AS [ID], [Extent1].[Name] AS [Name], [Extent1].[Age] AS [Age], [Extent1].[FavoriteColor] AS [FavoriteColor] FROM (SELECT [Extent1].[ID] AS [ID], [Extent1].[Name] AS [Name], [Extent1].[Age] AS [Age], [Extent1].[FavoriteColor] AS [FavoriteColor], row_number() OVER (ORDER BY [Extent1].[ID] ASC) AS [row_number] FROM [dbo].[People] AS [Extent1]) AS [Extent1] WHERE [Extent1].[row_number] > 15 ORDER BY [Extent1].[ID] ASC; 

Guid

Método

 private T getRandomEntityInPlace(IGenericRepository repo) { return repo.Items.OrderBy(o => Guid.NewGuid()).First(); } 

SQL generado

 SELECT TOP (1) [Project1].[ID] AS [ID], [Project1].[Name] AS [Name], [Project1].[Age] AS [Age], [Project1].[FavoriteColor] AS [FavoriteColor] FROM (SELECT NEWID() AS [C1], [Extent1].[ID] AS [ID], [Extent1].[Name] AS [Name], [Extent1].[Age] AS [Age], [Extent1].[FavoriteColor] AS [FavoriteColor] FROM [dbo].[People] AS [Extent1]) AS [Project1] ORDER BY [Project1].[C1] ASC 

Por lo tanto, en EF más nuevo, puede ver nuevamente que NewGuid se traduce a SQL (como confirmado por @DrewNoakes https://stackoverflow.com/a/4120132/1037948 ). Aunque ambos son métodos “en sql”, supongo que la versión Guid es más rápida. Si no tienes que ordenarlos para omitirlos, y puedes adivinar razonablemente la cantidad que se debe omitir, entonces quizás el método Skip sea mejor.

lolo_house tiene una solución realmente simple, simple y genérica. Solo necesita poner el código en una clase estática separada para que funcione.

 using System; using System.Collections.Generic; using System.Linq; namespace SpanishDrills.Utilities { public static class LinqHelper { public static IEnumerable Randomize(this IEnumerable pCol) { List lResultado = new List(); List lLista = pCol.ToList(); Random lRandom = new Random(); int lintPos = 0; while (lLista.Count > 0) { lintPos = lRandom.Next(lLista.Count); lResultado.Add(lLista[lintPos]); lLista.RemoveAt(lintPos); } return lResultado; } } } 

Entonces para usar el código solo hazlo:

 var randomizeQuery = Query.Randomize(); 

¡Tan sencillo! Gracias lolo_house.

Creo que es mejor no agregar propiedades a la clase. Mejor usar la posición:

 public static IEnumerable Randomize(this IEnumerable pCol) { List lResultado = new List(); List lLista = pCol.ToList(); Random lRandom = new Random(); int lintPos = 0; while (lLista.Count > 0) { lintPos = lRandom.Next(lLista.Count); lResultado.Add(lLista[lintPos]); lLista.RemoveAt(lintPos); } return lResultado; } 

Y la llamada será (como lista () o para Matriz ()):

var result = IEnumerable.Where (..). Aleatorizar ();