Nov 5, 2009

Fluent mapping conventions. Автомаппинг.

Линки на остальные статьи

Рассмотрим модель описанную ранее, но с небольшой модификацией. Диаграмма классов будет выглядеть следующим образом:

image

И храним все это в следующей базе:

image

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

Но это скучно и не понятно зачем, ведь имена этих свойств и так можно получить при помощи рефлексии. Так же существует четкий принцип в программировании - DRY (don’t repeat yourself). Я думаю никому не надо объяснять почему повторяющийся код это плохо. А мы при маппинге очень часто повторяем себя поскольку вероятно имена полей будут совпадать с именами колонок в таблицах и т.д. Итак чтобы этого избежать в Fluent NHibernate существует Automapping.

Чтобы воспользоваться им создадим следующую структуру файлов:

image

Собственно исходный код классов в папке (и соответственно неймспейсе) Entities можно посмотреть в примере о связях.

Интерес представляет папка mappings. Начнем по порядку. Для генерирования маппингов Fluent NHibernate использует Conventions (соглашения). Они устанавливают правила формирования названий полей объектов, названий таблиц и т.д.

CategoryMap, OrderMap, ProductMap – к ним мы обратимся позже, это классы, которые позволяют дополнять или же переопределять маппинги полученные при помощи conventions.

ModelGenerator – класс, которой собственно и соберет все воедино. Итак рассмотрим его исходный код:

public class ModelGenerator
{
 public AutoPersistenceModel Generate()
 {
  AutoPersistenceModel automap = new AutoPersistenceModel();

  automap.Conventions.AddFromAssemblyOf<ModelGenerator>();
  automap.UseOverridesFromAssemblyOf<ModelGenerator>();

automap.AddEntityAssembly(Assembly.GetAssembly(typeof (Product))) .Where(x=>x.Namespace.EndsWith("Entities")); return automap; } }
  • 5 строка – добавить все conventions из сборки, которая содержит класс ModelGenerator
  • 6 строка – взять все переопределения из сборки содержащей ModelGenerator (далее подробней)
  • 7 строка – объекты, которые необходимо замапить находятся в сборке, содержащей Product
  • 8 строка – среди объектов взять только те, неймспейс которых заканчивается на Entities

Для примера я создал 2 соглашения. 1 для идентификаторов:

public class PrimaryKeyConvention : IIdConvention
{
 public void Apply(IIdentityInstance instance)
 {
  instance.GeneratedBy.Native();
  instance.Column("Id");
 }
}

Данное соглашение говорит о том, что идентификаторы генерируются базой данных, и что колонка, отвечающая за Id называется Id.

Соглашение для имен таблиц:

public class TableNameConvention : IClassConvention
{
 public void Apply(IClassInstance instance)
 {
  instance.Table(Inflector.Net.Inflector.Pluralize(instance.EntityType.Name)); 
 }
}

Тут используется Inflector, для того, чтобы в базе данных имена таблиц были в множественном числе. Например таблица с продуктами – Products, с категориями – Categories, в то время как объекты называются Product и Category.

Вообще говоря, если создавать базу с нуля, и следовать простым правилам, то все связи можно создавать при помощи соглашений, вообще не приходя к ручному маппингу. Но естественно не всегда есть такая возможность либо какой то из маппингов нужно изменить. Допустим по умолчанию (по соглашениям) для коллекций установлен lazy load. А с точки зрения производительности для определенного объекта его надо выключить. Это можно сделать при помощи переопределений маппингов. Именно для для этого в классе ModelGenerator указано UseOverridesFromAssemblyOf(...). Рассмотрим как это работает:

public class CategoryMap : IAutoMappingOverride<Category>
{
 public void Override(AutoMapping<Category> mapping)
 {
  mapping.HasManyToMany(x => x.Products)
   .Access.CamelCaseField()
   .Cascade.SaveUpdate()
   .AsSet()
   .ParentKeyColumn("CategoryId")
   .ChildKeyColumn("ProductId")
   .Table("Products_Categories");
 }
}

public class OrderMap : IAutoMappingOverride<Order>
{
 public void Override(AutoMapping<Order> mapping)
 {
  mapping.References(x => x.Product, "ProductId");
 }
}

public class ProductMap : IAutoMappingOverride<Product>
{
 public void Override(AutoMapping<Product> mapping)
 {
  mapping.HasManyToMany(x => x.Categories)
    .Access.CamelCaseField()
    .Table("Products_Categories")
    .ParentKeyColumn("CategoryId")
    .ChildKeyColumn("ProductId")
    .AsSet()
    .Inverse()
    .Cascade.SaveUpdate();

  mapping.HasMany(x => x.Orders)
    .KeyColumn("ProductId")
    .Inverse()
    .Cascade.AllDeleteOrphan();
 }
}

Как можно увидеть перепределение ничем не отличается от обычного Fluent маппинга с одним лишь различием – не надо упоминать каждое свойство.

Ну и в конце собственно изменения в файле Global.asax:

protected static ISessionFactory CreateSessionFactory()
{
 ModelGenerator modelGenerator = new ModelGenerator();
 var sessionFactory = Fluently.Configure()
    .Database(MsSqlConfiguration.MsSql2005.ConnectionString(@"connection string"))
    .Mappings(m =>m.AutoMappings.Add(modelGenerator.Generate()))
    .BuildSessionFactory();
 return sessionFactory;
}

Вот и все. При таком подходе, если следовать правильно настроенным соглашениям, то добавление нового класса будет заключаться непосредственно в добавлении этого класса и создании таблицы.

No comments:

Post a Comment