Udostępnij za pośrednictwem


Konwencje oparte na modelu

Uwaga / Notatka

Tylko od EF6 wzwyż — funkcje, interfejsy API itp. omówione na tej stronie zostały wprowadzone w Entity Framework 6. Jeśli używasz starszej wersji, niektóre lub wszystkie informacje nie mają zastosowania.

Konwencje oparte na modelu to zaawansowana metoda konfiguracji modelu opartego na konwencji. W większości scenariuszy należy użyć interfejsu API konwencji Custom Code First w DbModelBuilder. Zaleca się zrozumienie interfejsu API DbModelBuilder w kontekście konwencji, zanim użyje się konwencji opartych na modelu.

Konwencje oparte na modelu umożliwiają tworzenie konwencji mających wpływ na właściwości i tabele, które nie można konfigurować za pomocą konwencji standardowych. Przykłady z nich to kolumny dyskryminujące w modelach tabelizowania hierarchii i kolumny niezależnych powiązań.

Tworzenie konwencji

Pierwszym krokiem tworzenia konwencji opartej na modelu jest wybranie, kiedy w przepływie danych konwencja powinna być zastosowana do modelu. Istnieją dwa typy konwencji modelu: Koncepcyjne (C-Space) i Store (S-Space). Konwencja C-Space jest stosowana do modelu tworzonego przez aplikację, natomiast konwencja S-Space jest stosowana do wersji modelu reprezentującego bazę danych i kontroluje takie elementy, jak nazwane kolumny generowane automatycznie.

Konwencja modelu to klasa, która rozszerza model IConceptualModelConvention lub IStoreModelConvention. Oba interfejsy akceptują typ ogólny, który może być typu MetadataItem, który służy do filtrowania typu danych, do którego ma zastosowanie konwencja.

Dodawanie konwencji

Konwencje modelu są dodawane w taki sam sposób, jak klasy konwencji regularnych. W metodzie OnModelCreating dodaj konwencję do listy konwencji dla modelu.

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

Konwencję można również dodać w odniesieniu do innej konwencji przy użyciu metod Conventions.AddBefore<> lub Conventions.AddAfter<> . Aby uzyskać więcej informacji na temat konwencji stosowana przez program Entity Framework, zobacz sekcję notatek.

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

Przykład: Dyskryminująca konwencja modelu

Zmiana nazwy kolumn generowanych przez EF jest przykładem czegoś, czego nie można zrobić z konwencjami API. Jest to sytuacja, w której używanie konwencji modelu jest jedyną opcją.

Przykładem sposobu używania konwencji opartej na modelu do konfigurowania wygenerowanych kolumn jest dostosowywanie sposobu, w jaki są nazwane kolumny dyskryminujące. Poniżej przedstawiono przykład prostej modelowej konwencji, która zmienia nazwę każdej kolumny w modelu o nazwie "Discriminator" na "EntityType". Obejmuje to kolumny, które deweloper nazwał po prostu „Discriminator”. Ponieważ kolumna "Dyskryminator" jest wygenerowaną kolumną, musi działać w obszarze 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";  
        }  
    }  
}

Przykład: Ogólna konwencja zmiany nazw Inteligentnej Automatyki (IA)

Innym bardziej skomplikowanym przykładem konwencji opartych na modelu w działaniu jest skonfigurowanie sposobu, w jaki nazywane są Niezależne Asocjacje (IA). To sytuacja, w której konwencje modelu mają zastosowanie, ponieważ wystąpienia IA są generowane przez EF i nie występują w modelu, do którego można uzyskać dostęp za pomocą interfejsu API DbModelBuilder.

Gdy program EF generuje IA, tworzy kolumnę o nazwie EntityType_KeyName. Na przykład w przypadku skojarzenia o nazwie Klient z kolumną klucza o nazwie CustomerId wygeneruje kolumnę o nazwie Customer_CustomerId. Poniższa konwencja usuwa znak "_" z nazwy kolumny wygenerowanej dla 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);
            }                 
        }
    }
}

Rozszerzanie istniejących konwencji

Jeśli musisz napisać konwencję podobną do jednej z konwencji, które program Entity Framework już stosuje do modelu, zawsze można rozszerzyć taką konwencję, aby uniknąć konieczności ponownego zapisywania go od podstaw. Przykładem jest zastąpienie istniejącej konwencji dopasowywania identyfikatorów konwencją niestandardową. Dodatkową korzyścią z zastąpienia konwencji klucza jest to, że metoda zastępująca zostanie wywołana tylko wtedy, gdy nie wykryto klucza ani nie skonfigurowano go jawnie. Lista konwencji używanych przez program Entity Framework jest dostępna tutaj: 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;
    }
}

Następnie musimy dodać naszą nową konwencję przed istniejącą konwencją kluczową. Po dodaniu elementu CustomKeyDiscoveryConvention możemy usunąć element IdKeyDiscoveryConvention. Jeśli nie usuniemy istniejącej konwencji IdKeyDiscoveryConvention, nadal będzie miała pierwszeństwo przed konwencją odkrywania identyfikatorów, ponieważ jest wykonywana jako pierwsza, ale jeśli nie zostanie znaleziona żadna właściwość "klucz", zostanie uruchomiona konwencja "id." Widzimy to zachowanie, ponieważ każda konwencja postrzega model jako zaktualizowany przez poprzednią konwencję (zamiast działać na nim niezależnie, wszystkie konwencje są scalane), tak że, na przykład, kiedy poprzednia konwencja zaktualizowała nazwę kolumny, aby pasowała do czegoś, co jest istotne dla konwencji niestandardowej (choć wcześniej nazwa nie była istotna), będzie ona zastosowana do tej kolumny.

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

Notatki

Lista konwencji, które są obecnie stosowane przez program Entity Framework, jest dostępna w dokumentacji MSDN tutaj: http://msdn.microsoft.com/library/system.data.entity.modelconfiguration.conventions.aspx. Ta lista jest pobierana bezpośrednio z naszego kodu źródłowego. Kod źródłowy dla programu Entity Framework 6 jest dostępny w witrynie GitHub , a wiele konwencji używanych przez program Entity Framework to dobre punkty wyjścia dla niestandardowych konwencji opartych na modelu.