Freigeben über


Modellbasierte Konventionen

Hinweis

Nur EF6 und höher: Die Features, APIs usw., die auf dieser Seite erläutert werden, wurden in Entity Framework 6 eingeführt. Wenn Sie eine frühere Version verwenden, gelten manche Informationen nicht.

Modellbasierte Konventionen sind eine erweiterte Methode der auf Konventionen basierten Modellkonfiguration. In den meisten Szenarien sollte die benutzerdefinierte Code First-Konventionen-API für DbModelBuilder verwendet werden. Ein Verständnis der DbModelBuilder-API für Konventionen ist empfehlenswert, bevor modellbasierte Konventionen verwendet werden.

Modellbasierte Konventionen ermöglichen das Erstellen von Konventionen, die sich auf Eigenschaften und Tabellen auswirken, die nicht über Standardkonventionen konfigurierbar sind. Beispiele hierfür sind Diskriminatorspalten in „Tabelle pro Hierarchie“-Modellen und Spalten für unabhängige Zuordnungen.

Erstellen einer Konvention

Der erste Schritt beim Erstellen einer modellbasierten Konvention ist die Wahl, wann in der Pipeline die Konvention auf das Modell angewendet werden muss. Es gibt zwei Arten von Modellkonventionen, Konzept (C-Space) und Speicher (S-Space). Eine C-Space-Konvention wird auf das Modell angewendet, das von der Anwendung erstellt wird, während eine S-Space-Konvention auf die Version des Modells angewendet wird, welche die Datenbank darstellt und Dinge steuert, z. B. wie automatisch generierte Spalten benannt werden.

Eine Modellkonvention ist eine Klasse, die entweder von IConceptualModelConvention oder IStoreModelConvention erweitert wird. Diese Schnittstellen akzeptieren beide einen generischen Typ, der vom Typ MetadataItem sein kann und verwendet wird, um den Datentyp zu filtern, für den die Konvention gilt.

Hinzufügen einer Konvention

Modellkonventionen werden auf die gleiche Weise hinzugefügt wie normale Klassen von Konventionen. Fügen Sie in der OnModelCreating-Methode die Konvention der Liste der Konventionen für ein Modell hinzu.

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

Eine Konvention kann auch in Beziehung zu einer anderen Konvention mithilfe der Methoden Conventions.AddBefore<> oder Conventions.AddAfter<> hinzugefügt werden. Weitere Informationen zu den Konventionen, die Entity Framework anwendet, finden Sie im Abschnitt „Hinweise“.

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

Beispiel: Diskriminator-Modellkonvention

Das Umbenennen von Spalten, die von EF generiert werden, ist ein Beispiel für etwas, das Sie nicht mit den anderen Konventionen-APIs tun können. Dies ist eine Situation, in welcher die Verwendung von Modellkonventionen Ihre einzige Option ist.

Ein Beispiel für die Verwendung einer modellbasierten Konvention zum Konfigurieren generierter Spalten ist das Anpassen der Art und Weise, wie Diskriminatorspalten benannt werden. Nachfolgend finden Sie ein Beispiel für eine einfache modellbasierte Konvention, die jede Spalte im Modell mit dem Namen „Diskriminator“ in „EntityType“ umbenennt. Dazu gehören Spalten, die der Entwickler einfach „Diskriminator“ genannt hat. Da die Spalte „Diskriminator“ eine generierte Spalte ist, muss dies in S-Space ausgeführt werden.

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";  
        }  
    }  
}

Beispiel: Allgemeine IA-Umbenennungskonvention

Ein weiteres komplizierteres Beispiel für modellbasierte Konventionen in der Praxis besteht darin, die Art und Weise zu konfigurieren, wie unabhängige Zuordnungen (Independent Associations, IAs) benannt werden. Dies ist eine Situation, in der Modellkonventionen anwendbar sind, da IAs von EF generiert werden und nicht im Modell vorhanden sind, auf das die DbModelBuilder-API zugreifen kann.

Wenn EF eine IA generiert, wird eine Spalte mit dem Namen EntityType_KeyName erstellt. Für eine Zuordnung namens „Kunde“ mit einer Schlüsselspalte namens „CustomerId“ würde beispielsweise eine Spalte namens „Customer_CustomerId“ erzeugt. Die folgende Konvention entfernt das Zeichen „_“ aus dem Spaltennamen, der für die IA generiert wird.

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

Erweitern vorhandener Konventionen

Wenn Sie eine Konvention schreiben müssen, die einer der Konventionen ähnelt, die Entity Framework bereits auf Ihr Modell anwendet, können Sie diese Konvention jederzeit erweitern, um zu vermeiden, sie von Grund auf neu schreiben zu müssen. Ein Beispiel hierfür ist das Ersetzen der vorhandenen ID-Abgleichskonvention durch eine benutzerdefinierte. Ein zusätzlicher Nutzen beim Überschreiben der Schlüsselkonvention besteht darin, dass die überschriebene Methode nur aufgerufen wird, wenn noch kein Schlüssel erkannt oder explizit konfiguriert wurde. Eine Liste der Konventionen, die von Entity Framework verwendet werden, ist hier verfügbar: 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;
    }
}

Dann müssen wir unsere neue Konvention vor der bestehenden Schlüsselkonvention einfügen. Nachdem wir die CustomKeyDiscoveryConvention eingefügt haben, können wir die IdKeyDiscoveryConvention entfernen. Wenn wir die vorhandene IdKeyDiscoveryConvention nicht entfernt hätten, hätte diese Konvention weiterhin Vorrang vor der ID-Ermittlungskonvention, da sie zuerst ausgeführt wird, aber wenn keine „Schlüssel“-Eigenschaft gefunden wird, wird die „ID“-Konvention ausgeführt. Dieses Verhalten ist darauf zurückzuführen, dass jede Konvention das Modell als durch die vorherige Konvention aktualisiert ansieht (anstatt es unabhängig voneinander zu bearbeiten und alles miteinander zu kombinieren). Wenn also beispielsweise eine vorherige Konvention einen Spaltennamen aktualisiert hat, damit er mit etwas übereinstimmt, das für Ihre benutzerdefinierte Konvention von Interesse ist (während der Name vorher nicht von Interesse war), dann wird er auf diese Spalte angewendet.

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

Hinweise

Eine Liste der Konventionen, die derzeit von Entity Framework angewendet werden, ist hier in der MSDN-Dokumentation verfügbar: http://msdn.microsoft.com/library/system.data.entity.modelconfiguration.conventions.aspx. Diese Liste wird direkt aus unserem Quellcode abgerufen. Der Quellcode für Entity Framework 6 ist auf GitHub verfügbar, und viele der von Entity Framework verwendeten Konventionen sind gute Startpunkte für benutzerdefinierte modellbasierte Konventionen.