经过了“开篇”和“简单映射”两篇文章,相信大家对Fluent NHibernate 有了一定的了解了,FluentNHibernate实际就是对 NHibernate 映射的一定扩展,我们能完全利用强类型、泛型、Lambde表达式等等Vs、Framework等特性简单完成映射工作,同时也能让我们学习NHibernate的映射方式,一举夺得,这么好玩的东东,有理由不继续完成这个系列吗?废话不说,回到正题。
从这一篇开始,我们将使用Fluent NHibernate RC 1.0 版来进行演示,先前的代码,我会另外进行说明
继承
在OOP中,继承作为OO中重要的特性,如果NHibernate没有对它的支持,那怎么能称为完整的ORM框架呢?那怎么通过数据库设计来完成继承呢?常见的方法有三种,今天我们就一一来进行叙述。继承的概念我就不说了,如果你连继承还不知道的话,可以好好学起,从头学起,呵呵。
- Table per class hierarchy(所有子类在一张表)
- Table per subclass(父类一张表,每个子类一张表)
- Other(其他方式)
准备
我们还是以前两篇的电子商务来说事,我们产品不可能只有一种,但他们有相同的属性,每种产品类型又有自己的属性拿它来事是最好的例子了。产品的相同属性,可以参考上一篇。好,我们假设我们的产品还有书和手机,他们有各自自己的属性,我们就简单加一点属性,书有作者,手机有品牌和型号,我们写这2个Entity Model:
public class MobileProduct : Product { public virtual string Brand { get; set; } public virtual string Number { get; set; } } public class BookProduct : Product { public virtual string Author { get; set; } }
Table per class hierarchy
这种方式在我们学习NHibernate中,一定看到过了,其实就是把父类、子类的所有属性放到一个表中,这样做的好处就是我们不需要建立其他表,一张表格全搞定,但缺点也显而易见,在属性少的情况下或许没有什么,但是多了以后,我们的维护、扩展就变得相对麻烦,而且会产生很多亢余字段,而且我们必须再添加一个标识符,来区分。
因为需要标识符,所以我们必须添加一个标识符,我这里使用枚举类型来表示,代码改变成如下:
public enum ProductType { Mobile, Book } public abstract class Product { public abstract ProductType Type { get; } } public class MobileProduct : Product { public override ProductType Type { get { return ProductType.Mobile; } } } public class BookProduct : Product { public override ProductType Type { get { return ProductType.Book; } } }
数据库的设计,大家需要注意,子类的几个字段,最好设置成null,避免造成不能insert的问题。我们还需要增加一个标识符字段,我使用的是tinyint,如图:
Fluent 映射如下:
public class ProductMap : ClassMap<Product> { public ProductMap() { Id(p => p.ProductID); Map(p => p.CreateTime); Map(p => p.Name); Map(p => p.Price); DiscriminateSubClassesOnColumn<ProductType>("Type", ProductType.Book); } } public class BookMap : SubclassMap<BookProduct> { public BookMap() { DiscriminatorValue("1"); Map(p => p.Author); } } public class MobileMap : SubclassMap<MobileProduct> { public MobileMap() { DiscriminatorValue("0"); Map(p => p.Brand); Map(p => p.Number); } }
在RC版本之前,我们可以使用
DiscriminateSubClassesOnColumn<ProductType>("Type", ProductType.Book) .SubClass<BookProduct>(b => { b.Map(p => p.Author); });
但是RC版本已经不建议我们这样使用,好的做法是把子类映射区分开来,等一下你会知道,第一种策略和第二种策略都会采用这样的方式来进行映射,至于映射会使用subclass还好是join-subclass,FluentNHibernate会自动生成的。
在说一下DiscriminateSubClassesOnColumn,这就是标识符的一个映射方式,因为在RC版之前,我们可以使用SetAttribate的方法,设定我们的父类为"not-null" 但是在RC版中,这个方法已经彻底被抛弃掉了,我们不得不使用上面的方法,给定一个默认的标识符。在子类中,我们必须制定对应的标识符值,这里又出现败笔了,只能设定string类型,My God,变相的SetAttribute,我觉得这个已经脱离了Fluent的称号了(在RC升级介绍中已经说明)。
PS:刚看了下最新的FNT,已经修正了这个bug,现在已经改为:DiscriminatorValue(object value),详细点击
我们来看下测试代码:
[TestMethod] public void FluentCreateData() { var factory = FluentSessionFactory.GetCurrentFactory(); using (var session = factory.OpenSession()) { Product nokia = new MobileProduct() { Brand = "Nokia", Number = "N91", CreateTime = DateTime.Now, Price = 5600, Name = "Nokia N91 Mobile" }; session.Save(nokia); Product book = new BookProduct() { Author = "六六", CreateTime = DateTime.Now, Price = 20, Name = "蜗居" }; session.Save(book); } } [TestMethod] public void FluentSeleteData() { var factory = FluentSessionFactory.GetCurrentFactory(); using (var session = factory.OpenSession()) { var product = session.CreateCriteria<BookProduct>().List<BookProduct>().FirstOrDefault(); Assert.AreEqual("蜗居", product.Name); Assert.AreEqual(20, product.Price); } }
测试结果:
Table per subclass
在这种方式中,一个父类表包括了一些共同的属性,子类表除了主键外,就只有属于自己的属性。这种方式表结构清晰,而且不会有亢余字段,同时方便扩展,是不错的选择,不过这不是说其他方式没有用,要看你的使用场景。看表结构:
这里注意的是,子表的主键不是递增的(废话,呵呵)。
上Mapping:
public class ProductMap : ClassMap<Product> { public ProductMap() { Id(p => p.ProductID); Map(p => p.CreateTime); Map(p => p.Name); Map(p => p.Price); } } public class BookMap : SubclassMap<BookProduct> { public BookMap() { Table("Book"); KeyColumn("ProductID"); Map(p => p.Author); } } public class MobileMap : SubclassMap<MobileProduct> { public MobileMap() { Table("Mobile"); KeyColumn("ProductID"); Map(p => p.Brand); Map(p => p.Number); } }
怎么样,跟第一种方式是不是类似啊,呵呵,这要感谢这次的升级,我们无需改动太大,就能在这2中方式之间切换,很便利吧,至于不足,可能就是KeyColum不会自动去识别,有点遗憾,如果没有指定的话,默认为:"Product_ID"。
我们的测试代码,同第一种方式,直接看测试结果:
其他方式
Table per concrete class(每个子类一张表),这种方式应该使用union-subclass标签,但FNT不支持这种方式,为什么呢,呵呵,因为这种方式不好,无论是从结构还是编写上来说,都是不好的做法,尽量避免的做法,所以FNT索性不支持了,如果真的需要这种方式,那你就直接分开映射ClassMap<T>吧,联合查询的话,或许就比较复杂了。
还有几种混合方式,其实就是第一种和第二种的结合,这要看不同需求了,同时也可以结合上面两种映射方式完成,就不做介绍了。
总结
这次说了一下继承的映射方式,其实很早就写好了,正好遇到Fluent NHibernate发布了RC版,所以用了一些时间去学习了下,总的来说,这次升级呢非常好,可能会存在更多的bug,但不影响我们的正常使用,而且现在的更新也非常快,后面几张可能说下几种不常见的映射和会遇到的些麻烦,不过感觉园子里使用NHibernate的人不太多,关注度不多,不过不影响我继续写下去的决心,这次也很偶然接触了FNT,确实有好多好的地方可以借鉴一下,而且在系列中,基本上每个范例都写了测试代码,虽然丑陋了点,但从小做起嘛,以后要养成这个习惯。