Соглашения на основе моделей

Примечание.

Только в EF6 и более поздних версиях. Функции, API и другие возможности, описанные на этой странице, появились в Entity Framework 6. При использовании более ранней версии могут быть неприменимы некоторые или все сведения.

Соглашения на основе моделей — это расширенный метод конфигурации модели на основе соглашений. Для большинства сценариев следует использовать API первого соглашения пользовательского кода в DbModelBuilder . Перед использованием соглашений рекомендуется понимать API DbModelBuilder для соглашений.

Соглашения на основе моделей позволяют создавать соглашения, влияющие на свойства и таблицы, которые не настраиваются с помощью стандартных соглашений. Примерами этих столбцов являются дискриминационные столбцы в таблице для каждой модели иерархии и столбцы Независимой ассоциации.

Создание соглашения

Первым шагом при создании соглашения на основе модели является выбор того, когда в конвейере необходимо применить соглашение к модели. Существует два типа соглашений о модели, концептуальные (C-Space) и Store (S-Space). Соглашение C-Space применяется к модели, созданной приложением, в то время как соглашение S-Space применяется к версии модели, представляющей базу данных и управляет такими элементами, как автоматически созданные столбцы.

Соглашение модели — это класс, который расширяется от IConceptualModelConvention или IStoreModelConvention. Эти интерфейсы принимают универсальный тип, который может быть типом MetadataItem, который используется для фильтрации типа данных, к которому применяется соглашение.

Добавление соглашения

Соглашения модели добавляются так же, как и обычные классы соглашений. В методе OnModelCreating добавьте соглашение в список соглашений для модели.

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

Соглашение также можно добавить в отношении другого соглашения с помощью методов Conventions.AddBefore<> или Conventions.AddAfter<> . Дополнительные сведения о соглашениях, применимых Entity Framework, см. в разделе заметок.

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

Пример: Соглашение о дискриминационных модельх

Переименование столбцов, созданных EF, является примером того, что нельзя сделать с другими API-интерфейсами соглашений. Это ситуация, когда использование соглашений о модели является единственным вариантом.

Пример использования соглашения на основе модели для настройки созданных столбцов — настройка способа именования дискриминационных столбцов. Ниже приведен пример простого соглашения на основе модели, которое переименовывает каждый столбец модели с именем "Дискриминационный" на EntityType. К ним относятся столбцы, которые разработчик просто назвал "Дискриминатор". Так как столбец "Дискриминатор" представляет собой созданный столбец, который должен выполняться в 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";  
        }  
    }  
}

Пример: Общая конвенция об переименовании IA

Еще одним более сложным примером соглашений на основе моделей в действии является настройка способа именования независимых ассоциаций (IAs). Это ситуация, когда применимы соглашения о модели, так как IAs создаются EF и не присутствуют в модели, к которой может получить доступ API DbModelBuilder.

При создании IA EF создается столбец с именем EntityType_KeyName. Например, для сопоставления с именем Customer с ключевым столбцом CustomerId он создаст столбец с именем Customer_CustomerId. В следующем соглашении символ "_" удаляется из имени столбца, созданного для 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);
            }                 
        }
    }
}

Расширение существующих соглашений

Если вам нужно написать соглашение, аналогичное одному из соглашений, которые Entity Framework уже применяется к модели, вы всегда можете расширить это соглашение, чтобы избежать необходимости переписать его с нуля. Примером этого является замена существующего соглашения о сопоставлении идентификаторов с пользовательским. Дополнительное преимущество переопределения соглашения о ключе заключается в том, что переопределенный метод вызывается только в том случае, если ключ уже обнаружен или явно настроен. Список соглашений, используемых Entity Framework, доступен здесь: 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;
    }
}

Затем необходимо добавить новое соглашение до существующего ключевого соглашения. После добавления CustomKeyDiscoveryConvention можно удалить IdKeyDiscoveryConvention. Если мы не удалили существующее соглашение IdKeyDiscoveryConvention, это соглашение по-прежнему будет иметь приоритет над соглашением об обнаружении идентификаторов, так как оно выполняется первым, но в случае, если свойство key не найдено, соглашение "id" будет выполняться. Мы видим это поведение, так как каждое соглашение видит модель как обновленную предыдущей конвенцией (вместо того, чтобы работать на ней независимо и все объединены вместе), чтобы, например, предыдущее соглашение обновило имя столбца, чтобы оно соответствовало своему пользовательскому соглашению (когда до этого имя не было интересом), то оно будет применяться к этому столбцу.

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

Примечания.

Список соглашений, которые в настоящее время применяются Entity Framework, доступен в документации MSDN: http://msdn.microsoft.com/library/system.data.entity.modelconfiguration.conventions.aspx Этот список извлекается непосредственно из исходного кода. Исходный код entity Framework 6 доступен на сайте GitHub , и многие соглашения, используемые Entity Framework, являются хорошими отправными точками для соглашений на основе пользовательских моделей.