Линки на остальные статьи
Рассмотрим модель описанную ранее, но с небольшой модификацией. Диаграмма классов будет выглядеть следующим образом:
И храним все это в следующей базе:
По прежнему имеется категория, продукт и заказ. Но у продукта несколько больше свойств нежели в предидущем примере. Для маппинга этого объекта пришлось бы указывать каждое свойство по очереди вызывая метод Map.
Но это скучно и не понятно зачем, ведь имена этих свойств и так можно получить при помощи рефлексии. Так же существует четкий принцип в программировании - DRY (don’t repeat yourself). Я думаю никому не надо объяснять почему повторяющийся код это плохо. А мы при маппинге очень часто повторяем себя поскольку вероятно имена полей будут совпадать с именами колонок в таблицах и т.д. Итак чтобы этого избежать в Fluent NHibernate существует Automapping.
Чтобы воспользоваться им создадим следующую структуру файлов:
Собственно исходный код классов в папке (и соответственно неймспейсе) 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