Creo que son ya unas cuantas entradas las que llevo con EF 6 en este blog, y eso que aún no tenemos ni la primera CTP, pero a cada nueva build que hacen podemos ver nuevas e interesantes cosas que me gusta adelantarme a escribir.
De entre estas nuevas cosillas me gustaría destacar estos tres nuevos métodos que he podido contribuir en sus correspondientes pull request ( addrange, removerange, haschanges )
AddRange
Como seguramente os imaginaréis, este pequeño método nos permite agregar dentro de nuestros DbSet<T> y DbSet un conjunto de elementos de una única pasada, evitando así tener que hacer las n repetitivas llamadas al método Add. Aunque esto parezca trivial, y en realidad, fuera fácil de construir con un método extensor, la verdad es que tiene un pelín más de miga, puesto que algo que podíamos hacer era aprovechar este nuevo método para evitar la detección de cambios para cada uno de los elementos de la colección a incluir.
Nota:
Como seguramente sabréis, EF realiza un proceso de detección de cambios de forma automática ( si no hemos configurado lo contrario ) en ciertos procesos, como por ejemplo en el de agregar elementos al DbSet, por lo tanto, esto podría suponer una sobrecarga en ciertos escenarios con muchas entradas en el ObjectStateManager. Si quieres saber más sobre la detección de cambios en EF, entonces nada mejor que leer del mejor, en concreto, esta serie de Arthur Vickers.
Al final, una vez valorado esto y agregado este nuevo método en la superficie publica de DbSet<T> y DbSet, el código queda tan liviano como vemos a continuación en el método AddRange de nuestro InternalSet.
1 2 3 4 5 6 7 8 9 |
<span class="kwrd">public</span> <span class="kwrd">virtual</span> <span class="kwrd">void</span> AddRange(IEnumerable entities) { DebugCheck.NotNull(entities); InternalContext.DetectChanges(); ActOnSet( entity => InternalContext.ObjectContext.AddObject(EntitySetName, entity), EntityState.Added, entities, <span class="str">"Add"</span>); } |
RemoveRange
Bien, después de explicado lo anterior, este es de cajón, en realidad aplican los mimos argumentos con respecto a relajar la detección de cambios, por lo tanto, la implementación en nuestro InternalSet es similar a lo siguiente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<span class="kwrd">public</span> <span class="kwrd">virtual</span> <span class="kwrd">void</span> RemoveRange(IEnumerable entities) { DebugCheck.NotNull(entities); <span class="rem">// prevent "enumerator was changed" exception</span> <span class="rem">// if entities is syncronized with other elements</span> <span class="rem">// (e.g: local view from DbSet.Local.)</span> var copyOfEntities = entities .Cast<<span class="kwrd">object</span>>().ToList(); InternalContext.DetectChanges(); ActOnSet( entity => InternalContext.ObjectContext.DeleteObject(entity), EntityState.Deleted, copyOfEntities, <span class="str">"Delete"</span>); } |
1 |
  |
HasChanges
Este método es una pequeña ayuda para comprobar si nuestra unidad de trabajo tiene cambios pendientes, elementos agregados, borrados o modificados en la misma y no llevados aún a la base de datos. El mismo está situado en la clase DbChangeTracker y como podemos ver en el siguiente tests funcional lo tendremos accesible mediante la propiedad ChangeTracker de nuestros DbContext.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[Fact] <span class="kwrd">public</span> <span class="kwrd">void</span> HasChanges_return_true_if_context_have_entities_in_deleted_state() { <span class="kwrd">using</span> (var context = <span class="kwrd">new</span> SomeContext()) { var foo = <span class="kwrd">new</span> Foo() { Bar = <span class="str">"the foo"</span> }; context.Foos.Attach(foo); context.Entry(foo).State = EntityState.Deleted; Assert.True(context.ChangeTracker.HasChanges()); } } |
La implementación es realmente sencilla puesto que internamente ya se dispone de un método para comprobar el número de entradas en el ObjectStateManager.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<span class="rem">/// <summary></span> <span class="rem">/// Check if the <see cref="DbContext" /> contain changes, added, modified or deleted entities and relationships of POCO entities. </span> <span class="rem">/// This method call first to detect changes on the underlying <see cref="DbContext" />.</span> <span class="rem">/// </summary></span> <span class="rem">/// <returns>True if underlying <see cref="DbContext" /> have changes, else false.</returns></span> <span class="kwrd">public</span> <span class="kwrd">bool</span> HasChanges() { _internalContext.DetectChanges(); var objectStateManager = _internalContext .ObjectContext.ObjectStateManager; DebugCheck.NotNull(objectStateManager); var entriesAffected = objectStateManager.GetObjectStateEntriesCount(EntityState.Added | EntityState.Deleted | EntityState.Modified); <span class="kwrd">return</span> entriesAffected > 0; } |
Para terminar esta pequeña entrada me gustaría hablar de otra “gran” novedad que tenemos en las últimas builds de EF, novedad introducida por el propio equipo, en concreto por Andrew Peters (@anpete), que consiste en la posibilidad de hacer el scaffolding de las entidades mapeadas con procedimientos almacenados de forma automática. Lo mejor para explicarlo es ver un pequeño ejemplo, para ello partiremos del siguiente modelo y su correspondiente configuración:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<span class="kwrd">public</span> <span class="kwrd">class</span> Blog { <span class="kwrd">public</span> <span class="kwrd">int</span> Id { get; set; } <span class="kwrd">public</span> <span class="kwrd">string</span> FirstName { get; set; } <span class="kwrd">public</span> <span class="kwrd">string</span> LastName { get; set; } } <span class="kwrd">public</span> <span class="kwrd">class</span> BlogContext :DbContext { <span class="kwrd">public</span> DbSet<Blog> Blogs { get; set; } <span class="kwrd">protected</span> <span class="kwrd">override</span> <span class="kwrd">void</span> OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Configurations.AddFromAssembly(<span class="kwrd">typeof</span>(BlogContext).Assembly); } } <span class="kwrd">internal</span> <span class="kwrd">class</span> BlogConfiguration :EntityTypeConfiguration<Blog> { <span class="kwrd">private</span> BlogConfiguration() { <span class="kwrd">this</span>.MapToStoredProcedures(); } } |
Nada nuevo, o por lo menos de lo que no hayamos hablado ya, una entidad mapeada con procedimientos almacenados por medió del método MapToStoreProcedures, aplicando convenciones al no estar “configurado”. Lo nuevo en las últimas builds, le recomiendo usar los paquetes de NuGet del feed de ASP.NET Stack nightly builds, está en que las migraciones son capaces de hacer el scaffolding de estos procedimientos almacenados, por lo tanto, si ejecutaramos la creación de una migración, tal cual sigue:
1 2 3 |
PM> enable-migrations Checking <span class="kwrd">if</span> the context targets an existing database... Code First Migrations enabled <span class="kwrd">for</span> project MapToStoreProcedures. |
1 2 3 |
PM> Add-Migration InitialScaffolding Scaffolding migration <span class="str">'InitialScaffolding'</span>. The Designer Code <span class="kwrd">for</span> <span class="kwrd">this</span> migration file includes a snapshot of your current Code First model. This snapshot <span class="kwrd">is</span> used to calculate the changes to your model when you scaffold the next migration. If you make additional changes to your model that you want to include <span class="kwrd">in</span> <span class="kwrd">this</span> migration, then you can re-scaffold it by running <span class="str">'Add-Migration InitialScaffolding'</span> again. |
Veríamos un resultado similar a lo siguiente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
<span class="kwrd">namespace</span> MapToStoreProcedures.Migrations { <span class="kwrd">using</span> System; <span class="kwrd">using</span> System.Data.Entity.Migrations; <span class="kwrd">public</span> <span class="kwrd">partial</span> <span class="kwrd">class</span> InitialScaffolding : DbMigration { <span class="kwrd">public</span> <span class="kwrd">override</span> <span class="kwrd">void</span> Up() { CreateTable( <span class="str">"dbo.Blogs"</span>, c => <span class="kwrd">new</span> { Id = c.Int(nullable: <span class="kwrd">false</span>, identity: <span class="kwrd">true</span>), FirstName = c.String(), LastName = c.String(), }) .PrimaryKey(t => t.Id); CreateStoredProcedure( <span class="str">"Blog_Insert"</span>, p => <span class="kwrd">new</span> { FirstName = p.String(), LastName = p.String(), }, body: <span class="str">@"INSERT [dbo].[Blogs]([FirstName], [LastName]) VALUES (@FirstName, @LastName) DECLARE @Id int SELECT @Id = [Id] FROM [dbo].[Blogs] WHERE @@ROWCOUNT > 0 and [Id] = scope_identity() SELECT t0.[Id] FROM [dbo].[Blogs] as t0 WHERE @@ROWCOUNT > 0 and t0.[Id] = @Id"</span> ); CreateStoredProcedure( <span class="str">"Blog_Update"</span>, p => <span class="kwrd">new</span> { Id = p.Int(), FirstName = p.String(), LastName = p.String(), }, body: <span class="str">@"UPDATE [dbo].[Blogs] SET [FirstName] = @FirstName, [LastName] = @LastName WHERE ([Id] = @Id)"</span> ); CreateStoredProcedure( <span class="str">"Blog_Delete"</span>, p => <span class="kwrd">new</span> { Id = p.Int(), }, body: <span class="str">@"DELETE [dbo].[Blogs] WHERE ([Id] = @Id)"</span> ); } <span class="kwrd">public</span> <span class="kwrd">override</span> <span class="kwrd">void</span> Down() { DropStoredProcedure(<span class="str">"Blog_Delete"</span>); DropStoredProcedure(<span class="str">"Blog_Update"</span>); DropStoredProcedure(<span class="str">"Blog_Insert"</span>); DropTable(<span class="str">"dbo.Blogs"</span>); } } } |
1 |
  |
¿Qué os parece?
Saludos
Unai
1 |
  |