Convenções baseadas em modelo
Observação
EF6 em diante apenas: os recursos, as APIs etc. discutidos nessa página foram introduzidos no Entity Framework 6. Se você estiver usando uma versão anterior, algumas ou todas as informações não se aplicarão.
As convenções baseadas em modelo são um método avançado de configuração de modelo baseado em convenção. Para a maioria dos cenários, a API de Convenção de Primeiro Código Personalizado no DbModelBuilder deve ser usada. É recomendável entender a API DbModelBuilder para convenções antes de usar convenções baseadas em modelo.
As convenções baseadas em modelo permitem a criação de convenções que afetam propriedades e tabelas que não são configuráveis por meio de convenções padrão. Exemplos deles são colunas discriminatórias em modelos de tabela por hierarquia e colunas de Associação Independente.
Como criar uma convenção
A primeira etapa na criação de uma convenção baseada em modelo é escolher quando, no pipeline, a convenção precisa ser aplicada ao modelo. Há dois tipos de convenções de modelo, Conceitual (C-Space) e Store (S-Space). Uma convenção C-Space é aplicada ao modelo criado pelo aplicativo, enquanto uma convenção S-Space é aplicada à versão do modelo que representa o banco de dados e controla itens como colunas geradas automaticamente são nomeadas.
Uma convenção de modelo é uma classe que se estende de IConceptualModelConvention ou IStoreModelConvention. Essas interfaces aceitam um tipo genérico que pode ser do tipo MetadataItem, que é usado para filtrar o tipo de dados ao qual a convenção se aplica.
Como adicionar uma convenção
As convenções de modelo são adicionadas da mesma forma que as classes de convenções regulares. No método OnModelCreating, adicione a convenção à lista de convenções de um 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>();
}
}
Uma convenção também pode ser adicionada em relação a outra convenção usando os métodos Conventions.AddBefore<> ou Conventions.AddAfter<>. Para obter mais informações sobre as convenções que o Entity Framework aplica, consulte a seção de anotações.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.AddAfter<IdKeyDiscoveryConvention>(new MyModelBasedConvention());
}
Exemplo: Convenção de Modelo Discriminatório
Renomear colunas geradas pelo EF é um exemplo de algo que você não pode fazer com as outras APIs de convenções. Essa é uma situação em que usar convenções de modelo é sua única opção.
Um exemplo de como usar uma convenção baseada em modelo para configurar colunas geradas é personalizar a forma como colunas discriminatórias são nomeadas. Veja abaixo um exemplo de uma convenção baseada em modelo simples que renomeia cada coluna no modelo chamado "Discriminador" como "EntityType". Isso inclui colunas que o desenvolvedor simplesmente chamou de "Discriminatória". Como a coluna "Discriminador" é uma coluna gerada, isso precisa ser executado no S-Space.
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";
}
}
}
Exemplo: Convenção Geral de Renomeação de IA
Outro exemplo mais complicado de convenções baseadas em modelo em ação é configurar a forma como as Associações Independentes (IAs) são nomeadas. Essa é uma situação em que as convenções de modelo são aplicáveis porque as IAs são geradas pelo EF e não estão presentes no modelo que a API DbModelBuilder pode acessar.
Quando o EF gera uma IA, ele cria uma coluna chamada EntityType_KeyName. Por exemplo, para uma associação chamada Customer com uma coluna de chave chamada CustomerId, ela geraria uma coluna chamada Customer_CustomerId. A convenção a seguir tira o caractere '_' do nome da coluna que é gerado para a 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);
}
}
}
}
Como estender convenções existentes
Se você precisar escrever uma convenção semelhante a uma das convenções que o Entity Framework já aplica ao seu modelo, você sempre poderá estender essa convenção para evitar ter que reescrevê-la do zero. Um exemplo disso é substituir a convenção de correspondência de ID existente por uma personalizada. Um benefício adicional para substituir a convenção de chave é que o método substituído será chamado somente se não houver nenhuma chave já detectada ou configurada explicitamente. Uma lista de convenções usadas pelo Entity Framework está disponível aqui: 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;
}
}
Em seguida, precisamos adicionar nossa nova convenção antes da convenção-chave existente. Depois de adicionarmos o CustomKeyDiscoveryConvention, podemos remover a IdKeyDiscoveryConvention. Se não removermos a IdKeyDiscoveryConvention existente, essa convenção ainda terá precedência sobre a convenção de descoberta de Id, pois ela é executada primeiro, mas no caso em que nenhuma propriedade "chave" for encontrada, a convenção "id" será executada. Vemos esse comportamento porque cada convenção vê o modelo como atualizado pela convenção anterior (em vez de operar nele de forma independente e tudo combinado) de modo que, por exemplo, uma convenção anterior atualizou um nome de coluna para corresponder algo de interesse à sua convenção personalizada (quando antes desse nome não era de interesse) então ela se aplicará a essa coluna.
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>();
}
}
Observações
Uma lista de convenções que atualmente são aplicadas pelo Entity Framework está disponível na documentação do MSDN aqui: http://msdn.microsoft.com/library/system.data.entity.modelconfiguration.conventions.aspx. Essa lista é extraída diretamente do nosso código-fonte. O código-fonte do Entity Framework 6 está disponível no GitHub e muitas das convenções usadas pelo Entity Framework são bons pontos de partida para convenções personalizadas baseadas em modelo.