Линки на остальные статьи
Рассмотрим модель описанную ранее, но с небольшой модификацией. Диаграмма классов будет выглядеть следующим образом:
И храним все это в следующей базе:
По прежнему имеется категория, продукт и заказ. Но у продукта несколько больше свойств нежели в предидущем примере. Для маппинга этого объекта пришлось бы указывать каждое свойство по очереди вызывая метод 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