Modellezésalapú elgondolások

Megjegyzés:

CSAK EF6 – Az ezen az oldalon tárgyalt funkciók, API-k stb. az Entity Framework 6-ban jelentek meg. Ha korábbi verziót használ, az információk egy része vagy egésze nem érvényes.

A modellalapú konvenciók a konvenciókon alapuló modellkonfiguráció speciális módszerei. A legtöbb esetben a DbModelBuilder custom code First Convention API-t kell használni. Modellalapú konvenciók használata előtt ajánlott megismerni a DbModelBuilder API-t a konvenciókhoz.

A modellalapú konvenciók olyan konvenciók létrehozását teszik lehetővé, amelyek a standard konvenciókon keresztül nem konfigurálható tulajdonságokat és táblákat befolyásolják. Ilyenek például a hierarchiamodellek szerinti táblázat diszkriminatív oszlopai és a független társítás oszlopai.

Konvenció létrehozása

A modellalapú konvenciók létrehozásának első lépése az, hogy mikor kell alkalmazni a konvenciót a modellre a folyamatláncban. A modellkonvencióknak két típusa van: a fogalmi (C-Space) és a Store (S-Space). A C-Space konvenciók az alkalmazás által létrehozott modellre lesznek alkalmazva, míg az S-Space konvenciók a modell azon verziójára vonatkoznak, amely az adatbázist jelöli, és szabályozza az automatikusan létrehozott oszlopok elnevezését.

A modellkonvenciók az IConceptualModelConvention vagy az IStoreModelConvention osztálytól terjednek ki. Ezek a felületek egyaránt elfogadnak egy olyan általános típust, amely metaadattípusú lehet, amely a konvenció által alkalmazott adattípus szűrésére szolgál.

Konvenció hozzáadása

A modellkonvenciók ugyanúgy lesznek hozzáadva, mint a normál konvenciók osztályai. Az OnModelCreating metódusban adja hozzá a konvenciót a modell konvencióinak listájához.

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

Konvenciók egy másik konvencióval kapcsolatban is hozzáadhatók a Conventions.AddBefore<> vagy Conventions.AddAfter<> metódusok használatával. Az Entity Framework által alkalmazott konvenciókról további információt a megjegyzések szakaszban talál.

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

Példa: Diszkriminátor modell konvenció

Az EF által létrehozott oszlopok átnevezése egy példa valamire, amelyet nem lehet elvégezni a többi konvenciójú API-val. Ez az a helyzet, amikor a modellkonvenciók használata az egyetlen lehetőség.

A modellalapú konvenciók generált oszlopok konfigurálásának egyik példája a diszkriminatív oszlopok elnevezésének testreszabása. Az alábbi példa bemutat egy egyszerű modellalapú konvenciót, amely átnevezi a "Discriminator" nevű modell minden oszlopát "EntityType"-ra. Ide tartoznak azok az oszlopok, amelyeket a fejlesztő egyszerűen "Diszkriminatívnak" nevezett. Mivel a "Diszkriminatív" oszlop egy generált oszlop, ennek az S-Space-ben kell futnia.

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

Példa: Általános IA átnevezési egyezmény

A modellalapú konvenciók egy másik bonyolultabb példája a független társítások (IA-k) elnevezésének konfigurálása. Ez az a helyzet, amikor a modellkonvenciók azért alkalmazhatók, mert az EF hozza létre az IA-kat, és nem szerepelnek a DbModelBuilder API által elérhető modellben.

Amikor az EF létrehoz egy IA-t, létrehoz egy EntityType_KeyName nevű oszlopot. Például egy Customer nevű, CustomerId nevű kulcsoszloptal rendelkező társítás esetén egy Customer_CustomerId nevű oszlop jön létre. Az alábbi konvenció kicsíkozza a "_" karaktert az IA-hoz létrehozott oszlopnévből.

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

Meglévő egyezmények kiterjesztése

Ha olyan konvenciót kell írnia, amely hasonló az Entity Framework által a modellre már alkalmazott egyik konvencióhoz, akkor mindig kiterjesztheti ezt a konvenciót, hogy ne kelljen teljesen újraírnia. Erre példa a meglévő azonosítóegyeztetési konvenció lecserélése egy egyedileg meghatározottra. A kulcskonvenciók felülírásának további előnye, hogy a felülírt metódus csak akkor lesz meghívva, ha nincs már észlelt vagy explicit módon konfigurált kulcs. Az Entity Framework által használt konvenciók listája itt érhető el: 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;
    }
}

Ezt követően hozzá kell adnunk az új konvenciónkat a meglévő kulcskonvenció előtt. A CustomKeyDiscoveryConvention hozzáadása után eltávolíthatjuk az IdKeyDiscoveryConventiont. Ha nem távolítjuk el a meglévő IdKeyDiscoveryConventiont, akkor ez a konvenció továbbra is elsőbbséget élvez az azonosítófelderítési konvencióval szemben, mivel először fut, de abban az esetben, ha nem található "kulcs" tulajdonság, az "id" konvenció fog futni. Ezt a viselkedést azért látjuk, mert minden konvenció az előző konvenció által frissített modellt látja (ahelyett, hogy függetlenül működne rajta, és az összes együtt lenne), így ha például egy korábbi konvenció frissített egy oszlopnevet, hogy megfeleljen az egyéni konvenciónak (amikor korábban a név nem volt érdekes), akkor az adott oszlopra fog vonatkozni.

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

Jegyzetek

Az Entity Framework által jelenleg alkalmazott konvenciók listája az MSDN dokumentációjában található: http://msdn.microsoft.com/library/system.data.entity.modelconfiguration.conventions.aspx. Ez a lista közvetlenül a forráskódból származik. Az Entity Framework 6 forráskódja elérhető a GitHubon , és az Entity Framework által használt számos konvenció jó kiindulópont az egyéni modellalapú konvenciókhoz.