En los dos entradas anteriores sobre convenciones, I y II, se han detallado tanto el significado como la jerarquía de las mismas, así como el funcionamiento real y su relación con Data Annotations. A lo largo de esta entrada trataremos de ver como crear una nueva convención, la cual además de fines formativos tenga validez en la realidad.
Los antecedentes
El mapeo por defecto de una relación uno a muchos en esta CTP tiene asociada una convención por la cual este tipo de relaciones tienen establecida la restricción de borrado en cascada. Así, si por ejemplo, partiéramos del siguiente código, podríamos ver en la base de datos la opción OnDelete Cascade para la relación entre Customer y Order.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<span class="kwrd">public</span> <span class="kwrd">class</span> Customer { <span class="kwrd">public</span> <span class="kwrd">int</span> CustomerId { 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">virtual</span> ICollection<Order> Orders { get; set; } } <span class="kwrd">public</span> <span class="kwrd">class</span> Order { <span class="kwrd">public</span> <span class="kwrd">int</span> OrderId { get; set; } <span class="kwrd">public</span> <span class="kwrd">decimal</span> TotalOrder { get; set; } <span class="kwrd">public</span> <span class="kwrd">int</span> CustomerId { get; set; } <span class="kwrd">public</span> <span class="kwrd">virtual</span> Customer Customer {get;set;} } |
1 |
|
Por supuesto, esta convención de borrado en cascada para las relaciones uno a muchos puede ser eliminada por código quitando la misma de la lista de convenciones como podemos ver a continuación:
El problema de esta solución es que requiere de un código adicional. Además, al quitar la convención estamos eliminando esta característica de todas las posibles relaciones.. Nuestro objetivo será el de crear un atributo de Data Annotations, similar al MaxLenght que nos sirva de convención y con el cual podrámos establecer si una propiedad de navegación llevará asociada una restricción en cascada o no.
1 2 3 4 5 |
<span class="kwrd">protected</span> <span class="kwrd">override</span> <span class="kwrd">void</span> OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder) { modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>(); <span class="kwrd">base</span>.OnModelCreating(modelBuilder); } |
La solución
El primer paso que realizaremos será el de definir nuestro atributo, recuerde de la anterior entrada que todas las convenciones de tipo AttributeConfigurationConvention<,,> llevan asociadas un atributo, y como, además, si necesitaramos un atributo distinto a los definidos en Data Annotations podemos crear el nuestro.
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 |
<span class="kwrd">namespace</span> System.ComponentModel.DataAnnotations { <span class="kwrd">public</span> <span class="kwrd">class</span> CascadeDeleteAttribute : Attribute { <span class="preproc">#region</span> Properties <span class="kwrd">bool</span> _cascadeDeleteAction; <span class="rem">/// <summary></span> <span class="rem">/// Get cacade delete action value</span> <span class="rem">/// </summary></span> <span class="kwrd">public</span> <span class="kwrd">bool</span> CascadeDeleteAction { get { <span class="kwrd">return</span> _cascadeDeleteAction; } } <span class="preproc">#endregion</span> <span class="preproc">#region</span> Constructor <span class="rem">/// <summary></span> <span class="rem">/// Create a new instance if CascadeDeleteAttribute</span> <span class="rem">/// </summary></span> <span class="rem">/// <param name="cascadeDeleteAction">True if cacade delete action is preferred option</param></span> <span class="kwrd">public</span> CascadeDeleteAttribute(<span class="kwrd">bool</span> cascadeDeleteAction) { _cascadeDeleteAction = cascadeDeleteAction; } <span class="preproc">#endregion</span> } } |
Una vez creado el atributo, pasaremos a crear nuestra convención. Puesto que la misma queremos que aplique a las relaciones tendremos que seleccionar PropertyInfo y NavigationPropertyConfiguration como los dos primeros elementos de nuestro genérico. A continuación podemos ver el código de nuestra nueva convención:
1 2 3 4 5 6 7 8 9 |
<span class="kwrd">public</span> <span class="kwrd">class</span> DeleteCascadeActionAttributeConvention : AttributeConfigurationConvention<PropertyInfo, NavigationPropertyConfiguration, CascadeDeleteAttribute> { <span class="kwrd">public</span> <span class="kwrd">override</span> <span class="kwrd">void</span> Apply(PropertyInfo memberInfo, NavigationPropertyConfiguration configuration, CascadeDeleteAttribute attribute) { configuration.DeleteAction = (attribute.CascadeDeleteAction) ? EdmOperationAction.Cascade : EdmOperationAction.None; } } |
1 |
|
Por supuesto, esta convención hay que agregarla a la lista de convenciones:
1 2 3 4 5 6 |
<span class="kwrd">protected</span> <span class="kwrd">override</span> <span class="kwrd">void</span> OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder) { modelBuilder.Conventions.Add(<span class="kwrd">new</span> DeleteCascadeActionAttributeConvention()); <span class="kwrd">base</span>.OnModelCreating(modelBuilder); } |
Ahora, para utilizarla simplemente tenemos que decorar nuestras propiedades de navegación como sigue:
1 2 3 4 5 6 7 8 9 |
<span class="kwrd">public</span> <span class="kwrd">class</span> Order { <span class="kwrd">public</span> <span class="kwrd">int</span> OrderId { get; set; } <span class="kwrd">public</span> <span class="kwrd">decimal</span> TotalOrder { get; set; } <span class="kwrd">public</span> <span class="kwrd">int</span> CustomerId { get; set; } [CascadeDelete(<span class="kwrd">false</span>)] <span class="kwrd">public</span> <span class="kwrd">virtual</span> Customer Customer {get;set;} } |
Espero que os resulte de interés
Saludos
Unai
En el último post sobre convenciones miramos con usar las características de plugable conventions en
Muy buenos Post, Unai, la verdad es que se agradece este tipo de material en el que se va al grano y se evita toda la literatura.
Estoy probando la Beta 2 del ODAC de Oracle para Entity Framework y me pregunto si soportara Code First, has probado algo de esto con la Beta?