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.

Buscando al culpable

Os pongo en antecedentes Sql Azure no soporta transacciones distribuidas y para confirmarlo podéis leer esta entrada TransactionScope() in Sql Azure, o por lo menos con ese mismo escenario es con el que yo me he encontrado.

La aplicación está desarrollada utilizando los siguientes componentes.

  • Aplicación desarrollada en MVC3
  • Entity Framework 4.2
  • Sql Azure

Una de las reglas de negocio de esta aplicación es que a la entidad cliente se le debe de asociar un usuario, pues bueno que fue lo que hice, para no reinventar la rueda y puesto que MVC3 me dota de la posibilidad de utilizar MemberShip Provider.

Vamos a utilizar el metodo MemberShip.CreateUser y en la entidad Cliente creo una columna que sea un Guid y que haga referencia a este, aparentemente solucionado en un entorno de desarrollo, pero cuando hago el despliegue en Azure me encuentro con un magnífico error, que resumiendo me esta confirmando que este no soporta transacciones distribuidas.

Nos queda localizar quien es el culpable de este error y nos ponemos manos a la obra.

1. Transaction Scope. Primer descartado puesto que en otros puntos de la aplicación se está utilizando y no generaba ningún error.

2. MemberShip Provider. Para que TransactionScope cree una transacción distribuida una de las condiciones es que la cadena de conexión sea diferente. Así que vamos a confirmar que MemberShip no altera la cadena de conexión. Donde miramos? Pues en la propiedad  Provider del objeto MemberShip y dentro de este en el campo privado _sqlConnectionString.

Esto lo podemos hacer de dos formas para el más friki con System.Reflection utilizando este código.

   1: var connectionStringField = Membership.Provider.GetType()

   2: .GetField("_sqlConnectionString", BindingFlags.Instance | BindingFlags.NonPublic);

   3:  

   4: var conexion = connectionStringField.GetValue(Membership.Provider);

Y para los que no tienen ganas de complicarse la vida que utilicen “inspección rapida” de visual studio o cualquier otro mecanismo yo utilizo este puesto que es rápidoSonrisa Shift+F9, bueno eso lo dejo a elección del consumidor.

Dibujo3

Pues me parece que solo nos queda un candidato, será será EntityFramework, pues sí hemos acertado y por qué?.

3. EntityFramework. A partir de la versión 4.2 hace cosas por nosotros, que no se si las deberían de hacer pero sin preguntarlo agregan a nuestra cadena de conexión “Application Name=EntityFrameworkMUE” si esta en nuestro config no contiene “Application Name”. Y de verdad que no miento y para ello lo mejor es abrir con Reflector EntityFramework.dll y en el objeto InternalConnection localizamos un metodo estático que se llama.

“AddAppNameCookieToConnectionString”

No muestro el código completo pero si un aperitivo de lo que haceSonrisa

   1: builder.ApplicationName = "EntityFrameworkMUE";

   2: connection.ConnectionString = builder.ToString();

   3: return connection.ConnectionString;

Redoble de tambores. a partir de la versión 4.2 EntityFramework es el causante de generar transacciones distribuidas, puesto que agrega a la cadena de conexión cosas sin nuestro permiso.

En esta otra entrada del foro de c# podéis encontrar opiniones de otras personas con las que coincido en su totalidad Transaction Scope y sobre todo lo que más me alegra es que en ese foro se muestre la solución a una respuesta que en otro con muchísimo prestigio se encuentre esto Using TransactionScope with Entity Framework code first and universal providers, no somos tan malos. Así que a participar, que entre todos podemos hacer que la cosa mejore

Solución.

Agregar a nuestra cadena de conexion en el config “Application Name=No me ayudes tanto que me gusta hacerlo a mi”

Conclusiones.

Sencillas.

1. Que MVC no debería incorporar MemberShip y mucho menos crear un Controlador AcountController con todo el mecanismo incorporado. Uno es abierto y fácil de configurar “MVC” y el otro es hermético como una lata, MemberShip. Por algo se creo en 2005.

2.Que le agradezco a EntityFramework todo lo que me ahorra, pero que no haga cosas de este tipo,  que tenga yo la libertad de configurar lo que quiera y como quiera. A los señores del equipo yo les invitaría a tomarse unas vacaciones en conjunto con el equipo de MVC.

3. Que estamos en un momento en el que los cambios tan rápidos nos están haciendo caer en este tipo de cosas, detalles que son muy difíciles de resolver y que por tanto, con un poco más de calma quizá todos seamos más felices, quien produce y quien consume.

Entity Framework y los indices únicos.

Ya me imaginaba yo que después de ver con Profiler las sentencias que genera  con algún que otro problema nos íbamos a encontrar.

Vamos a plantearnos el siguiente problema, tenemos una entidad países con las siguientes propiedades.

   1: public class Pais

   2: {

   3:     public int Id { get; set; }

   4:     public string Nombre { get; set; }

   5:     public string Codigo { get; set; }

   6: }

Utilizando EF 4.3 y Migrations vamos a generar la tabla en la bb.dd y uno de los requerimientos es que “Codigo” sea único, si queréis ampliar temas de Migrations podéis visitar el blog de Javier Torrecilla. Yo os muestro el código tal y como lo he generado para crear la tabla.

   1: namespace WindowsFormsApplication1.Migrations

   2: {

   3:     using System.Data.Entity.Migrations;

   4:     

   5:     public partial class Paises : DbMigration

   6:     {

   7:         public override void Up()

   8:         {

   9:             CreateTable(

  10:                 "Pais",

  11:                 c => new

  12:                     {

  13:                         Id = c.Int(nullable: false, identity: true),

  14:                         Nombre = c.String(nullable:false,maxLength:50,unicode:false),

  15:                         Codigo = c.String(nullable: false, maxLength: 3, unicode: false),

  16:                     })

  17:                 .PrimaryKey(t => t.Id);

  18:  

  19:             CreateIndex("Pais", "Codigo", unique: true);

  20:             

  21:         }

  22:         

  23:         public override void Down()

  24:         {

  25:             DropTable("Pais");

  26:            

  27:         }

  28:     }

  29: }

Ahora vamos con nuestros requerimientos, en la tabla tenemos dos paises “España” y “Francia” que los podemos agregar con las siguientes instrucciones.

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

   2: {

   3:     ct.Paises.Add(new Pais(){Nombre="España",Codigo="34"});

   4:     ct.Paises.Add(new Pais() { Nombre = "Francia", Codigo = "33" });

   5:     ct.SaveChanges();

   6: }

Y ahora nos dicen que lea la tabla Países y que cambie el código de “España a 33” y elimine “Francia” y de paso que inserte “China con código 35”.

Bueno pues sencillo

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

   2: {

   3:     var paises = ct.Paises.AsEnumerable();

   4:     foreach (var pais in paises)

   5:     {

   6:         if (pais.Nombre == "Francia")

   7:         {

   8:             ct.Paises.Remove(pais);

   9:         }

  10:         else if (pais.Nombre == "España")

  11:         {

  12:             pais.Codigo = "33";

  13:         }

  14:     }

  15:     ct.Paises.Add(new Pais() { Nombre = "La China", Codigo = "35" });

  16:     ct.SaveChanges();

  17: }

Que ocurre que cuando ejecutamos nos encontramos con una sorprendente exception del tipo DbUpdateException, pero si miramos su InnerExceptión nos encontramos una SqlExceptión que nos dice lo siguiente.

“Cannot insert duplicate key row in object ‘dbo.Pais’ with unique index ‘IX_Codigo’.”

Claro que ocurre que Entity Framework ejecuta primero “Update,Delete,Insert”, con lo cual yo siempre pensé que era “Delete,Update,Insert”, pero aún así en determinados escenarios nos podría ocasionar problemas.

Conclusiones.

Os voy a ser sincero, es un problema complicado de resolver, pero como dicen poder se puede, la verdad que no lo he probado con Merge, pero yo hasta ahora lo hacía de la siguiente forma.

Delete Tabla From, Update Tabla From, Insert Into Select y la verdad que os confieso que dormía bastante tranquilo, cosa que a partir de este momento no lo voy a hacer tanto.

Lanzo una pregunta ¿Os gustaría en linq que se pudieses ejecutar este tipo de sentencías?

Null. La Historia interminable 2/3

Si recordamos el anterior Post, una de las frase que comente fue que desde el 2005 yo estaba libre de null y claro no hay que decirlo sino realmente demostrarlo y es por eso el motivo de esta entrada.

Lo primero que vamos a hacer es definir una interface que herede de IDataRecord y en ella definir unos métodos que nos permitan leer Nullables.

   1: public interface IDataRecordNullable : IDataRecord

   2: {

   3:     bool? GetNullBoolean(int i);

   4:     byte? GetNullByte(int i);

   5:     char? GetNullChar(int i);

   6:     DateTime? GetNullDateTime(int i);

   7:     Decimal? GetNullDecimal(int i);

   8:     double? GetNullDouble(int i);

   9:     float? GetNullFloat(int i);

  10:     Guid? GetNullGuid(int i);

  11:     Int16? GetNullInt16(int i);

  12:     Int32? GetNullInt32(int i);

  13:     Int64? GetNullInt64(int i);

  14:     string GetNullString(int i);

  15: }

Una vez definida  nos toca implementarla para ello voy a definir una clase a la cual en el constructor le voy a pasar como parametro un IDataRecord.

Solamente voy a implementar una par de métodos y eso sí os dejo la tarea del resto por si alguien quiere vivir un poco más feliz.

   1: public class MyDataRecord : IDataRecordNullable

   2: {

   3:     private readonly IDataRecord DataRecord;

   4:     public MyDataRecord(IDataRecord DataRecord)

   5:     {

   6:         if (DataRecord == null)

   7:         {

   8:             throw new ArgumentNullException();

   9:         }

  10:         this.DataRecord = DataRecord;

  11:     }

  12:     public int GetInt32(int i)

  13:     {

  14:         return DataRecord.GetInt32(i);

  15:     }

  16:     public int? GetNullInt32(int i)

  17:     {

  18:         if (IsDBNull(i))

  19:             return null;

  20:         return GetInt32(i);

  21:     }

  22: }

A partir de este momento la cosa se hace más agradable simplemente lo que tengo que hacer es utilizar esa clase para leer mis datos tal y como os muestro.

   1: using (SqlConnection cn = new SqlConnection(@"Tu conexion"))

   2: {

   3:     cn.Open();

   4:     string sql = @"SELECT CAST(NULL AS INT) NUMERO,CAST(NULL AS VARCHAR(50)) 

   5:                 CADENA UNION ALL SELECT CAST(1 AS INT),CAST('HOLA' AS VARCHAR(50)) ";

   6:     SqlCommand cmd = new SqlCommand(sql, cn);

   7:     SqlDataReader rd = cmd.ExecuteReader();

   8:                 

   9:     MyDataRecord reader = new MyDataRecord(rd);

  10:  

  11:     while (rd.Read())

  12:     {

  13:         int? numero = reader.GetNullInt32(0);

  14:         string Cadena = reader.GetNullString(1);

  15:     }

  16: }

Como podéis observar sencillo y por fin me puedo librar de utilizar este código una y otra vez.

   1: int? numero = rd.IsDBNull(0) ? null : rd.GetInt32(i);

El ejemplo es útil para cualquier versión del framework a partir de la 2.0, pero claro os voy a dar una idea, para los más modernos, entre los que me incluyo. Porque no utilizamos Extension Methods y podemos hacer lo mismo sin tener que crear un nuevo objeto cuando voy a leer.

Vamos con ello.

   1: public static class ExtensionDbDataReader

   2: {

   3:     public static int? GetNullInt32(this DbDataReader reader,int i)

   4:     {

   5:         if (reader.IsDBNull(i))

   6:             return null;

   7:         return reader.GetInt32(i);

   8:     }

   9: }

Como veis ya no tengo que ir instanciando mi clase sino simplemente al utilizar un Extension Methods me encuentro con un método extensor en cualquiera de las clases que heredan  DbDataReader, jo sin quererlo como que me sirve para cualquier motor de bb.dd.

Bueno os dejo una tarea, que es implementar 11 métodos para cada uno de los tipos primitivos y uno para el maldito “string”. Y ahora voy con ese, que se quedo en el tintero en el anterior post.

Si analizamos nuestras aplicaciones un porcentaje muy alto de nuestras propiedades son string concretamente en una app de aproximadamente unas 500 tablas  y 3800 columnas 2500, son varchar o sus derivados, es decir el 66%. Todas ellas no tienen otra representación en el CLR que no sea un string y claro a este se le puede asignar null y no dice nada, hasta que llega a la bb.dd que si la columna no permite null esta si va a decir y además cosas feas.

Pues hasta el momento solamente nos queda una solución y es bastante tediosa, algo parecido a implementar en nuestras clases INotifyPropertyChanged o bien decorarlas con atributos de System.ComponentModel.DataAnnotations dependiendo de la tecnología que utilicemos en nuestras vistas, ambas de verdad para mí feas, pero feas.

Vamos a solucionar el problema de todos los string que no permiten nulos o por lo menos como yo lo he hecho hasta el momento.

   1: public class Entidad

   2: {

   3:     public int Id { get; set; }

   4:     string _Nombre;

   5:     public string Nombre

   6:     {

   7:         set

   8:         {

   9:             _Nombre = value;

  10:         }

  11:         get

  12:         {

  13:             return _Nombre ?? string.Empty;

  14:         }

  15:     }

  16: }

Como no solamente de leer vive el hombre y a veces nos toca escribir, cuando queremos guardar un valor null en la bb.dd, también tenemos que tener en cuenta alguna que otra cosa y es que no podemos enviar null sino System.DbNull.Value, esto es más sencillo y un poco más corto, simplemente vamos a crear un Extension method para SqlParameterCollection tal y como os muestro.

   1: public static class ExtensionDbParameterCollection

   2: {

   3:     public static SqlParameter AddValueWithNull(this SqlParameterCollection Parameters,string ParameterName,SqlDbType Type,int Size, object Value )

   4:     {     

   5:         var Parametro =  Parameters.Add(ParameterName, Type, Size);

   6:         Parametro.Value = (Value == null) ? System.DBNull.Value : Value;

   7:         return Parametro;

   8:     }

   9: }

Como podéis observar lo he implementado para SqlServer el resto de servidores os lo dejo a vosotros y una cosa importante no utilizar AddWithValue, en esta entrada del foro de c# podéis ver los motivos.

SqlParameterCollection.AddWithValue Method.

Para terminar y como se que alguno va a decir ¿porque no utilizas generic y te ahorras esos 11 métodos y el maldito? Pues sencillo porque me obliga a hacer cast en cada una de las lecturas y menos óptimo puede ser cualquier cosa y más si leemos muchos registros, de todas formas os dejo ese método con generic. Pero ya os he advertido de las consecuencias Guiño.

   1: public static class ExtensionDbDataReader

   2: {

   3:     public static Nullable<T> GetNullValue<T>(this DbDataReader Reader, int i) where T:struct

   4:     {

   5:         if (Reader.IsDBNull(i))

   6:             return null;

   7:         return (T)Reader.GetValue(i);

   8:         

   9:     }

  10: }

Saludos y a implementarlo.

Null.La historia interminable(1/3)

Esta mañana y después de ver esta entrada en el foro de c# Obtener los registros de una tabla como objetos y guardarlos en una lista y más concretamente estas líneas de código.

   1: object fechaNacim = reader["FechaNacim"];

   2: FechaNacimiento = fechaNacim == DBNull.Value ? null : (DateTime)fechaNacim;

Me han llegado a la cabeza recuerdos del pasado, mucho antes de la existencia de .Net. Os acordáis de ese error típico de las aplicaciones vb “error 91 object variable or with block variable not set”. Y después muerte. Pues ha llovido, pero lo seguimos teniendo presente en nuestras vidas.

Después de citar al viejo vb donde esto se solucionaba con esto

   1: if isnull(ADODC.Recordset.Fields("Cod_Localidad")) then

   2: end if

avanzamos en el tiempo y nos encontramos en la misma o parecida situación un null de la bb.dd nos ocasiona y no pocos problemas.

Si nos situamos en el año 2002 aparece un nuevo señor para solucionarnos el problema DBNull.Value, que pasada. Ya no aparece el error 91 pero si FormatException.

   1: var ValorBd = System.DBNull.Value;

   2: int Valor = int.Parse(ValorBd.ToString())

Y también  SqlNullValueException.

   1: using (SqlConnection cn = new SqlConnection(@"<Cadena de Conexion>"))

   2: {

   3:        cn.Open();

   4:        SqlCommand cmd = new SqlCommand("SELECT CAST(NULL AS INT)",cn);

   5:        SqlDataReader reader = cmd.ExecuteReader();

   6:        while(reader.Read())

   7:        {                    

   8:            int Valor = reader.GetInt32(0);

   9:        }

  10: }

y en vez de tener un premio que era lo que se conseguía antes de .Net obtenemos dos.

Bueno como una de nuestras virtudes es la paciencia  esperamos a 2005 y aparece una de las maravillas del Framework 2.0 System.Nullable<T> o para quien más le guste “?” detrás del tipo, parece que el problema está solucionado, pero no,  simplemente con profundizar un poco nos damos cuenta que las clases del namespace System.Data se han quedado igual y es por eso por lo que esta mañana para leer un null  nos encontramos con las primeras líneas de código de este post.

Seguimos avanzando años y vemos que ese problema parece que se va a convertir en un mal menor con la aparición de Entity Framework y la realidad que así es, pero sigue sin ser perfecto.

Si reflexionamos han pasado por lo menos para mí 20 años y seguimos pensado en Null.

Bueno he de reconocer que desde 2005 yo deje de pensar en el y eso es material para el próximo post o mejor dicho un capitulo más de esta historia.

Próximos capítulos.

Como puedo vivir mejor con Null sino utilizo Entity Framework.

Que le falta a EF desde mi punto de vista para de una vez olvidarnos del Null.

Como una de las cosas que más me gusta es el debate vamos a empezar con un aperitivo. ¿Debería string tener un tipo Nullable o lo que es lo mismo declarar este como “?string” ?

Espero que os haya gustado y sobre todo espero vuestra reflexión acerca de la pregunta.

Utilizando razor para generar un csv

En muchos de nuestros desarrollos nos solicitan la funcionalidad de exportar nuestros datos a diferentes formatos y hoy me ha tocado exportar a csv.

Lo lógico, es que lo hubiese hecho de la forma tradicional simple y sencillo defino un StringWriter y un foreach hasta que acabe,pero los años me están haciendo despertar la imaginación,con lo cual he pensado en hacerlo de otra formaSonrisa.

¿Cómo?.Voy a utilizar una vista que se encargue de generar el csv.

Manos a la obra.

Partimos para el ejemplo de un modelo sencillo una factura con las siguientes propiedades Número,Fecha e Importe.

using System;
using System.Collections.Generic;
using System.Linq;


namespace MvcApplication15.Models
{
    public class Factura
    {
        public int Id { get; set; }
        public DateTime FechaFactura { get; set; }
        public Decimal Importe { get; set; }
    }
}

Lógicamente el segundo paso es crear un controlador y un método dentro de él que nos permita solicitar el csv.

Que tengo que hacer, pues retornar un FileContentResult con MimeType “text/csv”.

public ActionResult GetCsv()
{
    //Generamos RazorViewEngine
    var razorView = new RazorViewEngine();
    var viewEngineResult = razorView
             .FindPartialView(ControllerContext, "_GetCsv", false);

    //Creamos la vista y pasamos el modelo
    var modelo = new List<Factura>()
    {
        new Factura(){Id=1,
                      FechaFactura = DateTime.Now.Date,
                      Importe = 1000},

        new Factura(){Id=1,
                      FechaFactura = DateTime.Now.AddDays(1).Date,
                      Importe = 2000}
    };
    var vista = View(viewEngineResult.View, modelo);

    //Renderizamos la vista
    using (StringWriter sw = new StringWriter())
    {
        var viewContext = new ViewContext(
            ControllerContext, 
            vista.View, 
            vista.ViewData, 
            vista.TempData, sw);
        
        vista.View.Render(viewContext, sw);
        
        var result = Encoding.UTF8.GetBytes(sw.ToString());
        
        viewEngineResult.ViewEngine
            .ReleaseView(ControllerContext, vista.View);
        
        return File(result, "text/csv", "facturas.csv");
    }
            

}

y por último tenemos que definir nuestra vista y solicitar nuestro archivo desde el explorador.

@model IEnumerable<MvcApplication15.Models.Factura>
@{
<text>Numero;Fecha;Importe</text>
}
@foreach (var item in Model)
{
<text>@item.Id;@string.Format("{0:dd/MM/yyyy}", item.FechaFactura);
      @string.Format("{0:###,###,##0.00}", item.Importe)</text>    
}
Nota: todo el texto que que se escribe dentro de las etiquetas <text> 
debe de estar en una sola línea.

Conclusiones.

Pues sencillo que MVC nos deja hacer escapar nuestra imaginación hasta límites insospechables, gracias a ser un framework abierto crece y crece o lo podemos hacer crecer Sonrisa.

Espero que os guste y sea de utilidad.

Mis Agradecimientos

HolaSonrisa, lo normal en estos casos es presentarse con esas cosas bonitas que a todos nos dejan estupendamente frente a los demás,pero a parte de eso, que no dudéis, que lo voy a hacer intentaré con esta presentación escribir algo que empiece a ser útil.

Bueno mis agradecimientos.

Jo a quien, pues primero al que te autoriza a escribir en este magnifico sitio Rodrigro Corral, Muchísimas gracias Rodrigo.

Lluis Franco, me es imposible poner todos tus links porque al final va a parecer que estás escribiendo tú, pero bueno todos te conocen y cito este porque si alguien entra a mi blog que  no se pierda esa magnifica serie que estás escribiendo sobre Parallel Series y de paso alguien podrá leer ese Maestro de Maestros Guiño. Muchísimas gracias Lluis.

Bueno esto sería interminable pero quiero citar a personas con las que en twitter comparto risas y buenos momentos @Jtorrecilla,Eduard Tomas(@eiximenis),Luis siempre estás programando y haciendo cenas:), todos ellos tienen como maestros que son, no es que yo me considere, blog’s en geeks. 

Bueno y la verdad que no me puedo olvidar de la comunidad, a todos los que me aguantáis diariamente en los foros de msdn y más concretamente en el foro de c# donde gracias a Leandro soy administrador. Leandro gracias.

No me podía olvidar de dos personas con las que comparto muchas horas y que  siempre me han alentando a escribir. Escribe, escribe…  Luis Y Ramirez y mi amigo del pajarito Nicoloco.

La verdad que soy un pelota, pero llevo una hora buscando todas las url’s para hacer publicidad con lo cual señores estámos en Marzo así que quiero caramelos por semana santa porque «jamón del norte» no me mandanSonrisa.

Sabía yo que alguien me dejaba en el tintero, esa persona que actúa como tu padre y te dice, céntrate, constancia ,etc . no sabéis quien es? mi gran amigo Salvador Ramos.

Y ahora toca contar un poco de mi historia durante un montón de años estoy ligado al desarrollo de aplicaciones empresariales, de esas que no tienen una entidad, ni dos, ni tres ,sino 500 o más y lo lógico es que tengas que pensarlas de forma muy detallada y examinando hasta el último ;.

Es por eso que hace tiempo leí este post Preventing SQL Injection with the Entity Framework and Data Services y como no me puse a probar y a jugar un poco con esa realidad.

Voy a realizar las pruebas que hice, Aunque os las voy a pasar con Entity Framewoek 4.3.1 o como se llame en el futuro, eso si  CodeFirst, me gusta Sonrisa

1. Vamos a definir un modelo sencillo una entidad y no 500

public class Entidad
    {
        public int Id { get; set; }        
        public string Nombre { get; set; }
    }

    public class Context : DbContext
    {
        

        public DbSet<Entidad> Entidad {get;set;}
            
        
    }

Vamos con la primera prueba

 using (Context ct = new Context())
            {

                var resultado = ct.Entidad.Where(c => c.Nombre == "Hola");
                foreach (var entidad in resultado)
                {

                }
                
            }

El resultado es el esperado me da la impresión que si es vulnerable no esta presente sp_executesql y genera una cadena Demonio.

SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[Nombre] AS [Nombre]
FROM [dbo].[Entidads] AS [Extent1]
WHERE N'Hola' = [Extent1].[Nombre]

 

bueno pues vamos a intentar que por lo menos a nadie se le ocurra escribir variable.ToString() , creo que es fácil educar al equipo Sonrisa.

Con lo cual vamos a ejecutar la siguiente prueba

 

var variable = "Hola";           
using (Context ct = new Context())
{

        var resultado = ct.Entidad.Where(c => c.Nombre == variable.ToString());
         foreach (var entidad in resultado)
         {

          }
                
 }

Y cual fue mi gran sorpresa, pues sencillo una enorme satisfacción al encontrarme con una exceptión del tipo NotSupportedException con el mensaje “LINQ to Entities no reconoce el método ‘System.String ToString()’ del método, y este método no se puede traducir en una expresión de almacén”.

Y eso , por que? Pues buscando pronto me encontré con esto Asignar método CLR a función canónica y que suerte ToString() no está soportado.Me di cuenta que lo que decía el post era verdad. Estas funciones están en el namespace System.Data.Objects.SqlClient y concretamente expuestas como métodos estáticos de la clase SqlFunction.

Espero que os haya gustado, hasta el próximo.