[原创]Fluent NHibernate之旅(三)-- 继承

经过了“开篇”和“简单映射”两篇文章,相信大家对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,确实有好多好的地方可以借鉴一下,而且在系列中,基本上每个范例都写了测试代码,虽然丑陋了点,但从小做起嘛,以后要养成这个习惯。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏海说

[转]我的编码习惯 - 接口定义

工作中,少不了要定义各种接口,系统集成要定义接口,前后台掉调用也要定义接口。接口定义一定程度上能反应程序员的编程功底。列举一下工作中我发现大家容易出现的问题:

953
来自专栏一“技”之长

iOS有关内存管理的二三事 原

随着移动设备的内存越来越大,程序员也已经度过了为了那一两M的内存在系统的抽丝剥茧的年代,对于JAVA的开发者,对内存更是伸手即取,并且从不关心什么时候还回去。但...

732
来自专栏听雨堂

字符串处理总结(旧)

在各类应用软件的开发中,字符串操作是最常见的操作之一。在各种不同的数据类型中,字符串类型是和现实世界关联最紧密的。对字符串的读入、比较、拼接、搜索、匹配、替换、...

2588
来自专栏偏前端工程师的驿站

JS魔法堂:不完全国际化&本地化手册 之 实战篇

前言  最近加入到新项目组负责前端技术预研和选型,其中涉及到一个熟悉又陌生的需求——国际化&本地化。熟悉的是之前的项目也玩过,陌生的是之前的实现仅仅停留在"有"...

27110
来自专栏小蠢驴iOS专题

实际开发中-Block导致循环引用的问题

2084
来自专栏Coding01

Javascript 从异步函数到 Promise 到 Async/Await

我最近正在看的一本书《聊聊架构》,在进入今天的主题之前,我想和大家分享这本书里的一个概念“生命周期”。

1754
来自专栏walterlv - 吕毅的博客

应该抛出什么异常?不应该抛出什么异常?(.NET/C#)

2018-02-04 13:25

1862
来自专栏zhisheng

面向对象设计原则之依赖倒转原则

前两天的算法排序文章,《Java常用排序算法/程序员必须掌握的8大排序算法(上)》、《Java常用排序算法/程序员必须掌握的8大排序算法(下)》,没看的可以点上...

3738
来自专栏JAVA高级架构开发

Java 11 正式发布,这 8 个逆天新特性教你写出更牛逼的代码

美国时间 09 月 25 日,Oralce 正式发布了 Java 11,这是据 Java 8 以后支持的首个长期版本。

1573
来自专栏tkokof 的技术,小趣及杂念

Coroutine,你究竟干了什么?(小续)

前篇中讲了一些自己关于Coroutine的理解,后来陆陆续续的又想到了一些,在此简单记录一下,内容不多,故作“小”续吧 :)

872

扫码关注云+社区

领取腾讯云代金券