【架构之路之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来映射项目。


郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。