Var (II).Los hermanos de Simba

Este post viene como consecuencia de los comentarios del gran @eiximenis, en este otro var en c# si, o var no. Lo que ha hecho Eduard es elevar con la segunda respuesta la entrada al destino de los elegidos:).

Referente al titulo “Los hermanos de Simba” es porque la fiera se va a comer al gatito.

La primera de las respuestas, he de reconocer que estuve pensando en escribirla en el primero de los post, pero eso de la vida que te hace perezoso y en un momento decides cortar.

Eduard nos dice lo siguiente.

Efectivamente, como bien dices en var el tipo del lvalue es siempre igual al del rvalue (eso es var precisamente). Pero, en según que ocasiones, eso no nos sirve… Un ejemplo rápido…

myListbox.Items.Add(new Foo());

foreach (var fooItem in myListbox.Items) { … }

En este caso, al usar var, fooItem es un Object, pero si yo sé que en la lista hay solo Foo, probablemente querré que fooItem sea de tipo Foo. Este es solo un ejemplo. A eso me refería yo 😉

Por que ocurre esto? Porque en el ejemplo Eduard está utilizando un ObjectCollection, claro a alguien le pareció demasiado tedioso con la aparición de generic reescribir Windows Form y demás y lo que se hizo fue dejar la cosa como estaba e implementar IEnumerable<T>, todos sabemos que para utilizar el magnifico foreach alguien debe de implementar en sus clases como mínimo IEnumerable y digo como mínimo, puesto que lo lógico es utilizar IEnumerable<T>.

Vamos con un ejemplo, partimos de la siguiente clase.

   1: public class Foo

   2: {  

   3:    

   4: }

Si yo escribo el siguiente conjunto de instrucciones

   1: var foo = new Foo();

   2: foreach (var item in foo)

   3: {

   4:  

   5: }

Recibo un error de compilación donde me dice lo siguiente.

La instrucción foreach no puede funcionar en variables de tipo ‘Namespace.Foo’ porque ‘Namespace.Foo’ no contiene ninguna definición pública para ‘GetEnumerator’.

Sí Implementamos IEnumerable en “Foo”.

   1: public class Foo:System.Collections.IEnumerable

   2: {

   3:  

   4:     public System.Collections.IEnumerator GetEnumerator()

   5:     {

   6:         throw new NotImplementedException();

   7:     }

   8: }

A partir de este momento yo puedo ejecutar foreach in “Foo”.

Pero claro, con esto no contestamos a Eduard, para ello nos debemos fijar en el valor devuelto por GetEnumerator un IEnumerator cuya definición es la siguiente.

   1: [ComVisible(true), Guid("496B0ABF-CDEE-11d3-88E8-00902754C43A")]

   2: public interface IEnumerator

   3: {

   4:     bool MoveNext();

   5:     object Current { get; }

   6:     void Reset();

   7: }

   8:  

Si nos fijamos Current es object, por eso es por lo que a “var” se le asigna un “object”, con lo cual “var” si está haciendo lo que dice hacer.

Claro porque “var” si funciona con List<T>,etc,etc, pues sencillo porque estos han subido un nivel y han implementado IEnumerable<T> y por tanto IEnumerator<T> donde la  propiedad Current devuelve T.

   1: public interface IEnumerator<T> : IDisposable, IEnumerator

   2: {

   3:     // Properties

   4:     T Current { get; }

   5: }

Pero como se que me va a decir que porque yo puede hacer lo siguiente.

   1: private void Metodo()

   2: {

   3:     var Lista = new System.Collections.ArrayList();

   4:     Lista.Add(new Foo());

   5:     foreach (Foo item in Lista)

   6:     {

   7:  

   8:     }

   9: }

Le vamos a contestar, con un simple “una cosa es lo que tus ojos ven y otra bien distinta lo que hay”.

Un poquito de IL:).

   1: .method private hidebysig instance void Metodo() cil managed

   2: {

   3:     .maxstack 2

   4:     .locals init (

   5:         [0] class [mscorlib]System.Collections.ArrayList Lista,

   6:         [1] class WindowsFormsApplication24.Foo item,

   7:         [2] class [mscorlib]System.Collections.IEnumerator CS$5$0000,

   8:         [3] bool CS$4$0001,

   9:         [4] class [mscorlib]System.IDisposable CS$0$0002)

  10:     L_0000: nop 

  11:     L_0001: newobj instance void [mscorlib]System.Collections.ArrayList::.ctor()

  12:     L_0006: stloc.0 

  13:     L_0007: ldloc.0 

  14:     L_0008: newobj instance void WindowsFormsApplication24.Foo::.ctor()

  15:     L_000d: callvirt instance int32 [mscorlib]System.Collections.ArrayList::Add(object)

  16:     L_0012: pop 

  17:     L_0013: nop 

  18:     L_0014: ldloc.0 

  19:     L_0015: callvirt instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Collections.ArrayList::GetEnumerator()

  20:     L_001a: stloc.2 

  21:     L_001b: br.s L_002b

  22:     L_001d: ldloc.2 

  23:     L_001e: callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()

  24:     L_0023: castclass WindowsFormsApplication24.Foo

  25:     L_0028: stloc.1 

  26:     L_0029: nop 

  27:     L_002a: nop 

  28:     L_002b: ldloc.2 

  29:     L_002c: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()

  30:     L_0031: stloc.3 

  31:     L_0032: ldloc.3 

  32:     L_0033: brtrue.s L_001d

  33:     L_0035: leave.s L_0051

  34:     L_0037: ldloc.2 

  35:     L_0038: isinst [mscorlib]System.IDisposable

  36:     L_003d: stloc.s CS$0$0002

  37:     L_003f: ldloc.s CS$0$0002

  38:     L_0041: ldnull 

  39:     L_0042: ceq 

  40:     L_0044: stloc.3 

  41:     L_0045: ldloc.3 

  42:     L_0046: brtrue.s L_0050

  43:     L_0048: ldloc.s CS$0$0002

  44:     L_004a: callvirt instance void [mscorlib]System.IDisposable::Dispose()

  45:     L_004f: nop 

  46:     L_0050: endfinally 

  47:     L_0051: nop 

  48:     L_0052: ret 

  49:     .try L_001b to L_0037 finally handler L_0037 to L_0051

  50: }

y más concretamente nos vamos a centrar en estás dos instrucciones

   1: L_001e: callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()

   2: L_0023: castclass WindowsFormsApplication24.Foo

Hombre un OpCodes.Castclass. Lo más bonito de este link no es otra cosa que la siguiente frase.

“Intenta convertir un objeto pasado por referencia en la clase especificada”

Efectivamente “intenta”, con lo cual si no puede, sorpresa y nos llevaremos como premio, un magnifico “InvalidCastException” y para ello simplemente creamos una clase Foo1 y la agregamos al ArrayList.

   1: private void Metodo()

   2: {

   3:  var Lista = new System.Collections.ArrayList();

   4:  Lista.Add(new Foo());

   5:  Lista.Add(new Foo1());

   6:  foreach (Foo item in Lista)

   7:  {

   8:  

   9:  }

  10: }

Lo que es evidente, que var funciona, puesto que es imposible saber que es lo que ArrayList tiene. Solo sabemos que tiene object y por tanto “var” es “object”.

Pero se te olvido una cosa OfType<T>() y entonces “var” si que funciona perfectamente, aunque que quede claro que no aconsejo el uso de esta instrucción en este caso, puesto que nos comemos un elemento. Lo que si aconsejo es que nos olvidemos para siempre si puede ser de las colecciones no genericas, aunque algunas veces es imposible y más cuando se trata de collecction ligadas a WindowsForm,WPF,Silverlight, que bastante mal se llevan con el uso de Generic.

En definitiva para que “var” funcione correctamente en este caso basta con escribir lo siguiente.

   1: private void Metodo()

   2: {

   3:     var Lista = new System.Collections.ArrayList();

   4:     Lista.Add(new Foo());

   5:     Lista.Add(new Foo1());

   6:     foreach (var item in Lista.OfType<Foo>())

   7:     {

   8:  

   9:     }

  10: }

Aunque como dije anteriormente el segundo elemento nos lo comemos, de esta forma veo que yo si utilizaría “var” siempre en un foreach.

Cuando me refería a no abusar,  me refería, aunque se puede, a utilizar este tipo de cosas.

   1: var i = ((Func<int, int, int>)((x, y) => x + y))(10,10);

Esto me recuerda a un antiguo compañero que cuando sumaba enteros escribía “var z = 0x3e8 + 0x3e8;” que si hombre que sí que z = 2000 ó z = 1000+ 1000;

Aclarado el primer comentario, vamos con el segundo que ese si que está lleno de mala leche:).

La pregunta es… Suponiendo que lo anterior compilase, esa otra línea debería hacerlo?

var r = (x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17 => x1+x17)(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17);

Repito: si la sentencia anterior compilase, esa debería compilar también? Sí? No? Tic, tac…

PD: Si, admito que si alguien crea una lambda con 17 parámetros es para darle una colleja 😛

La respuesta es No y os explico, para ello nos debemos trasladar a la primera aparición de Func y Action en el Framework 3.5. El número maximo de parametros permitidos eran 4.

Dibujo

Si avanzamos en el tiempo y analizamos esto mismo a partir del Framework 4.0 nos encontramos con que los parametros permitidos son 16, fijaos en la maldad de poner 17:).

Dibujo

tic,tac…. Pero no conformándonos con esto vamos a demostrarle a Eduard que si  compilo.

Alguien me impide a mí definir un Func y un Action con 17 parámetros o con 50, pues no otra cosa es que directamente te pueden llamar “ñapas”:).

   1: namespace System

   2: {

   3:     public delegate TResult Func<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T15, in T16,in T17, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, T13 arg13, T14 arg14, T15 arg15, T16 arg16,T17 arg17); 

   4:  

   5: }

Y ahora vamos a hacer que compile utilizando “var”.

   1: var i = ((Func<int, int, int,int,int,int,int,int,int,int,int,int,int,int,int,int,int,int>)

   2:                ((x1, x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17) => 

   3:                  x1+x2+x1+x4+x5+x6+x7+x8+x9+x10+x11+x12+x13+x14+x15+x16+x17))

   4:                (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17);

   5: Console.Write(i);

El resultado obtenido que es pues sencillo 151, pero claro no debéis de utilizar esta técnica para sumar una secuencia de números correlativa, mas vale que  veáis este video.

http://www.youtube.com/watch?feature=player_embedded&list=PLB33C2EAFBCB22655&v=9gv2YyRvdB0

Tic,Tac……

Saludos,

var en c# si, o var no

Este es de esos post donde uno piensa exactamente igual que cuando deshoja una margarita. Me quiere no me quiere. Mi opinión antes de seguir es que la quiero:).

Todo esto surge a raíz de una conversación en twitter que podéis seguir aquí y que lógicamente expuso diferentes opiniones que al final es en lo que consiste, Yo opino, tu opinas.

¿Quien tiene la razón? todos:).

Un poco de puesta en escena

https://twitter.com/#!/_PedroHurtado/status/202057547094564864, claro yo haciendo caso omiso a los consejos de mi padre “Pedro Dios te dio dos orejas para escuchar el doble de lo que hablas”. No se me ocurre otra cosa que lanzar esto:).

https://twitter.com/#!/_PedroHurtado/status/202058984570621953 y después no conforme con esto un poco más de fuerza bruta.

https://twitter.com/#!/_PedroHurtado/status/202060219935756288

Con lo cual surge todo esto.

https://twitter.com/#!/_PedroHurtado/status/202061813746110464 y más. Que no es necesario hacerse pesado y vamos al grano.

Que es var?

De las pocas cosas que da gusto leer en el MSND y uno se entera. Es la definición de Variables locales con asignación implícita de tipos (Guía de programación de C#).

Pero si nos centramos en esto vemos cosas que deberían tener algún que otro comentario.

“A las variables locales se les puede asignar un «tipo» deducido var en lugar de un tipo explícito. La palabra clave var indica al compilador que deduzca el tipo de la variable a partir de la expresión que se encuentra en el lado derecho de la instrucción de inicialización”

Efectivamente Eduard(@eiximenis), discrepo totalmente en tu afirmación “cuando el tipo del lvalue es distinto al tipo del rvalue… ;)” no creo que lvalue entendido como la parte izquierda sea distinto jamás al rvalue es decir el lvalue es deducido o mejor dicho inferido a partir del rvalue, con lo cual todo es valido para “var” y lo vamos a ver:).

@Jonathan_JFR, te voy a explicar porque los tipos anónimos o lambda expresion no se pueden inferir, para ello te recomiendo esta lectura Why can’t an anonymous method be assigned to var?. En la cual el gran Eric Lippert metió la pata hasta donde se puede meter:), me quedo con la respuesta de Mehrdad.

“@Eric: Good answer, but it’s slightly misleading to illustrate this as something that’s not possible, as this actually works completely fine in D. It’s just that you guys didn’t choose to give delegate literals their own type, and instead made them depend on their contexts… so IMHO the answer should be «because that’s how we made it» more than anything else. :)”

Pues si Eriq es confuso y te voy a poner un ejemplo sencillo.

var i = 123;

Esto que es?

Un int,uint,short,ushort,long,ulong,sbyte,byte

¿Por qué se infiere un int?

Creo que “porque así se decidió”.

Con lo cual tu para inferir un «tipo integrado” o un ValueType te tienes que ir a la siguiente tabla.

Tabla de valores predeterminados (Referencia de C#).

En resumen si yo quiero hacer que i sea del tipo Decimal tengo que hacer esto.

var i=123M; o bien var i = 123m; es decir hacer uso de los Literales y más concretamente de los sufijos de estos o bien en el caso de short que no lo tiene hacer uso de lo siguiente.

var i=(short)0;

A partir de este momento es fácil deducir como una Lambda Expresion o bien un Método Anónimo lo puedo poner a la derecha de “var”.

   1: var i = delegate(int y) //Error No se puede asignar metodo anonimo a una variable local tipificada implicitamente

   2: {

   3:    return y * 2;

   4: };

   5: var i = ((Func<int,int>)delegate(int y) //Ok

   6: {

   7:    return y * 2;

   8: });

Ahora vamos con las Lambda Expresion, estás pueden devolver o un Func,etc,etc o bien o una Expresión es decir esto sería un Func.

   1: Func<int, int, int> b = (x,y)=>x*y;

o eso mismo podría ser

   1: Expression<Func<int, int, int>> b = (x, y) => x * y;

Alguien me impide a mi hacer esto.

   1: var i = (int x, int y) => x * y;

Si el compilador, pero que ocurre si hago lo siguiente, pues que todo funciona.

   1: var i = (Func<int,int,int>)((x,y)=>x+y);

   2: var resultado = i(10, 10);

   3: Console.WriteLine(i.GetType());

   4: Console.WriteLine(resultado.GetType());

   5: Console.WriteLine(resultado);

Os imagináis el resultado, pues os lo muestro.

System.Func`3[System.Int32,System.Int32,System.Int32]

System.Int32

20

Otra cosa, puede hacer esto?.

var i = null, pues no y para ello lo mejor que podemos es revisar lo siguiente. Implicitly Typed Local Variables, concretamente el apartado de “restrictions”.

Pues sí puedo hacer y muchas cosas más.

   1: var nullint32 =(int?)0;

   2: var nulo = (Clase)null;

   3: var xByte = (byte)0;

   4: var Xdecimal= 0M;

   5: var XLong = 0L;

   6: var XDouble = 0D;

   7: var XUint = 0U;

   8: var XShort = 0;

   9: var xFloat = 0F;

  10: var XChar = '1';

  11: var XEnum = (aaa)1;

  12: var xShort = (short)32767;

Y de esta forma todas las que se os ocurran:).

Conclusiones.

1. Que como todo tiene su razón de ser, que se puede utilizar y ahorrar trabajo es evidente.

2. Que si  abusamos puede llegar a ser malo, también.

3. Yo lo utilizo siempre en linq, tipos anonimos,al crear un nuevo objeto, en using,devoluciones de metodos y foreach y hasta el momento no he tenido ningún problema.

4. Es el Dim de Vb. Nooooooooooooooo.

5. Es un variant de vb o bien un object de c#. Nooooooooo, si revisamos el IL vemos que exactamente tanto una como la otra sentencia genera lo mismo.

int i = 10;

var i=10;

6. Es confuso Noooooooooo, para mi confuso es no poner nombres claros a las variables, eso si que es confuso.

El scroll infinito.

Hola, os acordáis de Pepe, si hombre el usuario que nos ayudo a crear el patrón “Engañabobos”.  Seguro que a alguno y es normal le puede sonar a risa, por eso lo lógico, es que leáis antes este post .

Después de pensar que todo estaba solucionado, suena el teléfono y como no, es otra vez “Pepe”. Su frasecita “esto sigue igual de lento”.

Nos ponemos a trabajar,pensamos en voz alta y decimos :

1. Si estamos paginando.

2. Hemos hecho un análisis de las consultas y todas van bien.

Que puede pasar? Minutos después nos dimos cuenta que si que estábamos haciendo todo eso , pero….

Se ejecutaba un “SELECT COUNT()” en cada Round-Trip, para mostrar el número de paginas al usuario.

Efectivamente hemos localizado el problema, pero como lo solucionamos, pues sencillo y por eso el título.

Alguien utiliza twitter, a que sí, pues vamos a hacerlo para MVC.

1. Definimos una clase sencilla llamada Datos. Hoy no tenemos entidades ni EF, ni nada de eso.

   1: using System;

   2: using System.Collections.Generic;

   3: using System.Linq;

   4: using System.Web;

   5:  

   6: namespace MvcApplication17.Models

   7: {

   8:     public class Datos

   9:     {

  10:         public int Id { get; set; }

  11:         public string Nombre { get; set; }

  12:     }

  13: }

2. En nuestro controlador vamos a escribir lo siguiente en el método Index.

   1: public ActionResult Index(int? Pagina)

   2: {

   3:     ViewBag.Message = "Welcome to ASP.NET MVC!";

   4:  

   5:     List<Datos> datos = new List<Datos>();

   6:  

   7:     for (int i = 0; i < 500; i++)

   8:     {

   9:         int id = i+1;

  10:         datos.Add(new Datos() { Id = id, Nombre = string.Format("Nombre {0}", id) });

  11:     }

  12:  

  13:     var Skip = Pagina ?? 0;

  14:     var Take = 50;

  15:     var model = datos.Skip(Skip * Take).Take(Take);

  16:  

  17:     if (!Request.IsAjaxRequest())

  18:     {

  19:         return View(model);

  20:     }

  21:     else

  22:     {

  23:         return Json(model, JsonRequestBehavior.AllowGet);                                     

  24:     }

  25: }

Fijaos en un detalle “Request.IsAjaxRequest()”, con esto lo que hago es controlar si me están haciendo peticiones vía Ajax.

3. Nos queda la vista y un poco de algunas cosas. Siguiendo este magnífico post de @jmaguilar y viendo que podemos utilizar MVVM,uno de mis favoritos, nos ponemos manos a la obra:).

Para ello voy a utilizar dos referencias que podéis encontrar en la pagina oficial de knockout.

The "template" binding

The "attr" binding

Es decir nuestra vista va a quedar de la siguiente forma.

   1: @using MvcApplication17

   2: @model IEnumerable<MvcApplication17.Models.Datos>

   3:            

   4: @{

   5:     ViewBag.Title = "Home Page";    

   6: }

   7: <script src="@Url.Content("~/Scripts/knockout.js")" type="text/javascript"></script>
   1:  

   2: <h2>@ViewBag.Message</h2>

   3:  

   4: <p>

   5:     @Html.ActionLink("Create New", "Create")

   6: </p>

   7:  

   8: @*Tabla*@

   9: <table data-bind="template: { name: 'data-template', foreach: items }">

  10: <thead>

  11:     <tr>        

  12:         <th>Nombre</th>

  13:         <th></th>

  14:     </tr>

  15: </thead>

  16: </table> 

  17:  

  18: @*Template*@

  19: <script type="text/html" id="data-template">

  20:     <tr>    

  21:     <td data-bind="text: Nombre"></td>    

  22:     <td>

  23:         <a data-bind="attr: { href: 'Home/Edit/' + Id}">Edit</a>|

  24:         <a data-bind="attr: { href: 'Home/Details/' + Id}">Details</a>|

  25:         <a data-bind="attr: { href: 'Home/Delete/' + Id}">Delete</a>

  26:      </td>

  27:     </tr>

</script>

Claro esto no hace absolutamente nada, para ello, tenemos que utilizar javascript y dar vida a esta paginación.

   1: <script type="text/javascript">

   2: var Home_Index = {

   3:     items:ko.observableArray(@Html.Raw(@Json.Encode(Model))),

   4:     pagina:0,

   5:     endData:false,

   6:     redord:50,

   7:     scroll:function(){

   8:         if(($(window).scrollTop() == $(document).height() - $(window).height()) && !Home_Index.endData){

   9:             Home_Index.pagina++;

  10:             $.getJSON('Home/Index',{pagina:Home_Index.pagina},Home_Index.nextPage)

  11:         }

  12:     },

  13:     nextPage :function(data){

  14:         if (data.length>0){

  15:             $.each(data, function(i, item){

  16:                 Home_Index.items.push(item);

  17:             });

  18:         }      

  19:  

  20:         if (data.length < Home_Index.record){

  21:            Home_Index.endData=true;

  22:             Home_Index.paginga--;

  23:      

  24:         }

  25:     }              

  26: };

  27: ko.applyBindings(Home_Index);

  28: $(window).scroll(Home_Index.scroll);

  29: </script>

Y de repente nos encontramos con que “Pepe” está contento podemos paginar sin hacer un Count().

En definitiva nos tenemos que centrar en lo siguiente.

items:ko.observableArray(@Html.Raw(@Json.Encode(Model))),

Con esto evitamos que en la primera llamada tengamos que hacer una nueva llamada via ajax, cosa que he visto en muchas ocasiones. Cargar la pagina y después uno de los plugin hace una llamada para refrescar los datos.

El resto poca explicación tiene para los más puestos en javascript  excepto esta línea de la que podéis ampliar información Observables .

ko.applyBindings(Home_Index);

Claro alguno dirá, pero estar compiando esto en cada vista, como que no, pues os voy a dar una idea, podéis utilizar un método extensor y extender el objeto HmtlHelper, os paso la firma del método y quien quiera que se ponga manos a la obra:)

   1: public static class MisHelper

   2: {

   3:     public static MvcHtmlString InfiniteScroll(this System.Web.Mvc.HtmlHelper Helper,string Controller, string Action,int Record,object Model)

   4:     {

   5:         string Type = string.Format("{0}_{1}",Controller,Action);

   6:         StringBuilder sb = new StringBuilder();          

   7:         

   8:         //El resto es trabajo vuestro:)

   9:          

  10:         TagBuilder script = new TagBuilder("script");

  11:         script.Attributes.Add("Type", "text/javascript");

  12:         script.InnerHtml = sb.ToString();

  13:  

  14:       

  15:  

  16:         return new MvcHtmlString(script.ToString(TagRenderMode.Normal));

  17:     }

  18: }

Si hacemos esto conseguiremos en nuestra vista no tener esa cantidad de javascript y ejecutar esto vía razor.

@Html.InfiniteScroll("Home", "Index", 50, Model);

Como todos no son virtudes y existe algún que otro problema y sobre todo con Javascript y el formateo de fechas y números que lo odio:). Os voy a contar otra forma que se me ha ocurrido.

Separar mi vista en dos y las filas de la tabla generarlas en una vista parcial.

1. Controlador.

   1: public ActionResult Index(int? Pagina)

   2: {

   3:    ViewBag.Message = "Welcome to ASP.NET MVC!";

   4:  

   5:    List<Datos> datos = new List<Datos>();

   6:  

   7:    for (int i = 0; i < 500; i++)

   8:    {

   9:        int id = i+1;

  10:        datos.Add(new Datos() { Id = id, Nombre = string.Format("Nombre {0}", id) });

  11:    }

  12:  

  13:    var Skip = Pagina ?? 0;

  14:    var Take = 50;

  15:    var model = datos.Skip(Skip * Take).Take(Take);

  16:  

  17:    if (!Request.IsAjaxRequest())

  18:    {

  19:        return View(model);

  20:    }

  21:    else

  22:    {

  23:        return PartialView("_TableIndex", model);

  24:        //return Json(model, JsonRequestBehavior.AllowGet);                                     

  25:    }

  26: }

Si os fijáis la diferencia es sencilla en vez de devolver Json lo que hago es devolver un PartialViewResult.

2. Nuestra vista quedaría de la siguiente forma.

   1: @using MvcApplication17

   2: @model IEnumerable<MvcApplication17.Models.Datos>

   3:            

   4: @{

   5:     ViewBag.Title = "Home Page";    

   6: }

   7:  

   8: <h2>@ViewBag.Message</h2>

   9:  

  10: <p>

  11:     @Html.ActionLink("Create New", "Create")

  12: </p>

  13:  

  14: <table id = "Grid">

  15:     <tr>

  16:         <th>

  17:             Nombre

  18:         </th>

  19:         <th></th>

  20:     </tr>

  21:     @Html.Partial("_TableIndex",Model)

  22: </table>

  23: <script type="text/javascript">
   1:  

   2:     var Home_Index = {

   3:         pagina: 0,

   4:         endData: false,

   5:         redord: 50,

   6:         scroll: function () {

   7:             if (($(window).scrollTop() == $(document).height() - $(window).height()) && !Home_Index.endData) {

   8:                 Home_Index.pagina++;                

   9:                 $.get('Home/Index', { pagina: Home_Index.pagina }, Home_Index.nextPage);

  10:             }

  11:         },

  12:         nextPage: function (data) {           

  13:             data = data.replace(/^s*|s*$/g, "");

  14:             alert($(data).size())

  15:             if ($(data).length > 0) {

  16:             $('#Grid tr:last').after(data);

  17:             }

  18:             else {

  19:             Home_Index.pagina--;

  20:             Home_Index.endData = true;

  21:             }

  22:         }

  23:     };

  24: $(window).scroll(Home_Index.scroll);

</script>

3. Nuestra vista parcial.

 

   1: @model IEnumerable<MvcApplication17.Models.Datos>

   2: @foreach (var item in Model) {

   3:     <tr>

   4:         <td>

   5:             @Html.DisplayFor(modelItem => item.Nombre)

   6:         </td>

   7:         <td>

   8:             @Html.ActionLink("Edit", "Edit", new { id=item.Id }) |

   9:             @Html.ActionLink("Details", "Details", new { id=item.Id }) |

  10:             @Html.ActionLink("Delete", "Delete", new { id=item.Id })

  11:         </td>

  12:     </tr>

  13: }

 

Analicemos pues las ventajas y desventajas de utilizar esta última, tenemos más control como se puede ver gracias a Razor y el trabajo en el servidor, por contra al devolver html la diferencia en bytes entre utilizar la primera y la segunda forma  es notable 12 Kb frente a 1.5 Kb devolviendo Json en cada petición, es decir bastante diferencia.

Conclusiones.

Poco tiene esto que decir, mas que educar a nuestros usuarios para adaptarse a nuevas formas de ver la información y que pequeños detalles como este, hacen que una aplicación corra o se arrastre:).

Con esto cerramos los proxis dinámicos de EF

En mi anterior post y con bastante ánimo de critica intente explicar mi desacuerdo con ciertas cosas que pasan sin nuestro control. En este lo que intento es que todos conozcáis que es lo que pasa con algunas recomendaciones “Si trabajas con EF declara tus propiedades como virtual que es mejor” .

Mejor ¿Por qué?. Vamos a verlo:).

Si pensamos detenidamente cuando yo declaro algo con el modificador de acceso virtual es porque lo dejo preparado para que otra persona sea capaz de reemplazarlo con override. Es así no?.

Para poneros un poco en antecedentes os recomiendo estos dos post que os pueden ayudar a aclarar ciertas dudas.

Seguro que más de uno lo ha leído, pues Codefirst funciona igual y sino de donde la magia.

Pero me quedo con esta frase “Proxies are a form of dependency injection where additional functionality is injected into the entity”. Para mi no es Inyección de dependencias yo creo que mas que eso es una invasión:).

Otra cosa que me dio bastantes pistas fue esto

“The EF generates proxy types on the fly using the AssemblyBuilder and TypeBuilder in the .NET framework. All the code to perform lazy loading and change tracking is added to the dynamically generated types using the Reflection.Emit APIs”

Mi gran amigo Reflection.Emit desconocido pero potente para invadir. Bueno llegados a este punto, es darle un poco a la imaginación y a las malas artes para descubrir quien hace todo eso. Tras dar vueltas al código fuente con Reflector me encuentro con una clase internal. Como no!!!. Señores public y si puede ser  OpenSource que esto hará que EF crezca.

Sabéis el nombre pues os lo digo

Clase :“EntityProxyFactory” 

Namespace:“System.Data.Objects.Internal”

Dll: “System.Data.Entity.dll”.

Si esa que esta instalada en la GAC y que por mucho que con Nuget actualicemos la versión de EF nunca cambia.

Os acordáis de mi anterior post ese modelo malicioso de Facturas y Clientes,  bueno ahora voy a hacer con el otra cosa y es ver que genera EF por nosotros.

Para ello voy a utilizar reflection y acceder a ciertos campos privados de esa clase y modificarlos para poder guardar en disco la dll generada.

   1: BindingFlags Flags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

   2: using (ModelFacturas db = new ModelFacturas())

   3: {

   4:    //Obtenemos el ensamblado System.Data.Entity.Dll

   5:    Assembly a = typeof(System.Data.Objects.ObjectContext).Assembly;

   6:    

   7:    //Obtenemos el tipo EntityProxiFactory

   8:    var type = (from item in a.GetTypes()

   9:                where item.Name == "EntityProxyFactory"

  10:                select item).FirstOrDefault();

  11:  

  12:    //Modificamos el campo privado s_ProxyAssemblyBuilderAccess para

  13:    //permitir que el ensamblado dinamico se pueda guardar

  14:    //por defecto AssemblyBuilderAccess está marcado como Run

  15:    //Es decir se puede ejecutar pero no se puede Guardar

  16:    var field = type.GetField("s_ProxyAssemblyBuilderAccess", Flags);

  17:    

  18:    object o = Activator.CreateInstance(type);

  19:    field.SetValue(o, AssemblyBuilderAccess.RunAndSave);

  20:  

  21:    //provocamos que EF genere el proxi dinamico

  22:  

  23:    var cliente = db.Clientes.AsEnumerable().FirstOrDefault();

  24:    var facturas = db.Facturas.AsEnumerable().FirstOrDefault();             

  25:    

  26:    //Accedemos al ensamblado dinamico y lo guardamos en disco

  27:    var field2 = type.GetField("s_ModuleBuilders", Flags);

  28:  

  29:    Dictionary<Assembly, ModuleBuilder> dic = field2.GetValue(o) as Dictionary<Assembly, ModuleBuilder>;

  30:    if (dic.Any())

  31:    {

  32:        AssemblyBuilder asm = dic.First().Value.Assembly as AssemblyBuilder;

  33:        asm.Save("tequieroconocer.dll");

  34:    }   

  35: }

Pues a partir de este momento ya tenemos en nuestro directorio de ejecución.  Una dll llamada “tequieroconocer.dll”. El siguiente paso es sencillo la abrimos con cualquier herramienta como Reflector y a disfrutar y conocer:).

Dibujo

 

Fijaos en la magia, nuestras clases originales han sido alteradas e implementan dos interfaces

El resto de cosas os las dejo para que investiguéis, vosotros y de esa forma conocer a fondo este Framework, que tiene sus cosas buenas y otras que no son tan buenas.

Conclusiones.

El conocimiento no debe de tener limites , por lo tanto tiene que ser público y es por esto el motivo de este post.

Sobre todo os tenéis que acordar de dos parámetros importantes cuando utilicéis EF.

Ya conocéis un poquito más de ellos.

Lazy Load. El bueno de Martin ya lo advirtio.

Para que vayáis tomando el aperitivo de lo que viene os recomiendo primero esta lectura Lazy Load “segundo parrafo” y no esta Lazy loading. Bueno es un patrón o bien un antipatrón, para mi sencillo un antipatrón y mira que he tenido discusiones con grandes amigos al respecto, pero claro siguiendo mi línea quiero demostrar porque es un antipatrón.

Hace años y no pocos un grupo de amigos con más de una cerveza en la mano creamos un patrón para trabajar con bb.dd, aquel día no se nos ocurrió otra cosa que llamar a este “engaña bobos”. El nombre, lógicamente, vino definido por el momento.

Porque “engaña bobos”, pues sencillo el usuario nos pedía unos datos que era incapaz de ver y todos pensamos. Pues vamos a darle a sus ojos exclusivamente lo que puede ver.

  • 10 Registro por página.
  • Cinco Campos.

En definitiva este señor se quejaba de la lentitud de las consultas y después de aplicar este patrón a nuestro desarrollo, el fue feliz y nosotros no te digo nada. Jo con el Pepe que no se queja:).

De verdad  pensáis que estoy de broma, pues no, voy a demostrar el efecto del “engaña bobos” con Entity Framework 4.3.

Hace unos días y en una conversación de twitter me encontré con unos amigos a los que les dije lo siguiente Lazy Load el Antipatrón. Engaña bobos el patrón.

Lo demostramos, venga vamos a ello.

Primer paso. Un modelo sencillo, pero con bastante ocultismo, puesto que eso no representa ni la mitad de la vida real . Una factura y un cliente y hoy con DataAnotations, por cambiar de tercio:).

   1: public class ModelFacturas:DbContext

   2:    {

   3:        public ModelFacturas()

   4:        {

   5:            this.Configuration.LazyLoadingEnabled = false;

   6:        }

   7:        public DbSet<Cliente> Clientes { get; set; }

   8:        public DbSet<Factura> Facturas { get; set; }

   9:    }

  10: [Table("Clientes")]

  11: public class Cliente

  12: {

  13:    [Key]

  14:    public int Id { get; set; }

  15:    public string Nombre { get; set; }

  16: }

  17: [Table("Facturas")]

  18: public class Factura

  19: {

  20:    [Key]

  21:    public int Id { get; set; }        

  22:    public int ClienteId { get; set; }

  23:    [ForeignKey("ClienteId")]

  24:    public virtual Cliente Cliente { get; set; }

  25:    public Decimal Importe { get; set; }

  26:    public DateTime FechaFactura { get; set; }

  27:  

  28:  

  29: }

A que todos sabemos que oculta algo, por lo menos 50 propiedades más por entidad que cuesta leerlas:).

Segundo Paso. Pepe nos solicita una consulta con las facturas en la que tenemos que mostrar el importe, fecha de factura y nombre del cliente.

Tenemos muchas formas de hacerlo y algunas nos puede hacer que nos duela la cabeza, eso sí en desarrollo perfecto, ahora en producción Valium 10:).

1. Hemos leído  muchas veces que existe un método AsNoTracking() en el objeto DbQuery que hace entre otras maravillas ganar en performace.

   1: using (ModelFacturas db = new ModelFacturas())

   2: {

   3:    var resultado = from factura in db.Facturas.AsNoTracking()

   4:                    select factura;

   5:  

   6:    foreach (var item in resultado)

   7:    {

   8:        Console.WriteLine(item.Cliente.Nombre);

   9:    }

  10: }

La verdad que sí, pero en este caso no. Recupera las facturas y por cada factura recuperada lanza la siguiente sentencia.

   1: exec sp_executesql N'SELECT 

   2: [Extent1].[Id] AS [Id], 

   3: [Extent1].[Nombre] AS [Nombre]

   4: FROM [dbo].[Clientes] AS [Extent1]

   5: WHERE [Extent1].[Id] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=1

En mi caso la simulación eran 100 registros, pues vamos a pensar en 100.000:(.

2. No me gusta, por lo tanto nos olvidamos de “AsNoTracking”

   1: using (ModelFacturas db = new ModelFacturas())

   2: {

   3:    var resultado = from factura in db.Facturas

   4:                    select factura;

   5:  

   6:    foreach (var item in resultado)

   7:    {

   8:        Console.WriteLine(item.Cliente.Nombre);

   9:    }

  10: }

Respuesta en la bb.dd, solo dos sentencias. una para recuperar las facturas y la otra para recuperar el cliente, pero una sola, menuda maravilla.

   1: SELECT 

   2: [Extent1].[Id] AS [Id], 

   3: [Extent1].[ClienteId] AS [ClienteId], 

   4: [Extent1].[Importe] AS [Importe], 

   5: [Extent1].[FechaFactura] AS [FechaFactura]

   6: FROM [dbo].[Facturas] AS [Extent1]

   7:  

   8:  

   9: exec sp_executesql N'SELECT 

  10: [Extent1].[Id] AS [Id], 

  11: [Extent1].[Nombre] AS [Nombre]

  12: FROM [dbo].[Clientes] AS [Extent1]

  13: WHERE [Extent1].[Id] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=1

Pero claro no os he contado una cosa y es que para el ejemplo hice trampas , a todas las facturas le inserte el mismo cliente:).

   1: using (ModelFacturas db = new ModelFacturas())

   2: {

   3:     Cliente cl = new Cliente() { Nombre = "Cliente1"};

   4:  

   5:  

   6:     db.Clientes.Add(cl);

   7:  

   8:     db.SaveChanges();

   9:  

  10:     for (int i = 0; i < 100; i++)

  11:     {

  12:         db.Facturas.Add(new Factura() { ClienteId = cl.Id, FechaFactura = DateTime.Now.Date, Importe = 1 });

  13:     }

  14:  

  15:     db.SaveChanges();

  16: }

Con lo cual excepto que tu empresa tenga un solo cliente y mal tiene que funcionar , el resultado es casi igual de malo.

Tercer Paso.

Vamos a aplicar el “Engaña bobos” varias formas pero una sencilla gracias a EF.

   1: using (ModelFacturas db = new ModelFacturas())

   2: {

   3:    var resultado = from factura in db.Facturas.AsNoTracking()

   4:                    select new

   5:                    {

   6:                        FacturaId = factura.Id,

   7:                        Fecha = factura.FechaFactura,

   8:                        Importe = factura.Importe,

   9:                        Cliente = factura.Cliente.Nombre

  10:                    };

  11:  

  12:    foreach (var item in resultado)

  13:    {

  14:        Console.WriteLine(item.Cliente);

  15:    }

  16:  

  17:    

  18: }

Fijaos en detalles. Aquí si utilizo “AsNoTracking”, que pasa que tengo que definir una clase con cuatro campos, pues sí y así lo deberías de hacer o no te solicitaron eso, que pasa que da pereza, pues no. Si nos piden eso, no inventes, cual sería en problema?

Creo que ninguno siguiendo los principios No vas a necesitarlo (YAGNI). En este caso si lo necesito.

Y para acreditar el resultado solo hay que mirar esto.

   1: SELECT 

   2: [Extent1].[Id] AS [Id], 

   3: [Extent1].[FechaFactura] AS [FechaFactura], 

   4: [Extent1].[Importe] AS [Importe], 

   5: [Extent2].[Nombre] AS [Nombre]

   6: FROM  [dbo].[Facturas] AS [Extent1]

   7: INNER JOIN [dbo].[Clientes] AS [Extent2] ON [Extent1].[ClienteId] = [Extent2].[Id]

Señores, eficiente.

Conclusiones.

Que ocurre que nos da miedo hacer join o que no sabemos, por que la moda es no enseñarlo. En este caso es más eficiente un join que cualquier otra cosa, lo puedes hacer con “Incluce” con “join” o como os he mostrado, simple.Pero utilízalo.

Un detalle que he dejado pasar por alto y quizá alguno se ha dado cuenta,  en mi primer ejemplo de código escribí esto.

this.Configuration.LazyLoadingEnabled = false;

Efectivamente no utilices el Antipatrón o si lo haces conociendo lo que puede pasar, que ahorra poco, frente a una lectura explicita y cuando la necesito.