воскресенье, 10 июля 2011 г.

Маппинг в Entity Framework с помощью Fluent API

Как известно, в Entity Framework действует ряд соглашений для Code First. Изменить их можно с помощью аннотации данных (Data Annotations) или через интерфейс Fluent API. Их можно использовать совместно. В конечном счете, информация о маппинге собирается в порядке Fluent API, Аннотации данных, а затем - соглашения. В последнее время все большую популярность набирает удобный и понятный интерфейс Fluent. С помощью него посмотрим, каким образом можно кастомизировать маппинг.


Сразу оговорюсь, что моей целью является показать каким образом можно настраивать маппинг с fluent-интерфейсом, а не как делается сам маппинг.

Для начала создадим пару объектов, с которыми будем работать дальше. Не будем усложнять и нагружать классы какой-либо бизнес-логикой:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Category Category { get; set; }
}

public class Category
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<Product> Products { get; set; }
}

Теперь создадим контекст базы данных:

public class ExampleContext : DbContext
{
    public DbSet<Product> Products { get; set; }

    public DbSet<Category> Categories { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // настройка параметров таблицы через Fluent API
    }
}

Как указано в комментарии, настройка параметров генерируемой таблицы производится в переопределенном методе OnModelCreating соответствующего контекста базы данных. Все настройки аккумулируются в экземпляре DbModelBuilder - modelBuilder, который передается в метод в качестве параметра. А дальше можно поступать по-разному.

Первый способ

Первый способ заключается в том, чтобы каждую отдельную настройку производить через методы DbModelBuilder Entity или ComplexType. Сделаем некоторую настройку маппинга (пусть даже некоторые настройки будут совпадать с соглашениями):

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    // настройка параметров таблицы через Fluent API
    modelBuilder.Entity<Product>()
        .ToTable("Products");

    modelBuilder.Entity<Product>()
        .HasKey(p => p.Id);

    modelBuilder.Entity<Product>()
        .Property(p => p.Name)
        .HasColumnName("Name")
        .HasMaxLength(100)
        .IsRequired();

    modelBuilder.Entity<Category>()
        .ToTable("Categories");

    modelBuilder.Entity<Category>()
        .Property(c => c.Name)
        .HasColumnName("CategoryName");
}

Этот способ хорошо подойдет в том случае, когда настроек совсем немного и в основном подойдут параметры, полученные на основе соглашений. В противном случае, метод OnModelCreating разрастается до огромных размеров и превращается в кашу. Особенно напрягает необходимость каждый раз для настройки какого-либо параметра маппинга одной и той же сущности делать вызов метода Entity:

modelBuilder.Entity<Product>()
    .ToTable("Products");

modelBuilder.Entity<Product>()
    .HasKey(p => p.Id);

modelBuilder.Entity<Product>()
    .Property(p => p.Name)
    .HasColumnName("Name")
    .HasMaxLength(100)
    .IsRequired();

// и так далее...

Второй способ

Второй способ, скорее всего, особенно понравится тем, кто работал с NHibernate. Дело в том, что в этом случае настройка каждой сущности производится в отдельном классе. Для этого достаточно унаследовать класс с настройками типа от EntityTypeConfiguration или ComplexTypeConfiguration и указать параметры маппинга в конструкторе. Вот пример того же самого маппинга, который был в первом способе:

public class ProductTypeConfiguration : EntityTypeConfiguration<Product>
{
    public ProductTypeConfiguration()
    {
        ToTable("Products");

        HasKey(p => p.Id);

        Property(p => p.Name)
            .HasColumnName("Name")
            .HasMaxLength(100)
            .IsRequired();
    }
}

public class CategoryTypeConfiguration : EntityTypeConfiguration<Category>
{
    public CategoryTypeConfiguration()
    {
        ToTable("Categories");

        Property(c => c.Name)
            .HasColumnName("CategoryName");
    }
}

А затем в том же методе OnModelCreating укажем конфигурацию маппинга:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    // настройка параметров таблицы через Fluent API
    modelBuilder.Configurations
        .Add(new ProductTypeConfiguration())
        .Add(new CategoryTypeConfiguration());
}

Теперь чтобы изменить какие-либо параметры нет необходимости искать среди множества строк нужную. Достаточно лишь перейти к соответствующему классу конфигурации и произвести необходимые настройки.

Итак, мы рассмотрели два способа настройки маппинга Entity Framework через fluent-интерфейс. Какой из них использовать - дело каждого. В первом способе не нужно писать дополнительные классы конфигурации. Второй способ кажется более прозрачным и понятным, а также аналогичен подходу в NHibernate.

Комментариев нет:

Отправить комментарий