丹尼大叔

数学专业毕业,爱上编程的大叔,兴趣广泛。使用博客园这个平台分享我工作和业余的学习内容,以编程交友。有朋自远方来,不亦乐乎。

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

摘要

上一篇文章介绍了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 * 397) ^ 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 * 397) ^ (City != null ? City.GetHashCode() : 0);
31                 result = (result * 397) ^ Province.GetHashCode();
32                 result = (result * 397) ^ 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(250);
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) > 0)
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(10);
15             mapping.Map(x => x.ProductName).Not.Nullable().Length(50);
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上去查看更详细的内容。

posted on 2016-07-27 22:01  丹尼大叔  阅读(873)  评论(0编辑  收藏  举报