Convenciones basadas en un modelo
Nota:
Solo EF6 y versiones posteriores: las características, las API, etc. que se tratan en esta página se han incluido a partir de Entity Framework 6. Si usa una versión anterior, no se aplica parte o la totalidad de la información.
Las convenciones basadas en un modelo son un método avanzado de configuración de un modelo basado en una convención. Para la mayoría de los escenarios, se debe usar la API de convención de Code First personalizada en DbModelBuilder. Se recomienda comprender la API DbModelBuilder para las convenciones antes de usar convenciones basadas en un modelo.
Las convenciones basadas en un modelo permiten la creación de convenciones que afectan a propiedades y tablas que no se pueden configurar mediante convenciones estándar. Ejemplos de ello son las columnas discriminantes en los modelos de tabla por jerarquía y las columnas de asociación independiente.
Crear una convención
El primer paso para crear una convención basada en un modelo consiste en elegir cuándo se debe aplicar la convención en la canalización al modelo. Hay dos tipos de convenciones de modelo, conceptual (espacio C) y almacén (espacio S). Se aplica una convención de espacio C al modelo que compila la aplicación, mientras que se aplica una convención de espacio S a la versión del modelo que representa la base de datos y controla aspectos como el nombre de las columnas generadas automáticamente.
Una convención de modelo es una clase que se extiende desde IConceptualModelConvention o IStoreModelConvention. Estas interfaces aceptan un tipo genérico que puede ser de tipo MetadataItem, que se usa para filtrar el tipo de datos al que se aplica la convención.
Agregar una convención
Las convenciones de modelo se agregan de la misma manera que las clases de convenciones normales. En el método OnModelCreating, agregue la convención a la lista de convenciones de un modelo.
using System.Data.Entity;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.ModelConfiguration.Conventions;
public class BlogContext : DbContext
{
public DbSet<Post> Posts { get; set; }
public DbSet<Comment> Comments { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Add<MyModelBasedConvention>();
}
}
También se puede agregar una convención en relación con otra convención mediante los métodos Conventions.AddBefore<> o Conventions.AddAfter<>. Para obtener más información sobre las convenciones que Entity Framework aplica, consulte la sección de notas.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.AddAfter<IdKeyDiscoveryConvention>(new MyModelBasedConvention());
}
Ejemplo: convención del modelo discriminador
Cambiar el nombre de las columnas generadas por EF es un ejemplo de algo que no se puede hacer con las otras API de convenciones. Esta es una situación en la que el uso de convenciones de modelo es la única opción.
Un ejemplo de cómo usar una convención basada en un modelo para configurar las columnas generadas es personalizar la forma en que se denominan las columnas discriminadoras. A continuación se muestra un ejemplo de una convención basada en un modelo simple que cambia el nombre de todas las columnas del modelo denominadas "Discriminador" a "EntityType". Esto incluye columnas que el desarrollador simplemente llamó "Discriminador". Dado que la columna "Discriminador" es una columna generada, debe ejecutarse en el espacio S.
using System.Data.Entity;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.ModelConfiguration.Conventions;
class DiscriminatorRenamingConvention : IStoreModelConvention<EdmProperty>
{
public void Apply(EdmProperty property, DbModel model)
{
if (property.Name == "Discriminator")
{
property.Name = "EntityType";
}
}
}
Ejemplo: convención general de cambio de nombre de IA
Otro ejemplo más complicado de convenciones basadas en un modelo en acción es configurar la forma en que se denominan asociaciones independientes (IA). Se trata de una situación en la que las convenciones de modelo son aplicables porque EF genera las IA y no están presentes en el modelo al que puede acceder la API DbModelBuilder.
Cuando EF genera una IA, crea una columna denominada EntityType_KeyName. Por ejemplo, para una asociación denominada Customer con una columna de clave denominada CustomerId, generaría una columna denominada Customer_CustomerId. La siguiente convención quita el carácter "_" del nombre de columna que se genera para la IA.
using System.Data.Entity;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.ModelConfiguration.Conventions;
// Provides a convention for fixing the independent association (IA) foreign key column names.
public class ForeignKeyNamingConvention : IStoreModelConvention<AssociationType>
{
public void Apply(AssociationType association, DbModel model)
{
// Identify ForeignKey properties (including IAs)
if (association.IsForeignKey)
{
// rename FK columns
var constraint = association.Constraint;
if (DoPropertiesHaveDefaultNames(constraint.FromProperties, constraint.ToRole.Name, constraint.ToProperties))
{
NormalizeForeignKeyProperties(constraint.FromProperties);
}
if (DoPropertiesHaveDefaultNames(constraint.ToProperties, constraint.FromRole.Name, constraint.FromProperties))
{
NormalizeForeignKeyProperties(constraint.ToProperties);
}
}
}
private bool DoPropertiesHaveDefaultNames(ReadOnlyMetadataCollection<EdmProperty> properties, string roleName, ReadOnlyMetadataCollection<EdmProperty> otherEndProperties)
{
if (properties.Count != otherEndProperties.Count)
{
return false;
}
for (int i = 0; i < properties.Count; ++i)
{
if (!properties[i].Name.EndsWith("_" + otherEndProperties[i].Name))
{
return false;
}
}
return true;
}
private void NormalizeForeignKeyProperties(ReadOnlyMetadataCollection<EdmProperty> properties)
{
for (int i = 0; i < properties.Count; ++i)
{
int underscoreIndex = properties[i].Name.IndexOf('_');
if (underscoreIndex > 0)
{
properties[i].Name = properties[i].Name.Remove(underscoreIndex, 1);
}
}
}
}
Extender convenciones existentes
Si necesita escribir una convención que sea similar a una de las convenciones que Entity Framework ya aplica a su modelo, siempre puede extender esa convención para evitar tener que volver a escribirla desde cero. Un ejemplo de esto es reemplazar la convención de coincidencia de id. existente por una personalizada. Una ventaja adicional para invalidar la convención de clave es que solo se llamará al método invalidado si no hay ninguna clave ya detectada o configurada explícitamente. A continuación, se muestra una lista de las convenciones que usa Entity Framework: http://msdn.microsoft.com/library/system.data.entity.modelconfiguration.conventions.aspx.
using System.Data.Entity;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.ModelConfiguration.Conventions;
using System.Linq;
// Convention to detect primary key properties.
// Recognized naming patterns in order of precedence are:
// 1. 'Key'
// 2. [type name]Key
// Primary key detection is case insensitive.
public class CustomKeyDiscoveryConvention : KeyDiscoveryConvention
{
private const string Id = "Key";
protected override IEnumerable<EdmProperty> MatchKeyProperty(
EntityType entityType, IEnumerable<EdmProperty> primitiveProperties)
{
Debug.Assert(entityType != null);
Debug.Assert(primitiveProperties != null);
var matches = primitiveProperties
.Where(p => Id.Equals(p.Name, StringComparison.OrdinalIgnoreCase));
if (!matches.Any())
{
matches = primitiveProperties
.Where(p => (entityType.Name + Id).Equals(p.Name, StringComparison.OrdinalIgnoreCase));
}
// If the number of matches is more than one, then multiple properties matched differing only by
// case--for example, "Key" and "key".
if (matches.Count() > 1)
{
throw new InvalidOperationException("Multiple properties match the key convention");
}
return matches;
}
}
Después, es necesario agregar nuestra nueva convención antes de la convención de clave existente. Después de agregar CustomKeyDiscoveryConvention, podemos quitar IdKeyDiscoveryConvention. Si no quitamos la convención idKeyDiscoveryConvention existente, esta seguirá teniendo prioridad sobre la convención de detección de id., ya que se ejecuta primero, pero en el caso de que no se encuentre ninguna propiedad "key", se ejecutará la convención "id". Este comportamiento se debe a que cada convención ve el modelo actualizado por la convención anterior (en lugar de operar sobre él de forma independiente y combinarse todo), de modo que si una convención anterior actualiza el nombre de la columna para que coincida con lo que interesa en la convención personalizada (antes de eso, el nombre no interesaba), se aplicará a esa columna.
public class BlogContext : DbContext
{
public DbSet<Post> Posts { get; set; }
public DbSet<Comment> Comments { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.AddBefore<IdKeyDiscoveryConvention>(new CustomKeyDiscoveryConvention());
modelBuilder.Conventions.Remove<IdKeyDiscoveryConvention>();
}
}
Notas
Encontrará una lista de convenciones que actualmente aplica Entity Framework en la documentación de MSDN: http://msdn.microsoft.com/library/system.data.entity.modelconfiguration.conventions.aspx. Esta lista se extrae directamente del código fuente. El código fuente de Entity Framework 6 está disponible en GitHub y muchas de las convenciones usadas por Entity Framework son buenos puntos de partida para las convenciones basadas en un modelo personalizado.