« Все записи

Code First в Entity Framework: «продвинутый» подход к настройке модели с помощью Fluent API

Канонический подход к настройке модели Entity Framework Code First с помощью Fluent API - это взять все определенные в проекте сущности и сконфигурировать их внутри метода OnModelCreating объекта DbContext.

public class Product
{
    public int ProductId { get; set; }
    public string Name { get; set; }

    public string PrimaryCategoryCode { get; set; }
    public virtual Category PrimaryCategory { get; set; }

    public string SecondaryCategoryCode { get; set; }
    public virtual Category SecondaryCategory { get; set; }

    public virtual ICollection<Tag> Tags { get; set; }
}

...

protected override void OnModelCreating(
    DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<Product>().ToTable("products");
    // ... конфигурирование остальных сущностей

    base.OnModelCreating(modelBuilder);
}

Мы так и делали когда-то в нашем примере. Понятно, что такой подход не очень хорошо масштабируется по мере добавления сущностей, поэтому давайте испробуем альтернативный.

Для начала мы поместим конфигурацию каждого объекта в отдельный класс.

public class ProductConfiguration : EntityTypeConfiguration<Product>
{
    public ProductConfiguration()
    {
        ToTable("products");            
        Property(p => p.Name).HasMaxLength(255);
    }
}

Теперь построение модели - это просто добавление всех объектов конфигурации в регистратор конфигураций построителя модели.

protected override void OnModelCreating(
                            DbModelBuilder modelBuilder)
{
    modelBuilder.Configurations.Add(new ProductConfiguration());
    // ...и так для каждого объекта конфигурации 

    base.OnModelCreating(modelBuilder);
}

Но и теперь недостатком является то, что каждый объект конфигурации нужно добавлять в OnModelCreating, что является достаточно муторной работой и заставляет дергаться всякий раз когда что-то в модели меняется. Следующее наше усовершенствование поможет автоматизировать эту рутину.

«Продвинутый» подход

Мы ведь можем использовать reflection, или любой приличный IoC контейнер, чтобы найти все объекты конфигурации в приложении и передать их нам в виде коллекции. Понятно, что это должна быть коллекция типа EntityTypeConfiguration<TEntity>, и код построения модели может выглядеть примерно так:

foreach (var configuration in configurations)
{
    modelBuilder.Configurations.Add(configuration);
}

Правда, такой подход может быть не очень легко осуществим. Поскольку параметр TEntity из EntityTypeConfiguration<TEntity> будет варьироваться в зависимости от объекта, нам в конечном итоге придется оперировать сущностями универсального открытого EntityTypeConfiguration<> или закрытого типа EntityTypeConfiguration<object>. Это некрасиво и не особо нужно, и я избавлю вас от этого. Давайте использовать другой способ, который начинается с интерфейса IEntityConfiguration:

public interface IEntityConfiguration
{
    void AddConfiguration(ConfigurationRegistrar registrar);
}

Каждый класс конфигурации должен реализовывать этот интерфейс:

public class ProductConfiguration : 
    EntityTypeConfiguration<Product>,
    IEntityConfiguration
{
    public ProductConfiguration()
    {
        ToTable("products");   
        // ... 
    }

    public void AddConfiguration(ConfigurationRegistrar registrar)
    {
        registrar.Add(this);
    }
}

Тогда построение модели можно схематично представить следующим образом:

protected override void OnModelCreating(
                            DbModelBuilder modelBuilder)
{
    // Сначала мы получаем коллекцию объектов конфигурации
    // с помощью reflection, IoC-контейнера или MEF
    var configurations = ...
   
    foreach (var configuration in configurations)
    {
        configuration.AddConfiguration(
            modelBuilder.Configurations);    
    }            
    base.OnModelCreating(modelBuilder);
}

Как уже говорилось раньше, коллекцию объектов конфигурации, теперь уже реализующих интерфейс IEntityConfiguration, можно получить с помощью reflection, IoC-контейнера или MEF.

При таком подходе код внутри метода OnModelCreating никогда не изменяется, даже если новые сущности появляются в приложении. Кроме того, нет никаких generic-параметров, а также ослаблена связь между построителем модели и объектами конфигурации (с использованием паттерна "Посетитель (Visitor)").

comments powered by Disqus