EF4. Consultas ordenadas aleatoriamente

Hace unos días @fisica3 intentaba migrar un proyecto en LINQ to SQL a Entity Framework 4. El problema surgía cuando intentaba agregar al modelo una función SQL Server del tipo Composable.

Este tipo de funciones no se pueden mapear en un modelo de entidades, para hacer uso de estas hay que construir el Query con Entity SQL y utilizar las funciones, por ejemplo:

   1: using (var context = new ObjectContext("Name=Entities"))

   2: {

   3:     ObjectQuery<EntidadResult> query = context.CreateQuery<EntidadResult>

   4:         (@"select value c.Nombre, Model.Store.GetApellidos(c.Apellido1, c.Apellidos2) from Entities.Clientes as c");

   5:  

   6:     ObjectResult<EntidadResult> result = query.Execute(MergeOption.NoTracking);

   7:  

   8:     foreach (var item in result)

   9:     {

  10:         Console.WriteLine("{0} {1}", item.Nombre, item.Apellidos);

  11:     }

  12: }

El problema se produce si la función que queremos utilizar es parte de la condición de ordenación, la idea es utilizar una función que devuelve un GUID aleatorio para que la consulta se ordene aleatoriamente.

   1: CREATE FUNCTION [dbo].[GetNewId]()

   2: RETURNS uniqueidentifier

   3: AS

   4: BEGIN

   5:   RETURN (SELECT ID FROM RandomView)

   6: END

   7:  

   8: CREATE VIEW [dbo].[RandomView]

   9: AS

  10: SELECT NEWID() As ID

Si creamos un DataContext con LINQ to SQL y mapeamos la función GetNewId en nuestro modelo, tenemos la posibilidad de realizar nuestra consulta utilizando como ordenación el valor devuelto por la función.

   1: using (var context = new MusicaDataClassesDataContext())

   2: {

   3:     var canciones = from c in context.Canciones

   4:                     orderby context.GetNewId()

   5:                     select c;

   6:  

   7:     foreach (var item in canciones)

   8:     {

   9:         Console.WriteLine("{0} {1}", item.CancionId, item.Album);

  10:     }

  11: }

Entonces, ¿cómo podemos realizar esta consulta con Entity Framework?.

Entity Framework nos permite hacer uso de la expresión LET, que se utiliza para introducir una variable en el ámbito que puede ser utilizado por las siguientes cláusulas de consulta.  Similar a las variables locales en un cuerpo de método, esto le da una manera de evitar evaluar una expresión común de varias veces por almacenarlo en una variable.  Esto puede ser muy útil incluso en consultas mucho más simples.

Podemos crearnos una consulta con la expresión LET que contengan un valor aleatorio y este sería utilizado para la ordenación (algo parecido a la función que utilizamos en LINQ to SQL).

   1: using (var context = new MusicaEntities())

   2: {

   3:     var musica = from p in context.Canciones

   4:                  let guid = Guid.NewGuid()

   5:                  orderby guid

   6:                  select p;

   7:  

   8:     foreach (var item in musica)

   9:     {

  10:         Console.WriteLine("{0} - {1}", item.CancionId, item.Album);

  11:     }

  12: }

Entity Framework transforma el query anterior, con el let guid = Guid.NewGuid(), en la siguiente sentencia T-SQL, que contiene un subquery con la generación de nuestro GUID.

   1: SELECT 

   2: [Project1].[CancionId] AS [CancionId], [Project1].[Artista] AS [Artista], 

   3: [Project1].[Album] AS [Album], [Project1].[Titulo] AS [Titulo], [Project1].[AñoAlbum] AS [AñoAlbum], 

   4: [Project1].[AñoRanking] AS [AñoRanking], [Project1].[Ubicacion] AS [Ubicacion], [Project1].[Ranking99] AS [Ranking99]

   5: FROM ( SELECT 

   6:     [Extent1].[CancionId] AS [CancionId],     [Extent1].[Artista] AS [Artista], 

   7:     [Extent1].[Album] AS [Album],     [Extent1].[Titulo] AS [Titulo], 

   8:     [Extent1].[AñoAlbum] AS [AñoAlbum],     [Extent1].[AñoRanking] AS [AñoRanking], 

   9:     [Extent1].[Ubicacion] AS [Ubicacion],     [Extent1].[Ranking99] AS [Ranking99], 

  10:     NEWID() AS [C1]

  11:     FROM [dbo].[Canciones] AS [Extent1]

  12: )  AS [Project1]

  13: ORDER BY [Project1].[C1] ASC

Como vemos en la consulta transact, la función del CLR Guid.NewGuid se ha transformado en un NEWID() de T-SQL, con lo que se realiza la ordenación por ese campo aleatorio por fila.

Aunque no podamos utilizar la función que nos devuelve un valor aleatorio del tipo GUID, Entity Framework tiene la herramienta necesaria para realizar este método sin necesidad de utilizar la función.

 

Saludos a todos…

6 comentarios sobre “EF4. Consultas ordenadas aleatoriamente”

  1. Hola, gracias por la ayuda 😀

    Efectivamente, la solución pasa por el «let» que tu mencionabas, problema resuelto, pero no deja de ser un «workaround» para una limitación que tiene el EF, limitación que no tenia el L2S.

    En mi caso sirvió, pero a ver que pasa cuando en otras circunstancias sea efectivamente necesario usar una función o algún SP que se resista a ser importado.

    Dicho esto parece que los SP importados si que pueden ser llamados «directamente», pero si se colocan como parte de una consulta LINQ tambien dan problemas en tiempo de ejecución

    Ahora una pregunta tonta…. ¿Como hago para «congelar»? el contenido de la lista devuelta en la consulta? debido a las características propias del LINQ la consulta tiende a «reejecutarse» en cada inspección, por lo que tengo la duda de si uso la lista en varios puntos de la ejecución esta consulta volverá a lanzarse.

  2. Falta añadir que si se quiere realizar estas migraciones conviene verificar que el Target de la aplicación migrada sea .NET 4.

    Ojala en una futura versión este problema se haya resuelto, de momento debemos delimitar con claridad cuando podremos o no hacer uso de funciones Y SP dentro de EF.

  3. Hola Erneso, ha sido un placer ayudarte.

    Para cada caso seguro que podemos encontrar una solución al uso de los SP o Functions, piensa que una de los principios de un ORM es la independencia de la base de datos.

    Para ‘congelar’ lo que tienes que hacer es un ToList(), en ese momento se ejecuta la consulta y te la almacena desconectada del contexto como una lista.

  4. Albero, parece que estoy condenado a hacer comentarios en tus posts sobre EF…
    La solución de LET no es un workaround de LINQ To Entities más bien es parte del patron LINQ. La funcionalidad que pedis se hubiera resuelto más elegantemente usando un Model Defined Function…

    Saludos
    Unai

  5. Hola Unai, ¿condenado? Es un placer que comentes mis posts.

    Como bien dices, la forma más elegante (e incluso más correcta) sería utilizar las Model Defined Function, pero pienso que sería aumentar la complejidad de la solución a este problema. Publicaré la solución con Model Defined Function para este caso.

    Gracias!!!!

Responder a adiazmartin Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *