摘要

上一篇文章介绍了Fluent NHibernate基础知识。但是,Fluent NHibernate提供了一种更方便的Mapping方法称为Auto Mapping。只需在代码中定义一些Convention继承类,针对具体的属性、主键、关系、组件指定Mapping的规则,在实体类里定义简单的POCO对象就可以完成整个数据库的自动映射。Auto Mapping适合全新的系统开发,即是在系统设计时还没有数据库的时候。有点像Microsoft Entity Framework的Code First或是Model First的开发方式。

这篇文章介绍Fluent Mapping。本篇文章的代码可以到Fluent Auto Mapping下载。

1、Auto Mapping的优缺点

优点:

  • 更少的Mapping代码,因为不需要大量显式定义映射关系。
  • 数据库的Schema跟Model的定义更接近。
  • 程序员可以把更多的精力放在特殊的映射关系上。因为定义的那些Convention已经帮你完成了大部分的工作了。

缺点:

  • 因为大部分的映射都由Convention定义,不能方便地在细节上定义一些具体的映射关系。
  • 对于已经存在的数据库系统,不太适合使用Auto Mapping。

2、程序演示

1)新建控制台应用程序工程Demo.Auto.Entities。

2)在新建的工程中,使用NuGet安装FluentNHibernate。

3)添加Enum、Domain文件夹和Mapping文件夹。

4)在Enum文件夹内添加文件Enums.cs。

  1. namespace Demo.Auto.Entities.Enum
  2. {
  3. public enum CustomerCreditRating
  4. {
  5. Excellent, VeryVeryGood, VeryGood, Good, Neutral, Poor, Terrible
  6. }
  7. }

5)在Domain文件夹内添加文件Entity.cs。

  1. namespace Demo.Auto.Entities.Domain
  2. {
  3. public abstract class Entity
  4. {
  5. public virtual int Id { get; private set; }
  6. }
  7. }

Entity类是所有实体类的基类,包含主键属性Id。

6)在Domain文件夹内添加Name.cs和Address.cs。

Name类

  1. using System;
  2.  
  3. namespace Demo.Auto.Entities.Domain
  4. {
  5. public class Name
  6. {
  7. public string LastName { get; set; }
  8. public string FirstName { get; set; }
  9.  
  10. public Name() { }
  11.  
  12. public Name(string firstName, string lastName)
  13. {
  14. if (string.IsNullOrWhiteSpace(firstName))
  15. {
  16. throw new ArgumentException("First name must be defined.");
  17. }
  18. if (string.IsNullOrWhiteSpace(lastName))
  19. {
  20. throw new ArgumentException("Last name must be defined.");
  21. }
  22. FirstName = firstName;
  23. LastName = lastName;
  24. }
  25.  
  26. public override int GetHashCode()
  27. {
  28. unchecked
  29. {
  30. var result = FirstName.GetHashCode();
  31. result = (result * ) ^ LastName.GetHashCode();
  32. return result;
  33. }
  34. }
  35.  
  36. public bool Equals(Name other)
  37. {
  38. if (other == null) return false;
  39. if (ReferenceEquals(this, other)) return true;
  40. return Equals(other.FirstName, FirstName) &&
  41. Equals(other.LastName, LastName);
  42. }
  43.  
  44. public override bool Equals(object other)
  45. {
  46. return Equals(other as Name);
  47. }
  48. }
  49. }

Address类

  1. namespace Demo.Auto.Entities.Domain
  2. {
  3. public class Address
  4. {
  5. public virtual string Street { get; set; }
  6. public virtual string City { get; set; }
  7. public virtual string Province { get; set; }
  8. public virtual string Country { get; set; }
  9.  
  10. public bool Equals(Address other)
  11. {
  12. if (other == null) return false;
  13. if (ReferenceEquals(this, other)) return true;
  14. return Equals(other.Street, Street) &&
  15. Equals(other.City, City) &&
  16. Equals(other.Province, Province) &&
  17. Equals(other.Country, Country);
  18. }
  19.  
  20. public override bool Equals(object obj)
  21. {
  22. return Equals(obj as Address);
  23. }
  24.  
  25. public override int GetHashCode()
  26. {
  27. unchecked
  28. {
  29. var result = Street.GetHashCode();
  30. result = (result * ) ^ (City != null ? City.GetHashCode() : );
  31. result = (result * ) ^ Province.GetHashCode();
  32. result = (result * ) ^ Country.GetHashCode();
  33. return result;
  34. }
  35. }
  36. }
  37. }

Name类和Address类跟上一篇文章的Name类和Address类的代码一样,保持不变。

7)添加实体类Customer类、Product类和Order类。

Customer类

  1. using Demo.Auto.Entities.Enum;
  2. using System;
  3. using System.Collections.Generic;
  4.  
  5. namespace Demo.Auto.Entities.Domain
  6. {
  7. public class Customer : Entity
  8. {
  9. public Customer()
  10. {
  11. MemberSince = DateTime.UtcNow;
  12. }
  13.  
  14. public virtual Name Name { get; set; }
  15. public virtual double AverageRating { get; set; }
  16. public virtual int Points { get; set; }
  17. public virtual bool HasGoldStatus { get; set; }
  18. public virtual DateTime MemberSince { get; set; }
  19. public virtual CustomerCreditRating CreditRating { get; set; }
  20. public virtual Address Address { get; set; }
  21. public virtual IList<Order> Orders { get; set; }
  22. }
  23. }

Customer类继承Entity类,继承主键属性Id。

集合属性用IList接口定义。

Product类

  1. using System.Collections.Generic;
  2.  
  3. namespace Demo.Auto.Entities.Domain
  4. {
  5. public class Product : Entity
  6. {
  7. public virtual string ProductCode { get; set; }
  8.  
  9. public virtual string ProductName { get; set; }
  10.  
  11. public virtual string Description { get; set; }
  12.  
  13. public virtual IList<Order> Orders { get; set; }
  14. }
  15. }

Order类

  1. using System;
  2. using System.Collections.Generic;
  3.  
  4. namespace Demo.Auto.Entities.Domain
  5. {
  6. public class Order : Entity
  7. {
  8. public virtual DateTime Ordered { get; set; }
  9. public virtual DateTime? Shipped { get; set; }
  10. public virtual Address ShipTo { get; set; }
  11. public virtual Customer Customer { get; set; }
  12. public virtual IList<Product> Products { get; set; }
  13. }
  14. }

8)在Mapping文件夹下添加文件AutoMappingConfiguration。

  1. using Demo.Auto.Entities.Domain;
  2. using FluentNHibernate.Automapping;
  3. using FluentNHibernate.Conventions;
  4. using FluentNHibernate.Conventions.Instances;
  5. using System;
  6. using FluentNHibernate;
  7.  
  8. namespace Demo.Auto.Entities.Mapping
  9. {
  10.  
  11. }
  • 在namespace Demo.Auto.Entities.Mapping里添加DefaultAutomappingConfiguration的继承类AutoMappingConfiguration。设置哪些类型被映射成实体类,哪些类型被映射成组件类。
  1. public class AutoMappingConfiguration : DefaultAutomappingConfiguration
  2. {
  3. /// <summary>
  4. /// 类型是否是实体映射类型
  5. /// </summary>
  6. /// <param name="type"></param>
  7. /// <returns></returns>
  8. public override bool ShouldMap(Type type)
  9. {
  10. //跟Customer类在一个名称空间的所有的类都被映射
  11. return type.Namespace == typeof(Customer).Namespace;
  12. }
  13.  
  14. /// <summary>
  15. /// 类型是否是值对象映射类型
  16. /// </summary>
  17. /// <param name="type"></param>
  18. /// <returns></returns>
  19. public override bool IsComponent(Type type)
  20. {
  21. //指定Address类和Name类是值对象映射类型
  22. return type == typeof(Address)
  23. || type == typeof(Name);
  24. }
  25.  
  26. /// <summary>
  27. /// 映射值对象类型属性到数据库字段名
  28. /// </summary>
  29. /// <param name="member">值对象属性</param>
  30. /// <returns></returns>
  31. public override string GetComponentColumnPrefix(Member member)
  32. {
  33. //映射到数据库列名的前缀为空。默认生成的组件列列名是类名+属性名。例:CustomerCity
  34. return "";
  35. }
  36. }
  • 添加IIdConvention接口的继承类IdConvention。指定主键列列名、主键生成策略。
  1. public class IdConvention : IIdConvention
  2. {
  3. public void Apply(IIdentityInstance instance)
  4. {
  5. instance.GeneratedBy.Native();
  6. }
  7. }

这里指定所有的主键列的生成策略是Native的。默认的主键列名称是Id。

  • 添加IPropertyConvention接口的继承类DefaultStringLengthConvention。指定一般属性的通用映射规则。
  1. public class DefaultStringLengthConvention : IPropertyConvention
  2. {
  3. public void Apply(IPropertyInstance instance)
  4. {
  5. instance.Length();
  6. }
  7. }

这里指定所有string类型属性的长度是250个字符。默认是255。

  • 添加IHasManyToManyConvention接口的继承类HasManyToManyConvention。指定Many-to-Many映射的一般规则。
  1. public class HasManyToManyConvention : IHasManyToManyConvention
  2. {
  3. public void Apply(IManyToManyCollectionInstance instance)
  4. {
  5. //指定主键列列名是属性名+Id,例:ProductId
  6. instance.Key.Column(instance.EntityType.Name + "Id");
  7. //指定外键列列名是属性名+Id,例:OrderId
  8. instance.Relationship.Column(instance.Relationship.StringIdentifierForModel + "Id");
  9.  
  10. var firstName = instance.EntityType.Name; //主表映射类属性名
  11. var secondName = instance.ChildType.Name; //从表映射类属性名
  12. //定义关系的中间表表名。按主表和从表属性名的字母顺序设置中间表表名。
  13. //例:Product和Order,按字母顺序,字符串"Product"在"Order"之前,中间表表名设置为"ProductOrder"。
  14. //控制反转只设置成只有一个方向。
  15. if (StringComparer.OrdinalIgnoreCase.Compare(firstName, secondName) > )
  16. {
  17. instance.Table(string.Format("{0}{1}", firstName, secondName));
  18. instance.Not.Inverse(); //不反转
  19. }
  20. else
  21. {
  22. instance.Table(string.Format("{0}{1}", secondName, firstName));
  23. instance.Inverse(); //反转
  24. }
  25. //级联更新Casade,两个方向都设置成All
  26. instance.Cascade.All();
  27. }
  28. }

详细说明见代码中注释。

  • 添加IHasManyConvention接口的继承类HasOneToManyConvention。指定One-to-Many的一般映射规则。
  1. public class HasOneToManyConvention : IHasManyConvention
  2. {
  3. public void Apply(IOneToManyCollectionInstance instance)
  4. {
  5. //指定从表的外键列列名是属性名+Id,例:CustomerId
  6. instance.OtherSide.Column(instance.OtherSide.Name + "Id");
  7. //级联更新Casade:主表到从表设置成All
  8. instance.Cascade.All();
  9. }
  10. }

9)在Mapping文件夹下添加文件MappingOverride.cs。在这个文件里添加一些继承IAutoMappingOverride接口的类,可以对具体的一些实体类的映射进行重写。

  1. using Demo.Auto.Entities.Domain;
  2. using FluentNHibernate.Automapping;
  3. using FluentNHibernate.Automapping.Alterations;
  4.  
  5. namespace Demo.Auto.Entities.Mapping
  6. {
  7.  
  8. }

在namespace Demo.Auto.Entities.Mapping下添加三个类:CustomerMappingOverride、ProductMappingOverride、OrderMappingOverride。分别对实体类Customer、Product、Order的映射进行部分重写。

  1. public class CustomerMappingOverride : IAutoMappingOverride<Customer>
  2. {
  3. public void Override(AutoMapping<Customer> mapping)
  4. {
  5. mapping.Map(x => x.CreditRating).CustomType<Enum.CustomerCreditRating>();
  6. mapping.HasMany(x => x.Orders).Inverse().Cascade.AllDeleteOrphan().Fetch.Join();
  7. }
  8. }
  9.  
  10. public class ProductMappingOverride : IAutoMappingOverride<Product>
  11. {
  12. public void Override(AutoMapping<Product> mapping)
  13. {
  14. mapping.Map(x => x.ProductCode).Not.Nullable().Length();
  15. mapping.Map(x => x.ProductName).Not.Nullable().Length();
  16. mapping.HasManyToMany(x => x.Orders).Cascade.AllDeleteOrphan();
  17. }
  18. }
  19.  
  20. public class OrderMappingOverride : IAutoMappingOverride<Order>
  21. {
  22. public void Override(AutoMapping<Order> mapping)
  23. {
  24. mapping.References(x => x.Customer).Cascade.SaveUpdate();
  25. }
  26. }

10)修改Main函数,测试Auto Mapping。

  1. using Demo.Auto.Entities.Domain;
  2. using Demo.Auto.Entities.Mapping;
  3. using FluentNHibernate.Automapping;
  4. using FluentNHibernate.Cfg;
  5. using FluentNHibernate.Cfg.Db;
  6. using NHibernate.Tool.hbm2ddl;
  7. using System;
  8.  
  9. namespace Demo.Auto.Entities
  10. {
  11. class Program
  12. {
  13. const string connString = "server=localhost;" + "database=NHibernateDemoDB;" + "integrated security=SSPI;";
  14. static void Main(string[] args)
  15. {
  16. var cfg = new AutoMappingConfiguration();
  17. var configuration = Fluently.Configure()
  18. .Database(MsSqlConfiguration.MsSql2008.ConnectionString(connString))
  19. .Mappings(m =>
  20. m.AutoMappings.Add(AutoMap.AssemblyOf<Customer>(cfg)
  21. .Conventions.Setup(c =>
  22. {
  23. c.Add<IdConvention>();
  24. c.Add<DefaultStringLengthConvention>();
  25. c.Add<HasOneToManyConvention>();
  26. c.Add<HasManyToManyConvention>();
  27. })
  28. .UseOverridesFromAssemblyOf<CustomerMappingOverride>()
  29. .UseOverridesFromAssemblyOf<ProductMappingOverride>()
  30. .UseOverridesFromAssemblyOf<OrderMappingOverride>()
  31. ).ExportTo(@"c:\daniel"))
  32. .BuildConfiguration();
  33.  
  34. var exporter = new SchemaExport(configuration);
  35. exporter.Execute(true, false, false);
  36.  
  37. Console.Write("Hit enter to exit:");
  38. Console.ReadLine();
  39. }
  40. }
  41. }
  • FluentConfiguration对象的Mapping方法传入Lamda表达式指定Mapping方式。
  • AutoMap.AssemblyOf<Customer>(cfg):指定使用自动映射,传入使用自定义类AutoMappingConfiguration的对象cfg,按自定义类AutoMappingConfiguration中的重载方法进行映射。方法调用生成AutoPersistenceModel对象。
  • AutoPersistenceModel对象的Conventions.Setup方法传入Lamda表达式,添加一系列的Convention。
  • AutoPersistenceModel对象的UseOverridesFromAssemblyOf方法,传入继承于IAutoMappingOverride接口的类作为泛型参数,添加一系列的Override。
  • ExportTo(@"c:\daniel"))方法将自动映射的定义xml文件导出到文件夹c:\daniel。
  1. var exporter = new SchemaExport(configuration);
  2. exporter.Execute(true, false, false);

这两行代码生成创建数据库表的SQL语句。SchemaExport对象的Execute方法传入三个bool类型参数。第一个参数表示是否将SQL语句显示到控制台,第二个参数表示是否立即执行SQL语句,第三个参数表示是否删除并重建数据库表。

在C盘下创建文件夹daniel,执行程序,得到控制台输出:

到C:\daniel文件夹下,看到生成的三个xml配置文件。

打开这三个文件,看到跟手写的映射文件是一样的。

结语

Fluent NHibernate提供的Auto Mapping确实是一个很方便的方式,大量地减少了手写映射的代码量。对于新的项目的确是一个不错的映射方式。有兴趣的可以到Fluent NHibernate官网http://www.fluentnhibernate.org上去查看更详细的内容。

NHibernate系列文章二十八:NHibernate Mapping之Auto Mapping(附程序下载)的更多相关文章

  1. NHibernate系列文章二十:NHibernate关系之一对一(附程序下载)

    摘要 NHibernate一对一关系虽然不经常碰到,但是在对于数据库结构优化的时候,经常会碰到一对一关系.比如,产品详细信息比较多的时候,可以把产品详细信息放到另一张表里面,Product主表只记录产 ...

  2. WCF技术剖析之二十八:自己动手获取元数据[附源代码下载]

    原文:WCF技术剖析之二十八:自己动手获取元数据[附源代码下载] 元数据的发布方式决定了元数据的获取行为,WCF服务元数据架构体系通过ServiceMetadataBehavior实现了基于WS-ME ...

  3. NHibernate系列文章二十七:NHibernate Mapping之Fluent Mapping基础(附程序下载)

    摘要 从这一节起,介绍NHibernate Mapping的内容.前面文章都是使用的NHibernate XML Mapping.NHibernate XML Mapping是NHibernate最早 ...

  4. NHibernate系列文章二十四:NHibernate查询之Linq查询(附程序下载)

    摘要 NHibernate从3.0开始支持Linq查询.写Linq to NHibernate查询就跟写.net linq代码一样,非常灵活,可以很容易实现复杂的查询.这篇文章使用Linq to NH ...

  5. NHibernate系列文章二十五:NHibernate查询之Query Over查询(附程序下载)

    摘要 这一篇文章介绍在NHibernate 3.2里引入的Query Over查询,Query Over查询跟Criteria查询类似.首先创建IQueryOver对象,然后通过调用该对象的API函数 ...

  6. NHibernate系列文章二十二:NHibernate查询之HQL查询(附程序下载)

    摘要 NHibernate提供了多种查询方式,最早的HQL语言查询.Criteria查询和SQL Query,到NHibernate 3.0的Linq NHibernate,NHIbernate 4. ...

  7. NHibernate系列文章二十六:NHibernate查询之SQL Query查询(附程序下载)

    摘要 NHibernate在很早的版本就提供了SQL Query(原生SQL查询),对于很复杂的查询,如果使用其他的查询方式实现比较困难的时候,一般使用SQL Query.使用SQL Query是基于 ...

  8. NHibernate系列文章二十三:NHibernate查询之Criteria查询(附程序下载)

    摘要 上一篇文章介绍了NHibernate HQL,他的缺点是不能够在编译时发现问题.如果数据库表结构有改动引起了实体关系映射的类有改动,要同时修改这些HQL字符串.这篇文章介绍NHibernate面 ...

  9. NHibernate系列文章二:创建NHibernate工程

    摘要 这篇文章介绍了如何创建一个简单的使用NHibernate的控制台应用程序,包括使用NuGet.简单的配置.单表映射.对NHibernate配置文件添加智能提示.使用ISessionFactory ...

随机推荐

  1. 推荐的Android ORM框架

    1. OrmLite OrmLite 不是 Android 平台专用的ORM框架,它是Java ORM.支持JDBC连接,Spring以及Android平台.语法中广泛使用了注解(Annotation ...

  2. Java Junit单元测试

    使用Junit进行单元测试,首先引入Junit的jar,配置如下. @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(&quo ...

  3. ILGenerator.Emit动态 MSIL编程(三)之动态代理

    using System; using System.Linq; using System.Reflection; using System.Reflection.Emit; public seale ...

  4. YII rules常见规则

    public function rules() {     return array(         //必须填写         array('email, username, password, ...

  5. vmware12无法打开内核设备“\\.\Global\vmx86”

    vmware12 无法打开内核设备"\\.\Global\vmx86": 系统找不到指定的文件.你想要在安装 VMware Workstation 前重启吗? 打开vmware12 ...

  6. Objective-C的 KVC和KVO

    字面意思分别是: KVC是指key value coding,键值编码. KVO是指key value observing,键值观察. 直白的说法是: KVC就是将一个对象的属性及其值当做一个字典,可 ...

  7. WAMP虚拟目录的设置

    1.打开Apache的配置文件httpd.conf,并去掉#Include conf/extra/httpd-vhosts.conf前面的#!! 2.打开Apache的apache/conf/extr ...

  8. 动画--问题追踪:ImageView执行缩放动画ScaleAnimation之后,图像显示不全的问题。

    http://www.bkjia.com/Androidjc/929473.html: 问题追踪:ImageView执行缩放动画ScaleAnimation之后,图像显示不全的问题., 问题:我有一个 ...

  9. Python 多线程

    一.线程的使用 需导入模块: from threading import Thread 二.基本使用 def fun1(arg1, v): print(arg1) print('before') t1 ...

  10. JAVA下载文件中文乱码问题

    http://blog.itpub.net/92037/viewspace-788900/ 最后的中文乱码没有解决 现在我在系统中用到了两个组件,smartupload,一个支持中文,一个不支持.但是 ...