• NHibernate系列文章二十八:NHibernate Mapping之Auto Mapping(附程序下载)


    摘要

    上一篇文章介绍了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上去查看更详细的内容。

  • 相关阅读:
    推送技术 --SignalR
    软件解耦
    xrBarCode 条形码的密度设置
    Javascript 中方法的重写
    数据库锁
    oracle instr,substr 截取字符串
    循环读取写入表
    Oracle For 循环,字符串拼接,查找
    iis,webservice 启用 acrobat.exe 打印
    iis,webservice 打印
  • 原文地址:https://www.cnblogs.com/uncle_danny/p/5712780.html
Copyright © 2020-2023  润新知