Condividi tramite


Convenzioni basate su modello

Nota

Solo EF6 e versioni successive: funzionalità, API e altri argomenti discussi in questa pagina sono stati introdotti in Entity Framework 6. Se si usa una versione precedente, le informazioni qui riportate, o parte di esse, non sono applicabili.

Le convenzioni basate su modello sono un metodo avanzato di configurazione del modello basato su convenzioni. Per la maggior parte degli scenari è consigliabile usare l'API Code First Convention personalizzata in DbModelBuilder . È consigliabile comprendere l'API DbModelBuilder per le convenzioni prima di usare le convenzioni basate su modello.

Le convenzioni basate su modello consentono la creazione di convenzioni che influiscono su proprietà e tabelle che non sono configurabili tramite convenzioni standard. Esempi di queste sono colonne discriminatorie nella tabella per ogni modello di gerarchia e colonne Di associazione indipendente.

Creazione di una convenzione

Il primo passaggio per la creazione di una convenzione basata su modello consiste nel scegliere quando nella pipeline la convenzione deve essere applicata al modello. Esistono due tipi di convenzioni di modello, Conceptual (C-Space) e Store (S-Space). Una convenzione C-Space viene applicata al modello compilato dall'applicazione, mentre una convenzione S-Space viene applicata alla versione del modello che rappresenta il database e controlla elementi quali la modalità di denominazione delle colonne generate automaticamente.

Una convenzione di modello è una classe che si estende da IConceptualModelConvention o IStoreModelConvention. Queste interfacce accettano entrambi un tipo generico che può essere di tipo MetadataItem che viene usato per filtrare il tipo di dati a cui si applica la convenzione.

Aggiunta di una convenzione

Le convenzioni dei modelli vengono aggiunte allo stesso modo delle normali classi di convenzioni. Nel metodo OnModelCreating aggiungere la convenzione all'elenco di convenzioni per un modello.

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>();  
    }  
}

È anche possibile aggiungere una convenzione in relazione a un'altra convenzione usando i metodi Conventions.AddBefore<> o Conventions.AddAfter<> . Per altre informazioni sulle convenzioni che Entity Framework applica, vedere la sezione note.

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Conventions.AddAfter<IdKeyDiscoveryConvention>(new MyModelBasedConvention());
}

Esempio: Convenzione del modello discriminatorio

La ridenominazione delle colonne generate da EF è un esempio di un'operazione che non è possibile eseguire con le altre API delle convenzioni. Si tratta di una situazione in cui l'uso delle convenzioni del modello è l'unica opzione.

Un esempio di come usare una convenzione basata su modello per configurare le colonne generate consiste nel personalizzare il modo in cui vengono denominate le colonne discriminatorie. Di seguito è riportato un esempio di una convenzione semplice basata su modello che rinomina ogni colonna del modello denominata "Discriminator" in "EntityType". Sono incluse le colonne che lo sviluppatore ha semplicemente denominato "Discriminator". Poiché la colonna "Discriminator" è una colonna generata, questa operazione deve essere eseguita nello spazio 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";  
        }  
    }  
}

Esempio: Convenzione di ridenominazione IA generale

Un altro esempio più complesso di convenzioni basate su modelli in azione consiste nel configurare la modalità di denominazione delle associazioni indipendenti . Si tratta di una situazione in cui le convenzioni del modello sono applicabili perché le interfacce IA vengono generate da ENTITY e non sono presenti nel modello a cui l'API DbModelBuilder può accedere.

Quando EF genera un IA, crea una colonna denominata EntityType_KeyName. Ad esempio, per un'associazione denominata Customer con una colonna chiave denominata CustomerId, verrà generata una colonna denominata Customer_CustomerId. La convenzione seguente rimuove il carattere '_' dal nome della colonna generato per L'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);
            }                 
        }
    }
}

Estensione delle convenzioni esistenti

Se è necessario scrivere una convenzione simile a una delle convenzioni già applicabili a Entity Framework al modello, è sempre possibile estendere tale convenzione per evitare di dover riscriverla da zero. Un esempio consiste nel sostituire la convenzione di corrispondenza ID esistente con una convenzione personalizzata. Un ulteriore vantaggio per eseguire l'override della convenzione di chiave consiste nel fatto che il metodo sottoposto a override verrà chiamato solo se non è già stata rilevata o configurata in modo esplicito alcuna chiave. Un elenco di convenzioni usate da Entity Framework è disponibile qui: 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;
    }
}

È quindi necessario aggiungere la nuova convenzione prima della convenzione chiave esistente. Dopo aver aggiunto CustomKeyDiscoveryConvention, è possibile rimuovere IdKeyDiscoveryConvention. Se non è stata rimossa la convenzione IdKeyDiscoveryConvention esistente, questa convenzione avrà comunque la precedenza sulla convenzione di individuazione id perché viene eseguita per la prima volta, ma nel caso in cui non venga trovata alcuna proprietà "chiave", verrà eseguita la convenzione "id". Questo comportamento viene visualizzato perché ogni convenzione vede il modello come aggiornato dalla convenzione precedente (anziché operare su di esso in modo indipendente e tutti combinati) in modo che, se ad esempio, una convenzione precedente ha aggiornato un nome di colonna in modo che corrisponda a qualcosa di interessante alla convenzione personalizzata (quando prima che il nome non fosse di interesse), verrà applicata a tale colonna.

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>();
    }
}

Note

Un elenco di convenzioni attualmente applicate da Entity Framework è disponibile nella documentazione MSDN qui: http://msdn.microsoft.com/library/system.data.entity.modelconfiguration.conventions.aspx. Questo elenco viene estratto direttamente dal codice sorgente. Il codice sorgente per Entity Framework 6 è disponibile in GitHub e molte delle convenzioni usate da Entity Framework sono ottimi punti di partenza per le convenzioni basate su modelli personalizzati.