El día 22 tuve la suerte con @MiguelEgea, de dar una charla en Málaga .NET User Group y una de las cosas que afirme es que la tabla del sistema que se genera con la versión 4.3 “dbo.__MigrationHistory” en su columna “Model” no guarda un Hash sino que realmente es una copia del modelo, es decir un emdx comprimido.
Inmediatamente @pablonete me pone cara de incrédulo y que mejor que demostrarle eso con un post:).
Independientemente de demostrar esto lo primero que quiero desmitificar es que Codefirst utiliza Xml y no poco, en resumen el mismo que las otras dos modalidades DataBaseFirst y ModelFirst.
Lo segundo es que casi todos los que trabajamos con CodeFisrt tarde o temprano recibimos una “InvalidOperationException” tal y como os muestro.
El porque es sencillo tu modelo no coincide con lo que en versión 4.2 tenemos guardado en la tabla “EdmMetadata”.
Y es aquí donde @pablonete si tiene razón en versión 4.2 si se guarda un “Hash” mientras que en 4.3 no.
Vamos a demostrarlo con la versión 4.2.
1. Definimos un modelo sencillo que es valido para el ejemplo.
1: //Entidad
2: public class Cliente
3: {
4: public int Id { get; set; }
5: public string Nombre { get; set; }
6: }
7: //Configuracion de la entidad Cliente
8: public class ClienteEntityConfiguration : EntityTypeConfiguration<Cliente>
9: {
10: public ClienteEntityConfiguration()
11: {
12: this.ToTable("Clientes");
13: this.HasKey(c => c.Id);
14:
15: this.Property(c => c.Nombre).HasMaxLength(50).IsRequired();
16: }
17: }
18: //DbContext
19: public class ContextCliente : DbContext
20: {
21: protected override void OnModelCreating(DbModelBuilder modelBuilder)
22: {
23: modelBuilder.Configurations.Add(new ClienteEntityConfiguration());
24:
25: }
26: }
Sí creamos un cliente y posteriormente nos vamos a la bb.dd y ejecutamos “select * from EdmMetadata” el resultado de la consulta es el siguiente.
La forma sencilla de provocar “InvalidOperationException” es cambiar en la clase ClienteEntityConfiguration esta línea
this.Property(c => c.Nombre).HasMaxLength(50).IsRequired();
Por esta otra
this.Property(c => c.Nombre).HasMaxLength(100).IsRequired();
A partir de ese momento nuestro Hash se convierte en lo siguiente.
“0F9CDC01973A8E83D521518166B9F8C37E8D533704219CF1F2DC02B83AEE0F77”
El cual podemos obtener con el siguiente código.
1: using (ContextCliente ct = new ContextCliente())
2: {
3: var model = EdmMetadata.TryGetModelHash(ct);
4: }
A simple vista vemos que hay una diferencia y es por eso el motivo de recibir “InvalidOpertationException”.
Lógicamente se pueden definir estrategias que para mi son peores que recibir la exception.
1. Eliminar la bb.dd cada vez que se hace un cambio, con algo como lo siguiente.
1: System.Data.Entity.Database.SetInitializer(new DropCreateDatabaseIfModelChanges<ContextCliente>());
2. Eliminar de las conventions “IncludeMetadataConvention” que aún es peor solución puesto que tarde o temprano cantará:).
Como se puede observar en versión 4.2 bastantes cosas desde mi punto de vista mal pensadas, pero bueno eso es parte del pasado y lo mejor como siempre es ser positivos:)
Ahora vamos a demostrar como ese Hash se genera a partir de un Xml y de esa forma eliminar parte del mito “CodeFisrt no utiliza Xml”. Señores síiii:)
Vamos a dejar nuestro ContextCliente de la siguiente forma y utilizar un montón de Reflection para ver lo que se convierte a Hash.
1: public class ContextCliente : DbContext
2: {
3: protected override void OnModelCreating(DbModelBuilder modelBuilder)
4: {
5:
6: //Agregamos ClienteEntityConfigurtarion
7: // a modelBuilder.Configuration
8: modelBuilder.Configurations.Add(new ClienteEntityConfiguration());
9:
10: //Obtenemos un DbModel
11: var Model = modelBuilder.Build(this.Database.Connection);
12:
13: //Obtenemos una clase internal
14: //Llamada "DbDatabaseMetadataExtensions"
15: Type t = (from b in typeof(DbContext).Assembly.GetTypes()
16: where b.Name == "DbDatabaseMetadataExtensions"
17: select b).FirstOrDefault();
18:
19: //De esa clase el Metodo ToStoreItemCollection
20: var Method = (from b in t.GetMethods(BindingFlags.Static | BindingFlags.Public)
21: where b.Name == "ToStoreItemCollection"
22: select b).FirstOrDefault();
23:
24: //Obtenemos del DbModel la propiedad "DatabaseMapping"
25: var DataBaseMappingProperty = Model.GetType().GetProperty("DatabaseMapping", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
26:
27: var DataBaseMapping = DataBaseMappingProperty.GetValue(Model, null);
28: //Obtenemos de DataBaseMapping la propiedad Database
29: var DataBaseMetadaProperty = DataBaseMapping.GetType().GetProperty("Database",BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
30:
31: var DataBase = DataBaseMetadaProperty.GetValue(DataBaseMapping, null);
32:
33: Action<string> Action = (xml) =>
34: {
35: Console.WriteLine(ComputeSha256Hash(xml));
36: };
37: //Invocamos al metodoToStoreItemCollection
38: Method.Invoke(DataBase, new object[] { DataBase, Action });
39: Console.ReadLine();
40:
41: }
42:
43: static string ComputeSha256Hash(string input)
44: {
45: byte[] buffer = GetSha256HashAlgorithm().ComputeHash(Encoding.ASCII.GetBytes(input));
46: StringBuilder builder = new StringBuilder(buffer.Length * 2);
47: foreach (byte num in buffer)
48: {
49: builder.Append(num.ToString("X2", CultureInfo.InvariantCulture));
50: }
51: return builder.ToString();
52: }
53:
54:
55:
56: static SHA256 GetSha256HashAlgorithm()
57: {
58: try
59: {
60: return new SHA256CryptoServiceProvider();
61: }
62: catch (PlatformNotSupportedException)
63: {
64: return new SHA256Managed();
65: }
66: }
67: }
Solo os invito a que pongáis un breakPoint dentro del Action<string>
Y veáis el valor del parametro “xml”. Bueno para los más gandulones os lo muestro yo:)
1: <?xml version="1.0" encoding="utf-16"?>
2: <Schema Namespace="CodeFirstDatabaseSchema" Provider="System.Data.SqlClient" ProviderManifestToken="2008" Alias="Self" xmlns="http://schemas.microsoft.com/ado/2009/02/edm/ssdl">
3: <EntityType Name="Cliente">
4: <Key>
5: <PropertyRef Name="Id" />
6: </Key>
7: <Property Name="Id" Type="int" StoreGeneratedPattern="Identity" Nullable="false" />
8: <Property Name="Nombre" Type="nvarchar" MaxLength="100" Nullable="false" />
9: </EntityType>
10: <EntityType Name="EdmMetadata">
11: <Key>
12: <PropertyRef Name="Id" />
13: </Key>
14: <Property Name="Id" Type="int" StoreGeneratedPattern="Identity" Nullable="false" />
15: <Property Name="ModelHash" Type="nvarchar(max)" Nullable="true" />
16: </EntityType>
17: <EntityContainer Name="CodeFirstDatabase">
18: <EntitySet Name="Cliente" EntityType="Self.Cliente" Schema="dbo" Table="Clientes" />
19: <EntitySet Name="EdmMetadata" EntityType="Self.EdmMetadata" Schema="dbo" Table="EdmMetadata" />
20: </EntityContainer>
21: </Schema>
Convencidos que el Hash de la versión 4.2 de CodeFirst se genera a partir del xml del Modelo:).
Bueno publico y voy con el segundo en CodeFirst 4.3 no es Hash sino un edmx comprimido.
En el anterior post Desmitificando CodeFirst(1/2) , me he centrado en la versión 4.2 de Entity Framework