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

时间:2022-11-23 02:48:20
       最近在做项目的时候用到了NHibernate,使用它并不困难,但是很麻烦。如果我的数据库有几百张表如果想要一个个的映射岂不是很麻烦,所以这种情况下使用NHibernate就会很笨重,虽然这个ORM框架本身功能强大,但属于重量级的在使用的时候太笨重了,所以需要在项目中改良。这时候就应运而生了FluentNHibernate,它是流畅版的NHibernate,支持所有的NHibernate功能,而且还封装了配置文件的映射功能,也就是说可以将映射使用C#代码编写,这样在维护时就会很简单。
       在没有FluentNHibernate的情况下,如果使用NHibernate来做数据库映射,那么首先需要安装NHibernate(也就是应用Nhibernate.dll),然后创建Nhibernate.cfg.xml数据库配置文件,然后创建映射文件.xml,最后创建Session,直接对对象操作即可。虽然这样做并不困难,但是很麻烦,想象下如果数据库表有上百张,那使用这种方法映射不就很麻烦,笨重了吗。
       那么FluentNHibernate有什么好处呢,它能够省略创建映射文件.xml,使用C#代码编写映射文件,这样做能在一定情况下简化工作量,同时也便于对映射代码进行修改,具体使用方法接下来会详细讨论。

一、创建数据库配置文件

首先创建一个数据库的配置文件,刚开始使用的话手动编写太麻烦,这时候可以考虑使用自带的配置文件,在官网下载后会有一个名为Configuration_Templates的文件夹,里面有不同数据库的配置文件,可以使用它的默认设置,但是需要将名称改为Nhibernate.cfg.xml。这里使用如下的配置:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <!-- This is the System.Data.dll provider for SQL Server -->
  3. <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
  4. <session-factory name="KaddzvoteNHibernateFactory">
  5. <property name="connection.driver_class">
  6. NHibernate.Driver.SqlClientDriver
  7. </property>
  8. <property name="connection.connection_string">
  9. Data Source=.;Initial Catalog=Mapping;Integrated Security=true;Pooling=True;Min Pool Size=20;Max Pool Size=60
  10. </property>
  11. <property name="dialect">
  12. NHibernate.Dialect.MsSql2005Dialect
  13. </property>
  14. <property name="current_session_context_class">thread_static</property>
  15. <property name="generate_statistics">true</property>
  16. <property name="proxyfactory.factory_class">NHibernate.Bytecode.DefaultProxyFactoryFactory, NHibernate</property>
  17. <property name="query.substitutions">
  18. true 1, false 0, yes 'Y', no 'N'
  19. </property>
  20. <!--配置是否显示sql语句,true代表显示-->
  21. <property name="show_sql">true</property>
  22. </session-factory>
  23. </hibernate-configuration>
<?xml version="1.0" encoding="utf-8"?>
<!-- This is the System.Data.dll provider for SQL Server -->
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory name="KaddzvoteNHibernateFactory">
<property name="connection.driver_class">
NHibernate.Driver.SqlClientDriver
</property>
<property name="connection.connection_string">
Data Source=.;Initial Catalog=Mapping;Integrated Security=true;Pooling=True;Min Pool Size=20;Max Pool Size=60
</property>
<property name="dialect">
NHibernate.Dialect.MsSql2005Dialect
</property>
<property name="current_session_context_class">thread_static</property>
<property name="generate_statistics">true</property>
<property name="proxyfactory.factory_class">NHibernate.Bytecode.DefaultProxyFactoryFactory, NHibernate</property>
<property name="query.substitutions">
true 1, false 0, yes 'Y', no 'N'
</property>
<!--配置是否显示sql语句,true代表显示-->
<property name="show_sql">true</property>
</session-factory>
</hibernate-configuration>

二、创建实体

NHibernate的基本映射和Hibernate是完全相同的,有关基本的映射这里不再详细的讨论,可以翻阅笔者的前几篇文章。下面自己做了一个小的项目Demo,演示如何使用NHibernate创建一个数据库的映射,具体的数据库结构图如下:

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

上图的数据库结构图中涵盖了基本的映射关系,在实际的项目中也就是上面出现的几种基本的关系,其中涵盖了一对一、多对一、多对多的关联关系,接下来将会使用FluentNHibernate来实现基本的映射关系。

2.1 创建实体

添加完配置文件后使用第三方工具将数据库表导出为实体对象,也就是添加数据库表的实体对象。添加完成后继续添加数据库的映射类,添加映射类时需要继承NHibernate的ClassMap<T>类,将数据库实体放置到对象内部,这样在映射时能够直接使用,它使用的是泛型来实现的。数据库表的实体如下代码:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.ObjectModel;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. using FluentNHibernate.Automapping;
  8. using FluentNHibernate.Conventions;
  9. using NHibernate;
  10. using NHibernate.Collection.Generic;
  11. namespace ClassLibrary1.mapping
  12. {
  13. public abstract class Entity
  14. {
  15. virtual public int ID { get; set; }
  16. }
  17. public class User : Entity
  18. {
  19. virtual public string Name { get; set; }
  20. public virtual string No { get; set; }
  21. public virtual UserDetails UserDetails { get; set; }
  22. }
  23. public class Project : Entity
  24. {
  25. public Project()
  26. {
  27. Task=new List<Task>();
  28. Product=new List<Product>();
  29. }
  30. public virtual string Name { get; set; }
  31. public virtual User User { get; set; }
  32. public virtual IList<Product> Product { get; set; }
  33. public virtual IList<Task> Task{get;protected set; }
  34. }
  35. public class Product : Entity
  36. {
  37. public Product()
  38. {
  39. Project=new List<Project>();
  40. }
  41. public virtual IList<Project> Project { get; set; }
  42. public virtual string Name { get; set; }
  43. public virtual string Color { get; set; }
  44. }
  45. public class Task : Entity
  46. {
  47. public virtual string Name { get; set; }
  48. public virtual Project Project { get; set; }
  49. }
  50. public class UserDetails : Entity
  51. {
  52. public virtual User User { get; set; }
  53. public virtual int Sex { get; set; }
  54. public virtual int Age { get; set; }
  55. public virtual DateTime BirthDate { get; set; }
  56. public virtual decimal Height { get; set; }
  57. }
  58. }
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FluentNHibernate.Automapping;
using FluentNHibernate.Conventions;
using NHibernate;
using NHibernate.Collection.Generic; namespace ClassLibrary1.mapping
{ public abstract class Entity
{
virtual public int ID { get; set; }
} public class User : Entity
{
virtual public string Name { get; set; }
public virtual string No { get; set; } public virtual UserDetails UserDetails { get; set; }
} public class Project : Entity
{
public Project()
{
Task=new List<Task>();
Product=new List<Product>();
} public virtual string Name { get; set; }
public virtual User User { get; set; } public virtual IList<Product> Product { get; set; }
public virtual IList<Task> Task{get;protected set; } } public class Product : Entity
{
public Product()
{
Project=new List<Project>();
} public virtual IList<Project> Project { get; set; }
public virtual string Name { get; set; }
public virtual string Color { get; set; }
} public class Task : Entity
{
public virtual string Name { get; set; }
public virtual Project Project { get; set; }
} public class UserDetails : Entity
{
public virtual User User { get; set; }
public virtual int Sex { get; set; }
public virtual int Age { get; set; }
public virtual DateTime BirthDate { get; set; }
public virtual decimal Height { get; set; }
}
}

三、映射详解

在添加映射文件时需要继承FluentNHibernate的ClassMap<T>类,然后在类的构造函数中添加映射的方法,具体的方法是使用的lamuda表达式来映射的,方法的名称跟配置文件的名称基本一致,书写也很方便,接下来将会拆分映射来详细讲解FluentNHibernate的Mapping使用方法。

3.1 一对一映射

首先来看看一对一的映射关系,用户和用户信息表在实际中是一对一的关系,这两个表之间是通过使用UserID来相互关联的,它们有一个共同的ID,在插入Users表的同时也要写入UserDetails表,所以需要添加一对一的限制关系,具体的在Users和UsersDetails两表的映射方法如下代码:

  1. public class UsersMapping : ClassMap<User>
  2. {
  3. public UsersMapping()
  4. {
  5. Table("Users");
  6. LazyLoad();
  7. Id(x => x.ID).Column("UserID").GeneratedBy.Identity();
  8. HasOne(x => x.UserDetails).Cascade.All().PropertyRef("User");
  9. Map(x => x.Name).Nullable();
  10. Map(x => x.No).Nullable();
  11. }
  12. }
public class UsersMapping : ClassMap<User>
{
public UsersMapping()
{
Table("Users");
LazyLoad();
Id(x => x.ID).Column("UserID").GeneratedBy.Identity();
HasOne(x => x.UserDetails).Cascade.All().PropertyRef("User");
Map(x => x.Name).Nullable();
Map(x => x.No).Nullable(); }
}

Note:FluentNHibernate在映射时有很多种映射方法,比如Cascade它是指该对象在进行操作时关联到的子对象的操作类型,上面指定了All说明所有的操作都会关联到子表,还有SaveUpdate在添加和更新时关联子表,另外还有None类型不推荐使用此类型因为会出现很多问题。



        UserDetails的映射中的主键ID是继承自User类的所以在指定ID时需要添加Foreign外键关联的属性名,内部的参数一定要是UserDetails的属性名。

  1. public class UserDetailsMapping : ClassMap<UserDetails>
  2. {
  3. public UserDetailsMapping()
  4. {
  5. Table("UserDetails");
  6. LazyLoad();
  7. Id(x => x.ID).Column("UserID").GeneratedBy.Foreign("User");
  8. Map(x => x.Height).Nullable();
  9. Map(x => x.Age).Nullable();
  10. Map(x => x.Sex).Nullable();
  11. Map(x => x.BirthDate).Nullable();
  12. HasOne(x => x.User).Cascade.All();
  13. }
  14. }
public class UserDetailsMapping : ClassMap<UserDetails>
{
public UserDetailsMapping()
{
Table("UserDetails");
LazyLoad();
Id(x => x.ID).Column("UserID").GeneratedBy.Foreign("User");
Map(x => x.Height).Nullable();
Map(x => x.Age).Nullable();
Map(x => x.Sex).Nullable();
Map(x => x.BirthDate).Nullable();
HasOne(x => x.User).Cascade.All();
}
}

使用测试方法查看映射结果,具体的测试方法如下:

  1. using System;
  2. using System.Collections.Generic;
  3. using ClassLibrary1.mapping;
  4. using FluentNHibernate.Testing;
  5. using ClassLibrary1;
  6. using NHibernate;
  7. using NUnit.Framework;
  8. namespace UnitTestProject1
  9. {
  10. [TestFixture]
  11. public class UnitTest1:NHConfig
  12. {
  13. [Test]
  14. public void TestUsers_UserDetails()
  15. {
  16. //get user from database
  17. User user1 = Session.Load<User>(1);
  18. //save the User data
  19. Session.Transaction.Begin();
  20. User user=new User()
  21. {
  22. Name = "Jack",
  23. No = "12321"
  24. };
  25. UserDetails userDetails=new UserDetails()
  26. {
  27. Age = 12,
  28. BirthDate = DateTime.Now.Date,
  29. Height = 240,
  30. Sex = 1
  31. };
  32. user.UserDetails = userDetails;
  33. userDetails.User = user;
  34. Session.Save(user);
  35. Session.Transaction.Commit();
  36. }
  37. }
  38. }
using System;
using System.Collections.Generic;
using ClassLibrary1.mapping;
using FluentNHibernate.Testing;
using ClassLibrary1;
using NHibernate;
using NUnit.Framework;
namespace UnitTestProject1
{
[TestFixture]
public class UnitTest1:NHConfig
{
[Test]
public void TestUsers_UserDetails()
{
//get user from database
User user1 = Session.Load<User>(1); //save the User data
Session.Transaction.Begin();
User user=new User()
{
Name = "Jack",
No = "12321"
};
UserDetails userDetails=new UserDetails()
{
Age = 12,
BirthDate = DateTime.Now.Date,
Height = 240,
Sex = 1
};
user.UserDetails = userDetails;
userDetails.User = user;
Session.Save(user);
Session.Transaction.Commit();
}
}
}

在get和save对象的地方添加断点,Debug运行测试就会看到执行的结果。

3.2 一对多/多对一

一对多和多对一是相对而言的正如上例中的Projects和Tasks类似,一个Projects有多个Tasks,反过来说就是多个Tasks可能会对应一个Projects所以有时一对多的关系也就是多对一的关系,只不过是一种特殊的多对一。这里的多对一比较特殊,常见的多对一的关系比如学生和班级的关系,多个学生属于一个班级。

         一对多的映射方法和一对一的映射方法其实很多地方是类似的,只不过一对多的关系里面要添加一个外键引用关系,然后在多的一端添加一个外键,在一的一端添加HasMany,映射到Projects和Tasks中就是在Tasks(多)中添加Project的外键。

3.2.1 映射

首先时Tasks表的映射,因为Tasks表是多的一端,所以要添加对Projects表的外键引用关系,另外因为是一种外键引用不关系到父表的操作,所以这里可以使用Cascade.None()。

  1. public class TasksMappping : ClassMap<ClassLibrary1.mapping.Task>
  2. {
  3. public TasksMappping()
  4. {
  5. Table("Tasks");
  6. LazyLoad();
  7. Id(x => x.ID).Column("TaskID").GeneratedBy.Identity();
  8. References(x => x.Project).Nullable().Column("ProjectID").Cascade.None();
  9. Map(x => x.Name).Nullable();
  10. }
  11. }
public class TasksMappping : ClassMap<ClassLibrary1.mapping.Task>
{
public TasksMappping()
{
Table("Tasks");
LazyLoad();
Id(x => x.ID).Column("TaskID").GeneratedBy.Identity();
References(x => x.Project).Nullable().Column("ProjectID").Cascade.None();
Map(x => x.Name).Nullable();
}
}

在Projects表中,因为该表中的一个ID会对应多个Tasks所以在添加HasMany方法,来表明Projects和Tasks的多对一的关系,如下代码它会涉及到任务的添加和更新操作,所以需要使用Cascade.SaveUpdate()。

  1. public class ProjectsMapping:ClassMap<Project>
  2. {
  3. public ProjectsMapping()
  4. {
  5. Table("Projects");
  6. LazyLoad();
  7. Id(x => x.ID).Column("ProjectID").GeneratedBy.Identity();
  8. References(x => x.User).Column("UserID").Cascade.None();
  9. Map(x => x.Name).Nullable();
  10. HasMany(x => x.Task).KeyColumn("ProjectID").LazyLoad().Cascade.SaveUpdate();
  11. }
  12. }
public class ProjectsMapping:ClassMap<Project>
{
public ProjectsMapping()
{
Table("Projects");
LazyLoad();
Id(x => x.ID).Column("ProjectID").GeneratedBy.Identity();
References(x => x.User).Column("UserID").Cascade.None();
Map(x => x.Name).Nullable();
HasMany(x => x.Task).KeyColumn("ProjectID").LazyLoad().Cascade.SaveUpdate();
}
}

Note:TasksMapping中的外键关系使用的是Cascade.None这说明它的操作不会涉及到Projects的操作,但是ProjectsMapping中使用了Cascade.SaveUpdate()方法所以在save或者update Projects的时候会连带着修改Tasks。

3.2.2 Unit Test

编写单元测试代码如下:

  1. [Test]
  2. public void TestOneToMany()
  3. {
  4. Project project = Session.Get<Project>(15);
  5. //save the User data
  6. Session.Transaction.Begin();
  7. Task task = new Task()
  8. {
  9. Name ="create",
  10. Project = project
  11. };
  12. Session.Save(task);
  13. Session.Transaction.Commit();
  14. Task task1 = Session.Get<Task>(1);
  15. }
[Test]
public void TestOneToMany()
{
Project project = Session.Get<Project>(15); //save the User data
Session.Transaction.Begin();
Task task = new Task()
{
Name ="create",
Project = project
};
Session.Save(task);
Session.Transaction.Commit(); Task task1 = Session.Get<Task>(1);
}

执行查看结果:

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

这里使用的一对多的关联只是单向的关联,在关联中不仅有单向的另外还有双向关联,具体使用方法这里不再详细讨论,有兴趣学习的话可以翻阅笔者的前篇文章有关Hibernate的关联关系。

3.3 多对多


上文详细讨论了一对一、多对一/一对多的关系,使用FluentNHibernate来映射这种关系就很简单了,最后继续讨论多对多的关系,多对多的关系在使用的时候更类似于一对一的关系,因为它属于双向的关联,所以需要在关联的两端同时添加HasManyToMany的映射方法,另外反应到数据库中这其实是需要建立关联表,利用第三张表来维护双向的关系,具体的使用方法如下实例。

3.3.1 映射

在项目中常见的多对多的关系有很多,比如本例中使用的Product和Project的关系,一个Project会有有很多Product,同时一个Product也可能会在多个Project中,它们之间就形成了多对多的关联关系。反映到映射关系中,代码如下:

  1. public class ProjectsMapping:ClassMap<Project>
  2. {
  3. public ProjectsMapping()
  4. {
  5. Table("Projects");
  6. LazyLoad();
  7. Id(x => x.ID).Column("ProjectID").GeneratedBy.Identity();
  8. References(x => x.User).Column("UserID").Cascade.None();
  9. Map(x => x.Name).Nullable();
  10. HasMany(x => x.Task).KeyColumn("ProjectID").LazyLoad().Cascade.SaveUpdate();
  11. HasManyToMany(x => x.Product).ParentKeyColumn("ProjectID").ChildKeyColumn("ProductID").Table("ProjectProduct");
  12. }
  13. }
  14. public class ProductMapping : ClassMap<Product>
  15. {
  16. public ProductMapping()
  17. {
  18. Table("Product");
  19. Id(x => x.ID).Column("ProductID").GeneratedBy.Identity();
  20. Map(x => x.Name).Nullable();
  21. Map(x => x.Color).Nullable();
  22. HasManyToMany(x => x.Project).ParentKeyColumn("ProductID").ChildKeyColumn("ProjectID").Table("ProjectProduct");
  23. }
  24. }
public class ProjectsMapping:ClassMap<Project>
{
public ProjectsMapping()
{
Table("Projects");
LazyLoad();
Id(x => x.ID).Column("ProjectID").GeneratedBy.Identity();
References(x => x.User).Column("UserID").Cascade.None();
Map(x => x.Name).Nullable();
HasMany(x => x.Task).KeyColumn("ProjectID").LazyLoad().Cascade.SaveUpdate();
HasManyToMany(x => x.Product).ParentKeyColumn("ProjectID").ChildKeyColumn("ProductID").Table("ProjectProduct");
}
} public class ProductMapping : ClassMap<Product>
{
public ProductMapping()
{
Table("Product");
Id(x => x.ID).Column("ProductID").GeneratedBy.Identity();
Map(x => x.Name).Nullable();
Map(x => x.Color).Nullable();
HasManyToMany(x => x.Project).ParentKeyColumn("ProductID").ChildKeyColumn("ProjectID").Table("ProjectProduct");
}
}

具体添加关联的步骤如下:

               (1)在映射的两端同时添加HasManyToMany的关系这样就形成了双向的关联关系

               (2)指定映射的ParentKey和ChildKey,一般会将对象本身的ID指定为ParentKey,关联对象的ID指定为ChildKey

               (3)指定关联关系的关系表,使用Table方法指定关联表,如上示例的Table("ProjectProduct")。

3.3.2  Unit Test

最后添加一个测试方法来查看映射的结果,是否实现了多对多的映射关系,具体测试方法如下。

  1. [Test]
  2. public void TestManyToMany()
  3. {
  4. Session.Transaction.Begin();
  5. //get the Project
  6. ICriteria query = Session.CreateCriteria<Project>();
  7. IList<Project> projects = query.List<Project>();
  8. //create the Product
  9. Product product=new Product()
  10. {
  11. Name = "Product1",
  12. Color = "Red"
  13. };
  14. product.Project = projects;
  15. Session.Save(product);
  16. Session.Transaction.Commit();
  17. }
[Test]
public void TestManyToMany()
{
Session.Transaction.Begin();
//get the Project
ICriteria query = Session.CreateCriteria<Project>();
IList<Project> projects = query.List<Project>(); //create the Product
Product product=new Product()
{
Name = "Product1",
Color = "Red"
};
product.Project = projects;
Session.Save(product);
Session.Transaction.Commit();
}

Debug运行测试方法,运行到get projects处查看projects所获取的对象信息如下图:

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

从上图可以看出已经获取到了与projects相关联的Product,这就是多对多的关系,在获取projects时同时获取了与它关联的Products,如果这里使用Lazyload方式的话就不会获取所有的信息,所以要根据具体的情况而定。



        继续运行测试,成功执行。

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



       查看数据库发现数据已经成功添加,如下图:

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

结语

本文主要讨论了FluentNHibernate的基本使用技巧,突出讨论了一对一的双向关联映射,一对多的单向关联和多对多的双向关联关系,它们使用相当简单,因为有了FluentNHibernate,只需要了解关联的规则就可以了,从数据模型到对象模型真的就很简单了。

虽然使用FluentNHibernate在一定程度上减少了编写代码,但是并不能真正的解决代码冗余的繁琐问题,可否有一中不需要编写Mapping代码的方法来实现映射关系呢?是的FluentNHibernate还封装了一种AutoMapping方式来映射对象,是一种自动映射的方法,只需要继承实现数据库表到对象的转换规则就可以了,具体的实现方法将会在下篇文章中详细讨论。

版权声明:本文为博主原创文章,未经博主允许不得转载。