Me gustan las migas con tropezones

Al igual que cuando como migas mi cuerpo me sabe a fiesta, cuando veo una patada a un acceso a datos mi cuerpo se descompone e intento luchar por dejar claro que debes hacer y que es lo que nunca deberías de hacer.

Y ayer en twitter se produjo una conversación que pertenece a la segunda opción “nunca que accedas a datos con EF tienes que utilizar esto”.

https://twitter.com/panicoenlaxbox/status/402507006063300608

Este Viernes tuve la suerte de estar en una charla de Enrique Catalá en #gusenet.

 Debate: de ADO.NET a EF6 con SQL Server y compartir momentos con gente preocupada por el acceso a datos.

Eladio Rincón

Sergio Navarro

Javi

Raúl Serrano

Fran

Y mis dos inseparables Pin y Pon, junto con algunos asistentes más, que no les sigo la pista por twitter.

No muchos la verdad:( para lo importante que puede llegar a ser esto en nuestra profesión.

De todo lo que pudimos hablar que no fue poco y que nos duro un par de días más, que pesaos pero que envidia Paco Monfort, me quedo con esta frase de Eladio

problema no son ORMs. problema es "desprecio" a capa d persistencia.

Sí Eladio, tienes toda la razón, pero tampoco nos lo pone fácil el framework, más bien nos pone tropezones que en las migas están muy buenos pero que no son más que eso, zancadillas y facilidad para cometer errores y no haber pensado las cosas bien desde el principio.

Cuando ayer Sergio León escribió esto

https://twitter.com/panicoenlaxbox/status/402508906066214912

Me salto el chispazo,  algo estaba mal. Y como siempre acostumbro cuando eso pasa a pulsar F12 en el método en cuestión y esperar que me dice el bueno de Visual Studio.

Algo está mal

Es fácil ver que unas son peligrosas y las otras no. Las dos primeras son dos autenticas bombas ejecutadas en EF y lo triste es que se pueden ejecutar:(.

Vamos con un ejemplo sencillo que puedes ejecutar en un proyecto mvc copiando simplemente el código.

   1: using System;

   2: using System.Collections.Generic;

   3: using System.Web;

   4: using System.Linq;

   5: using System.Web.Mvc;

   6: using System.Data.Entity;

   7: namespace MvcApplication12.Controllers

   8: {

   9:     public class HomeController : Controller

  10:     {

  11:         public ActionResult Index()

  12:         {

  13:             using (MyContext ct = new MyContext())

  14:             {

  15:                 ct.MyClass.Add(new MyClass() { Name = "Una locura utilizarlo" });

  16:                 ct.MyClass.Add(new MyClass() { Name = "No utilizarlo en EF es estar cuerdo" });

  17:                 ct.SaveChanges();

  18:             }

  19:  

  20:             using (MyContext ct = new MyContext())

  21:             {

  22:  

  23:                 var result = ct.MyClass.Where(Filter);

  24:                 foreach (var item in result)

  25:                 {

  26:                     Console.Write(item);

  27:                 }

  28:             }

  29:  

  30:             

  31:  

  32:             ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application.";

  33:  

  34:             return View();

  35:         }

  36:  

  37:         public static bool Filter(MyClass myclass)

  38:         {

  39:             return myclass.Name.Contains("locura");

  40:         }

  41:  

  42:           

  43:         public ActionResult About()

  44:         {

  45:             ViewBag.Message = "Your app description page.";

  46:  

  47:             return View();

  48:         }

  49:  

  50:         public ActionResult Contact()

  51:         {

  52:             ViewBag.Message = "Your contact page.";

  53:  

  54:             return View();

  55:         }

  56:     }

  57:  

  58:     public class MyContext : DbContext

  59:     {

  60:         public MyContext() : base("DefaultConnection") { }

  61:         public DbSet<MyClass> MyClass { get; set; }

  62:     }

  63:  

  64:     public class MyClass

  65:     {

  66:         public int Id { get; set; }

  67:         public string Name { get; set; }

  68:     }

  69:     

  70: }

 

Quiero que nos centremos en este fragmento de código.

   1: using (MyContext ct = new MyContext())

   2:             {

   3:                 ct.MyClass.Add(new MyClass() { Name = "Una locura utilizarlo" });

   4:                 ct.MyClass.Add(new MyClass() { Name = "No utilizarlo en EF es estar cuerdo" });

   5:                 ct.SaveChanges();

   6:             }

   7:  

   8:             using (MyContext ct = new MyContext())

   9:             {

  10:  

  11:                 var result = ct.MyClass.Where(Filter);

  12:                 foreach (var item in result)

  13:                 {

  14:                     Console.Write(item);

  15:                 }

  16:             }

Estamos insertando dos registros en nuestra base de datos y creyendo que estamos filtrando de esta aquellos registros cuyo campo “Name” contiene “locura”.

¿Tu crees?, pues la respuesta es sencilla, No. Estás ejecutando contra tu motor de base de datos esta sentencia sql.

“select id,name from MyClass”

Y pensando que lo que querías ejecutar es esto otro.

select id,name from MyClass where name like ‘%’ + @name + ‘%’

Y esto, ¿que es? pues si tengo dos registros como es el caso nada pero si tengo cientos,miles o millones lo que estás haciendo es traerte toda la tabla al cliente y después filtrar en el cliente con LINQ TO OBJECT, vamos que a la postre es como si hubiésemos escrito esto.

   1: using (MyContext ct = new MyContext())

   2: {

   3:     foreach (var item in ct.MyClass)

   4:     {

   5:         if (item.Name.Contains("Locura"))

   6:             Console.WriteLine(item);

   7:     }                

   8: }

Ahora amigo Sergio si lo ves claro y tu también Quique🙂 ,es que no se puedes trabajar tanto y hay que escribir los repositorios por las mañanas, cuando uno está fresco y no fiarse siempre de lo que dice Resharper:).

Cuando me quejaba del Framework no es que a mí me guste patalear sino que cuando algo está mal me gusta decirlo claro y conciso, vamos que soy poco diplomático “está mal” y en este caso para mi es un error definir los métodos extensores de IEnumerable<T> Linq to Object en el mismo namespace que los extensores de IQueryable<T>.

¿Por qué no se creo un namespace llamado System.Linq.Query por ejemplo?

De esta forma Resharper no se equivoca puesto que en vez de utilizar using System.Linq usas System.Linq.Query y esos métodos no aparecen:).

Consejos.

1. Si no sabes de bb.dd y estás definiendo consultas linq con EF, tienes un serio problema. El mayor cuello de botella de cualquier aplicación es la bb.dd, así que aprende. Por mucho que te hayan contado que eso ya no se lleva.

2.Utiliza siempre que trabajes con EF “query syntax” en vez de “method sintax”, por muy friki que te parezca. Se asemeja más a la realidad de una bb.dd aunque el resultado puede ser el mismo en algunos casos, pero no en este.

En este ejemplo en ejecución hubiese saltado una exception del tipo System.NotSupportedException si se hubiese escrito con query syntax.

Method Syntax.

var result = ct.MyClass.Where(Filter) // Funciona pero con resultados desagradables.

Query Syntax.

var result = from item in ct.MyClass

                   where Filter(item)

                   select item. //Compila pero en ejecución lanza una exception del tipo System.NotSupportedException.

3. Ten presente que cualquier cosa que en Entity Framework no devuelva un IQueryable<T> huele mal ,excepto FirstOrDefault, First,Count,Sum etc,etc.

Referencias.

Enumerable.Where Method 

No lo utilices nunca en Entity Framework.

Queryable.Where Method

Utilizalo suiempre con Entity Framework.

Linq to Object

LINQ to Entities

Sintaxis de consultas y sintaxis de métodos en LINQ (C#)

Query syntax vs. Method syntax

En el proximo #yonodetuxaml de #gusenet (4,5,6 de Abril de 2014)vamos a regalar 30 Litros de cerveza que le acabo de ganar a Pon y Migas con tropezones Sonrisa. Apuntalo Pin que paga Pooooon

PD. Sergio últimamente me haces trabajar mucho…..

4 comentarios en “Me gustan las migas con tropezones”

  1. Gran post!

    El problema real (al margen del tema del namespace en el que tienes mucha razón) es que por un lado el compilador es muy listo y por otro resharper se pasa de listo.

    Porque .Where(IsLondon) el compilador lo traduce al Where que acepta un Func y NO un Expression>. Porque el nombre de un método es convertible automaticamente a un delegado de la misma firma, pero NO a una expresión. Una expresión SIEMPRE parte de una expresión lambda.

    Súmale esto que IQueryable deriva de IEnumerable y el compilador lo tiene en cuenta a la hora de buscar métodos de extensión y ya tienes el lío montado.
    Y aquí es cuando R# “se pasa de listo” haciendonos una sugerencia que hace que el compilador NO pueda usar el método de IQueryable (porque no hay lambda que convertir a Expression) y salte al de IEnumerable.

    Por cierto… no es necesario usar query syntax:
    customers.Where(c=>c.IsLodon()) es suficiente para forzar el método extensor de IQueryable.

    Saludos!

  2. Vaya… Buena explicación en el artículo y buena aclaración de Eduard en el comentario.
    Esto es de ese tipo de cosas que pocos saben porque pocos se han preocupado de investigarlo y aprenderlo o porque pocos se preocupan del buen rendimiento de una aplicación.
    Good job master Pedro 🙂

  3. Muy bueno Pedro,

    No sé por qué me imaginaba que ibas a darle a EF 🙂 De todas formas, yo estoy parcialmente de acuerdo en lo que comentas, ya que aunque estas cosas facilitan que la gente pueda equivocarse, también da mucha potencia a quien sabe cómo funciona y usarlo bien.

    A mi realmente me gusta el poder mezclar unas partes de la consulta de BD y otras de memoria… me parece simplemente muy bueno, ya que permite plantear las consultas centrándome solo en temas de negocio y luego ya se resuelve cada parte donde pueda (teniendo en cuenta cosas como la que planteas).

    Otra cosa es que sea más fácil equivocarse y hacer las cosas mal, entonces habría que enseñar a la gente cómo hacerlo bien y entender Linq (como muy bien haces en este post).

    Resumiendo: muy buen post y con ejemplos como este iremos entendiendo mejor como aprovechar la flexibilidad y potencia que Linq nos da.

    Au

  4. Segundo intento (versión corta), el primero se lo ha comido geeks bytheface 🙁

    Primero: Un post cojonudo Pedro! Ahora veo de que iba los de los 30 litros de cerveza 🙂
    A ver, yo como no uso R# (aunque tenga licencia) no he podido experimentarlo, pero confío en vuestra palabra. De todos modos hay que ser un poco perro para no investigar lo que propone R#, no? O soy el único paranoico con las ‘optimizaciones’ de estos productos? Muchas veces la clavan y aprendes cosas que desconocías, pero a veces…
    Y para terminar ¡antes muerto que usar query syntax, tío! Basta con hacer las cosas bien (final comentario Edu) y no aceptar pulpo de buenas a primeras 😉
    Lo dicho, buen post colega! 😀

Deja un comentario

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