【架构之路之ORM】--FluentNHibernate之AutoMapping详解

上篇文章详细讨论了FluentNHibernate的基本映射的使用方法,它的映射基本用法是跟NHibernate完全一样的,首先要创建数据库链接配置文件,然后编写Table的Mapping,最后编写Unit Test来测试模块Mapping是否成功执行。FluentNHibernate之所以替代NHibernate并不仅仅是因为对Mapping配置文件的优化,另外它换可以优化数据库的链接xml以及规避Mapping文件的编写,这种完全自动化的编程方法就是AutoMapping,FluentNHibernate封装了自动化映射的方法,使得开发人员只需要几种到对Table的修改中,而不需要考虑数据模型到对象模型的转化过程。

在项目中如果使用了AutoMapping那么它的具体的架构图如下

上面的架构图使用了AutoMapping,它的使用能够避免繁杂的Mapping文件的编写,也就是说NHibernate在访问数据库时首先要从FluentNHibernate那里获取对象和数据是如何相互转化的,然后FluentNHibernate会从AutoMapping中获取各种映射的规则,也就是说AutoMapping只需要重写对象模型的映射规则就可以实现完全的映射,这样就使得程序的开发变得相当简单。

一、AutoMapping

上文使用架构图对FluentNHibernate的AutoMapping做了初步的解读,可以看出AutoMapping是依附于FluentNHibernate的,而且FluentNHibernate是依附于NHibernate的,它们三者首先是NHibernate先工作,查找到FluentNHibernate,然后是FluentNHibernate查找到AutoMapping的规则,按照规则进行映射。

往往在开发时我们都会把Mapping和Models放到Core的程序集内,供其它层的程序集调用,如上图的Repository层,是对数据库做的一系列操作,都封装到该层中。

1.1 AutoMapping详解

在没有AutoMapping情况下如果使用FluentNHibernate来实现映射的话,首先要创建Mapping文件,然后对每个Entity都做Mapping代码的编写,但是这样就太麻烦了,因为如果有很多表的话一个个的编写Mapping很浪费时间,同时也会产生很多冗余的Code,所以这时候可以考虑使用AutoMapping来映射文件,只不过需要按照创建的规则来创建数据库的。

(1)创建AutoMapping的映射配置类,继承DefaultAutomappingConfiguration类,并重写ShouldMap方法,指定实体类所在的命名空间

(2)实现映射的接口,定义Entity到Table自动Mapping的规则,主要的接口如下:

IClassConvention,Entity Class到Table的映射,需要实现指定Table名称的Apply方法,默认情况下是以Entity的名称作为Table名称。

IIdConvention,Entity Class到Table ID的映射规则,需要实现指定Table ID生成的规则及ID的生成规则等,如果不实现该接口的话默认情况下会以“实体名_ID”形式命名Table的ID。

IReferenceConvention,Entity Class到Table的外键列映射规则,需要实现Apply方法,来指定外键列名称的生成规则及连接对象的加载规则,默认情况下会以“外键对象_ID”形式命名外键列。

IPropertyConvention,Entity Class属性到Table列名称的生成规则及加载规则,默认情况下会以属性名作为Table的列名。

IHasOneConvention,Entity Class一对一映射的规则及列的生成规则,需要实现Apply方法指定外键列的命名规则,同时也可以规定外键对象的加载和操作方式,默认情况下会以“对象_ID”形式命名。

IHasManyConvention,Entity Class一对多映射的规则及列的生成规则,需要实现Apply方法指定列的命名规则,同时也可以规定外键对象的加载和操作方式,默认情况下会以“对象_ID”形式命名。

IHasManyToManyConvention,Entity Class多对多映射的规则及列的生成规则,需要实现Apply方法指定关联表及关联表中列的命名规则,默认情况下会以“对象_ID”形式命名列名,“对象To对象”形式命名表名。

(3)编写Unit Test运行AutoMapping的配置文件,生成NHibernate的映射xml,查看映射是否成功。

Note:AutoMapping的映射过程会生成一些冗余表或者冗余列,如在多对多的映射过程中因为对象的两端都有另外一端对象的引用,所以在生成表结构时会同时生成两个关联表,但是在数据时只会使用到一个关联表。所以如果不是必要的情况不要使用AutoMapping来映射表结构,在一些特别小型的项目中可以考虑使用AutoMapping,大型的项目建议手动创建映射文件,这样可以手动的控制。

1.2 实例详解

上文对AutoMapping的使用过程做了详细的讨论,接下来我们将会创建一个代码实例来讨论AutoMapping的具体使用方法。

这里还以上文中的数据结构图为例来详细的讨论AutoMapping的使用方法,示例中使用的AutoMapping自动生成的数据库结构图如下:

上图是使用了AutoMapping来映射Entity后生成的数据库结构图,图中包括了一对一、一对多、多对多的关系,这些关系在生成的时候是按照1.1所说的映射规则实现的,具体的使用方法将会按照上文中所说的开发步骤来详细讨论。

1.2.1 创建AutoMapping的配置文件

在使用NHibernate做开发时首先要创建一个数据库的镜像Session,在使用FluentNHibernate也是如此,在使用时首先要创建一个数据库的Session对象,然后通过将对象转化为持久态来实现数据的操作。

AutoMapping的配置文件在创建Session时首先要创建AutoPersistenceModel对象,也就是将实体类所在的程序集/命名空间及Entity到Table的映射规则注册到     FluentNHibernate的AutoMapping中,具体的代码如下:

/// <summary>
/// 继承默认的AutoMapping配置类
/// 重写ShouldMap方法指定映射Entity所在的命名空间
/// </summary>
public class StoreConfiguration : DefaultAutomappingConfiguration
{
    public override bool ShouldMap(Type type)
    {
        return type.Namespace == "ClassLibrary1.mapping";
    }
}

/// <summary>
/// 创建SessionFactory
/// </summary>
public class Build
{

    private static AutoPersistenceModel CreateAutomappings()
    {
        //AutoMap.Assembly(Assembly.Load("ClassLibrary1"), new StoreConfiguration())
        //添加需要映射的程序集,在添加时可以使用默认的DefaultAutomappingConfiguration也可以继承该类重写ShouldMap方法来指定映射的命名空间
        return AutoMap
            .Assembly(Assembly.Load("ClassLibrary1"), new StoreConfiguration())
            //Conventions.Setup()方法是将映射的具体规则绑定到AutoMapping配置中,如下配置了:
            //DefualtTableNameConvertion默认表命名方式
            //DefaultPrimarykeyConvention默认主键命名方式
            //DefualtStringLengthConvertion默认的字符串长度
            //DefaultReferenceConvention默认的外键创建方法
            //DefaultHasManyConvention默认的一对多的创建方法
            //DefualtHasOneConvertion默认的一对一的创建方法
            //HasManyToManyConvention默认的多对多的创建方法
            .Conventions.Setup(con =>
            {
                con.Add<DefualtTableNameConvertion>();
                con.Add<DefaultPrimarykeyConvention>();
                con.Add<DefualtStringLengthConvertion>();
                con.Add<DefaultReferenceConvention>();
                con.Add<DefaultHasManyConvention>();
                con.Add<DefualtHasOneConvertion>();
                con.Add<HasManyToManyConvention>();
            });
    }

    public ISessionFactory CreateSessionFactory()
    {
        return Fluently
            .Configure()    //创建或者指定数据库的配置xml,这里使用的是自动创建的方法
            //指定数据库的映射方法,其实它的底层使用的是反射来实现的映射
            .Database(
                MsSqlConfiguration
                    .MsSql2005
                    .ConnectionString(
                        "Data Source=.;Initial Catalog=Mapping;Integrated Security=true;Pooling=True;Min Pool Size=20;Max Pool Size=60")
            //指定连接字符串
                    .ShowSql()
            )
            //配置映射规则
            .Mappings(m => m.AutoMappings.Add(CreateAutomappings()) //将创建的AutoPersistenceModel对象添加到AutoMapping中
                .ExportTo("c:\\path") //导出映射文件
            )
            //生成数据库架构
            //new SchemaExport(cfg).Create(true, false)创建配置文件。
            //这里的Create方法是创建的脚本文件的映射方法,
            //第一个参数bool script指定是否生成数据库脚本
            //第二个参数bool export指定每次生成的数据库的创建脚本是否执行
            .ExposeConfiguration(cfg => new SchemaExport(cfg).Create(true, false))
            .BuildSessionFactory();
    }

}

有了上面的配置代码就可以实现AutoMapping规则了,但是往往我们的配置文件中需要实现映射的接口,通过实现这些接口来定义Entity和Table的相互映射的规则,实现这些接口后将这些类添加到SetUp到相应的AutoMapping中。

1.2.2 实现映射接口

添加好了AutoMapping的配置文件后基本上已经可以实现映射了,但是如果我们没有自定义映射规则的话将会以默认的方式生成相应的表结构,这些生成的表结构也可以通过实现接口来自定义这些规则,具体方法如下。

(1)IClassConvention

通过实现该接口来定义映射的Entity Class到Table的映射转换规则,如果不重写生成规则的话默认会以对象名作为表名。具体定义实现方法如下代码:

public class DefualtTableNameConvertion : IClassConvention
{
    /// <summary>
    /// 指定生成的表名或者表名到对象的映射
    /// </summary>
    /// <param name="instance">需要映射的Entity对象</param>
    public void Apply(FluentNHibernate.Conventions.Instances.IClassInstance instance)
    {
        //定义生成表名的规则,实体类名+s
        instance.Table(instance.EntityType.Name + "s");
    }
}

(2)IIdConvention

实现该接口来定义映射的Entity Class中的属性列到Table ID的映射规则,同时也可以指定ID的规则,默认情况下会以“实体名_ID”形式命名Table的ID。

public class DefaultPrimarykeyConvention : IIdConvention
{
    /// <summary>
    /// 指定Entity Class到Table ID的生成规则,及ID的规则
    /// </summary>
    /// <param name="instance">生成实体的对象</param>
    public void Apply(FluentNHibernate.Conventions.Instances.IIdentityInstance instance)
    {
        instance.Column(instance.EntityType.Name + "ID");
        instance.GeneratedBy.Native();
    }
}

(3)IReferenceConvention

实现该接口指定Entity Class到Table的外键列映射规则,默认情况下会以“外键对象_ID”形式命名外键列。

public class DefaultReferenceConvention : IReferenceConvention
{
    /// <summary>
    /// 指定对象外键的生成规则
    /// </summary>
    /// <param name="instance">外键对象</param>
    public void Apply(IManyToOneInstance instance)
    {
        instance.Column(instance.Name + "ID");
        instance.LazyLoad();

    }
}

(4)IPropertyConvention

Entity Class属性到Table列名称的生成规则及加载规则,同时也可以定义数据库列类型的生成的规则,默认情况下会以属性名作为Table的列名。

public class DefualtStringLengthConvertion : IPropertyConvention
{
    /// <summary>
    /// 配置Table列的名称及类型
    /// </summary>
    /// <param name="instance">对象的属性</param>
    public void Apply(FluentNHibernate.Conventions.Instances.IPropertyInstance instance)
    {
        //设置列的字符串长度为50
        instance.Length(50);
    }
}

(5)IHasOneConvention

Entity Class一对一映射的规则及列的生成规则,同时也可以规定外键对象的加载和操作方式,默认情况下会以“对象_ID”形式命名。

public class DefualtHasOneConvertion : IHasOneConvention
{
    /// <summary>
    /// 一对一映射中的外键列及对从表的操作的类型
    /// </summary>
    /// <param name="instance">关系对象</param>
    public void Apply(IOneToOneInstance instance)
    {
        //指定外键列的列名规则
        instance.ForeignKey(instance.EntityType.Name + "ID");
        //指定主表对从表的操作
        instance.Cascade.SaveUpdate();
        instance.LazyLoad();
    }
}

(6)IHasManyConvention

Entity Class一对多映射的规则及列的生成规则,同时也可以规定外键对象的加载和操作方式,默认情况下会以“对象_ID”形式命名。

public class DefaultHasManyConvention : IHasManyConvention
{
    /// <summary>
    /// 一对多对象的生成规则
    /// </summary>
    /// <param name="instance"></param>
    public void Apply(IOneToManyCollectionInstance instance)
    {
        instance.Key.Column(instance.EntityType.Name + "ID");
        instance.Cascade.AllDeleteOrphan();
        instance.LazyLoad();
    }
}

(7)IHasManyToManyConvention

Entity Class多对多映射的规则及列的生成规则,默认情况下会以“对象_ID”形式命名列名,“对象To对象”形式命名表名。

public class HasManyToManyConvention : IHasManyToManyConvention
{
    /// <summary>
    /// 多对多映射的规则
    /// </summary>
    /// <param name="instance">多对多关系实例</param>
    public void Apply(IManyToManyCollectionInstance instance)
    {
        //指定表名的生成规则,将表名按字符排序较小的一方表名在前方,表名较大的放到后面
        instance.Table(instance.EntityType.Name.ToString()+instance.ChildType.Name.ToString());
        //关联列的一方的命名方式
        instance.Key.Column(instance.EntityType.Name.ToString()+"ID");
        //关系列的另一方的命名方式
        instance.Relationship.Column(instance.ChildType.Name.ToString()+"ID");
    }
}

1.2.3 编写Unit Test测试映射

上文对AutoMapping的映射规则做了详细的讨论,并且添加了映射中的AutoMapping的映射规则的代码,映射的规则其实相当的简单只需要实现相应的映射规则即可,如果不重写映射规则的话将会按照默认的方式生成数据库表结构,所以在创建表结构的时候需要按照一定的规则创建表结构,然后按照自己创建的表结构的规则创建相应的映射规则配置文件,如上文的代码,接下来我们编写Unit Test来测试AutoMapping。

规则创建完成后需要首先运行一次AutoMapping文件,将配置文件设置为如下代码:

public ISessionFactory CreateSessionFactory()
    {
        return Fluently
            .Configure()    //创建或者指定数据库的配置xml,这里使用的是自动创建的方法
            //指定数据库的映射方法,其实它的底层使用的是反射来实现的映射
            .Database(
                MsSqlConfiguration
                    .MsSql2005
                    .ConnectionString(
                        "Data Source=.;Initial Catalog=Mapping;Integrated Security=true;Pooling=True;Min Pool Size=20;Max Pool Size=60")
            //指定连接字符串
                    .ShowSql()
            )
            //配置映射规则
            .Mappings(m => m.AutoMappings.Add(CreateAutomappings()) //将创建的AutoPersistenceModel对象添加到AutoMapping中
                .ExportTo("c:\\path") //导出映射文件
            )
            //生成数据库架构
            //new SchemaExport(cfg).Create(true, false)创建配置文件。
            //这里的Create方法是创建的脚本文件的映射方法,
            //第一个参数bool script指定是否生成数据库脚本
            //第二个参数bool export指定每次生成的数据库的创建脚本是否执行
            .ExposeConfiguration(cfg => new SchemaExport(cfg).Create(true, true))
            .BuildSessionFactory();
    }

这样每次运行时都会检索数据库的表结构并创建相应的数据库表结构,所以在运行一次后要改为.ExposeConfiguration(cfg => new SchemaExport(cfg).Create(true, false))。

使用单元测试方法来测试一对一的映射如下:

[TestFixture]
public class UnitTest1 : Build
{
    [Test]
    public void TestUsers_UserDetails()
    {
        ISessionFactory sessionFactory = CreateSessionFactory();
        ISession Session = sessionFactory.OpenSession();
        //get user from database
        //User user1 = Session.Load<User>(2);

        //save the User data
        Session.Transaction.Begin();

        //User usreUser = Session.Get<User>(2);
        User user = new User()
        {
            Name = "Jack",
            No = "12321"
        };
        UserDetail userDetails = new UserDetail()
        {
            Age = 12,
            BirthDate = DateTime.Now.Date,
            Height = 240,
            Sex = 1
        };
        user.UserDetails = userDetails;
        userDetails.User = user;
        Session.Save(user);
        Session.Save(userDetails);
        Session.Transaction.Commit();
    }
}

运行该Unit Test Method后生成的数据库表如下图:

运行完后插入的表结构的数据如下图:

编写代码测试多对多的映射关系如下:

[TestFixture]
public class UnitTest1:Build
{
    [Test]
    public void TestUsers_UserDetails()
    {
        ISessionFactory sessionFactory = CreateSessionFactory();
        ISession Session=sessionFactory.OpenSession();
        //get user from database
        //User user1 = Session.Load<User>(2);

        //save the User data
        Session.Transaction.Begin();

        //User usreUser = Session.Get<User>(2);
        User user=new User()
        {
            Name = "Jack",
            No = "12321"
        };
        UserDetail userDetails=new UserDetail()
        {
            Age = 12,
            BirthDate = DateTime.Now.Date,
            Height = 240,
            Sex = 1
        };
        user.UserDetails = userDetails;
        userDetails.User = user;
        Session.Save(user);
        Session.Save(userDetails);
        Session.Transaction.Commit();
    }

    [Test]
    public void TestUsers_ProductProject()
    {
        ISessionFactory sessionFactory = CreateSessionFactory();
        ISession Session = sessionFactory.OpenSession();
        //get user from database
        //User user1 = Session.Load<User>(1);

        //save the User data
        Session.Transaction.Begin();
        Project project = new Project()
        {
            Name = "project1"
        };
        Product product1 = new Product()
        {
            Name = "Product1",
            Color = "Red",
        };
        Product product2 = new Product()
        {
            Name = "Product2",
            Color = "Red"
        };
        product1.Project.Add(project);
        product2.Project.Add(project);
        //Project project1 = new Project()
        //{
        //    Name = "project2"
        //};
        project.Product.Add(product1);
        project.Product.Add(product2);
        Session.Save(project);
        Session.Save(product1);
        Session.Save(product2);
        Session.Transaction.Commit();
    }
    [Test]
    public void TestUsers_ProjectProduct()
    {
        ISessionFactory sessionFactory = CreateSessionFactory();
        ISession Session = sessionFactory.OpenSession();
        //get user from database
        //User user1 = Session.Load<User>(1);

        //save the User data
        Session.Transaction.Begin();
        Project project = new Project()
        {
            Name = "project1"
        };
        Project project1 = new Project()
        {
            Name = "project2"
        };
        Product product = new Product()
        {
            Name = "Product1",
            Color = "Red",
        };
        product.Project.Add(project);
        product.Project.Add(project1);
        //project.Product.Add(product);
        //project1.Product.Add(product);

        Session.Save(project);
        Session.Save(project1);
        Session.Save(product);
        Session.Transaction.Commit();
    }
}

运行两个方法后查看生成的相应数据如下:

结语

本文详细讨论了AutoMapping的使用方法,主要讨论了AutoMapping的使用步骤并使用上篇文章中的数据库结构作为实例来演示了AutoMapping代码的实现过程,该Project的源码下载地址为:http://download.csdn.net/detail/zhang_xinxiu/8309201。

虽然使用AutoMapping来实现映射很简单,而且也能够减少代码的冗余,但是在大型项目中并不推荐使用此种方法来映射,因为AutoMapping有太多的不确定性,不够成熟,供查询的资料很少,为了避免风险建议使用FluentNHibernate来映射项目。

时间: 2024-08-03 09:07:47

【架构之路之ORM】--FluentNHibernate之AutoMapping详解的相关文章

HTML5移动开发之路(14)——Video标签详解

本文为 兄弟连IT教育 机构官方 HTML5培训 教程,主要介绍:HTML5移动开发之路(14)--Video标签详解 一.使用技巧 在HTML5中可以使用<audio>或者<video>标签播放html5媒体,使用方式如下: [html] view plaincopyprint? <video src="move.mp4"></video> video标签中有很多属性,例如controls属性可以控制是否有控制台. [html] vie

【架构之路之ORM】--FluentNHibernate之基本映射详解

最近在做项目的时候用到了NHibernate,使用它并不困难,但是很麻烦.如果我的数据库有几百张表如果想要一个个的映射岂不是很麻烦,所以这种情况下使用NHibernate就会很笨重,虽然这个ORM框架本身功能强大,但属于重量级的在使用的时候太笨重了,所以需要在项目中改良.这时候就应运而生了FluentNHibernate,它是流畅版的NHibernate,支持所有的NHibernate功能,而且还封装了配置文件的映射功能,也就是说可以将映射使用C#代码编写,这样在维护时就会很简单. 在没有Flu

【SSH进阶之路】深入源码,详解Struts基本实现流程

通过一步步的封装我们实现了Struts的基本雏形,我们解决了Struts怎么实现MVC的问题,我们现在仅仅有了Struts的基础,对Struts的学习才刚刚开始,这篇我们要通过对比MVC来理解Struts的执行流程,最后深入Struts的源码,一看究竟. MVC M:业务逻辑,业务数据可以重复使用,我们经常说的javabean(其实struts没有实现业务层,也无法实现) V:显示逻辑,同一份数据,对应多中显示方法,JSP代码实现 C:控制流程器,Servlet代码实现. 我们通过时序图看一下M

架构进阶,Dagger2的原理及使用详解

目录 一:Dagger2是什么? 二:为什么要有Dagger2 三:Dagger2如何使用 基本的概念 如何使用Dagger2 高级用法 (1)构造方法需要其他参数时候(2) 模块之间的依赖关系(3) @Named注解使用(4) @Singleton注解(5)自定义Scoped(6)Subcomponent(7)lazy 和 Provider 四: MVP + Dagger2 Ps:文末有架构师进阶资料和面试题资料 一:Dagger2是什么? 是一个依赖注入框架,butterknife也是一个依

linux架构学习第二十五天HTTP协议详解

内容: 1.http协议概述 2.http协议特点 3.http的工作模式(过程) 4.http请求报文.响应报文格式.常见状态码解析 5.web资源概述(静态资源.动态资源) 1.http协议概述 http协议工作在TCP/IP模型的应用层,其定义web服务间通信的约定通信方式,HTTP基于tcp传送数据,默认是80端口(服务器端) 几个名词: http:hyper text transfer protocol,超文本传输协议,超文本传输协议(HTTP)是一种通信协议,它允许将超文本标记语言(

055:ORM外键使用详解

外键:在 MySQL 中,表有两种引擎,一种是 InnoDB ,另外一种是 myisam .如果使用的是 InnoDB 引擎,是支持外键约束的.外键的存在使得 ORM 框架在处理表关系的时候异常的强大.因此这里我们首先来介绍下外键在 Django 中的使用. 类定义为 class ForeignKey(to,on_delete,**options) .第一个参数是引用的是哪个模型,第二个参数是在使用外键引用的模型数据被删除了,这个字段该如何处理,比如有 CASCADE . SET_NULL 等.

软考之高级系统架构设计师(包含2009-2018历年真题详解+课本教程+论文范文+视频教程)

软考-高级系统架构设计师2009-2018历年考试真题以及详细答案(试题和答案分离的哦),同时含有课本教程.系统架构设计师视频教程.系统架构设计师论文范文.持续更新后续年份的资料.请点赞!!请点赞!!!绝对全部货真价实的资料!!!! 全网最全,独此一家,费心整理,希望各位同学顺利通过考试!!! 高级系统架构师历年真题以及答案解析.希赛第四版教程.最新清华版官方指定教程.论文范文等资源下载地址: 百度网盘: https://pan.baidu.com/s/1LDTJgGkuuNPKZ6K8eEzV

[Java开发之路](7)RandomAccessFile类详解

RandomAccessFile适用于大小已知的记录组成的文件,提供的对文件访问,既可以读文件,也可以写文件,并且支持随机访问文件,可以访问文件的任意位置.文件中记录的大小不一定都相同,只要我们知道记录的大小和位置.但是该类仅限于操作文件. RandomAccessFile不属于InputStream和OutputStream继承层次结构中的一部分.除了实现DataInput和DataOutput接口之外(DataInputStream和DataOutputStream也实现了这两个接口),它和

[js高手之路] dom常用节点属性兼容性详解与应用

一.每个DOM节点都有一个nodeType属性,表示节点类型, NodeType一共有12种类型,我们可以通过遍历内置的Node构造函数获取 1 window.onload = function(){ 2 var str = "<table>"; 3 for( var key in Node ){ 4 str += "<tr>"; 5 str += "<td>" + key + "</td>