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

时间:2022-10-24 22:07:07

摘要

上一篇文章介绍了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。

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

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

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

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

 namespace Demo.Auto.Entities.Enum
{
public enum CustomerCreditRating
{
Excellent, VeryVeryGood, VeryGood, Good, Neutral, Poor, Terrible
}
}

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

 namespace Demo.Auto.Entities.Domain
{
public abstract class Entity
{
public virtual int Id { get; private set; }
}
}

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

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

Name类

 using System;

 namespace Demo.Auto.Entities.Domain
{
public class Name
{
public string LastName { get; set; }
public string FirstName { get; set; } public Name() { } public Name(string firstName, string lastName)
{
if (string.IsNullOrWhiteSpace(firstName))
{
throw new ArgumentException("First name must be defined.");
}
if (string.IsNullOrWhiteSpace(lastName))
{
throw new ArgumentException("Last name must be defined.");
}
FirstName = firstName;
LastName = lastName;
} public override int GetHashCode()
{
unchecked
{
var result = FirstName.GetHashCode();
result = (result * ) ^ LastName.GetHashCode();
return result;
}
} public bool Equals(Name other)
{
if (other == null) return false;
if (ReferenceEquals(this, other)) return true;
return Equals(other.FirstName, FirstName) &&
Equals(other.LastName, LastName);
} public override bool Equals(object other)
{
return Equals(other as Name);
}
}
}

Address类

 namespace Demo.Auto.Entities.Domain
{
public class Address
{
public virtual string Street { get; set; }
public virtual string City { get; set; }
public virtual string Province { get; set; }
public virtual string Country { get; set; } public bool Equals(Address other)
{
if (other == null) return false;
if (ReferenceEquals(this, other)) return true;
return Equals(other.Street, Street) &&
Equals(other.City, City) &&
Equals(other.Province, Province) &&
Equals(other.Country, Country);
} public override bool Equals(object obj)
{
return Equals(obj as Address);
} public override int GetHashCode()
{
unchecked
{
var result = Street.GetHashCode();
result = (result * ) ^ (City != null ? City.GetHashCode() : );
result = (result * ) ^ Province.GetHashCode();
result = (result * ) ^ Country.GetHashCode();
return result;
}
}
}
}

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

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

Customer类

 using Demo.Auto.Entities.Enum;
using System;
using System.Collections.Generic; namespace Demo.Auto.Entities.Domain
{
public class Customer : Entity
{
public Customer()
{
MemberSince = DateTime.UtcNow;
} public virtual Name Name { get; set; }
public virtual double AverageRating { get; set; }
public virtual int Points { get; set; }
public virtual bool HasGoldStatus { get; set; }
public virtual DateTime MemberSince { get; set; }
public virtual CustomerCreditRating CreditRating { get; set; }
public virtual Address Address { get; set; }
public virtual IList<Order> Orders { get; set; }
}
}

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

集合属性用IList接口定义。

Product类

 using System.Collections.Generic;

 namespace Demo.Auto.Entities.Domain
{
public class Product : Entity
{
public virtual string ProductCode { get; set; } public virtual string ProductName { get; set; } public virtual string Description { get; set; } public virtual IList<Order> Orders { get; set; }
}
}

Order类

 using System;
using System.Collections.Generic; namespace Demo.Auto.Entities.Domain
{
public class Order : Entity
{
public virtual DateTime Ordered { get; set; }
public virtual DateTime? Shipped { get; set; }
public virtual Address ShipTo { get; set; }
public virtual Customer Customer { get; set; }
public virtual IList<Product> Products { get; set; }
}
}

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

 using Demo.Auto.Entities.Domain;
using FluentNHibernate.Automapping;
using FluentNHibernate.Conventions;
using FluentNHibernate.Conventions.Instances;
using System;
using FluentNHibernate; namespace Demo.Auto.Entities.Mapping
{ }
  • 在namespace Demo.Auto.Entities.Mapping里添加DefaultAutomappingConfiguration的继承类AutoMappingConfiguration。设置哪些类型被映射成实体类,哪些类型被映射成组件类。
     public class AutoMappingConfiguration : DefaultAutomappingConfiguration
{
/// <summary>
/// 类型是否是实体映射类型
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public override bool ShouldMap(Type type)
{
//跟Customer类在一个名称空间的所有的类都被映射
return type.Namespace == typeof(Customer).Namespace;
} /// <summary>
/// 类型是否是值对象映射类型
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public override bool IsComponent(Type type)
{
//指定Address类和Name类是值对象映射类型
return type == typeof(Address)
|| type == typeof(Name);
} /// <summary>
/// 映射值对象类型属性到数据库字段名
/// </summary>
/// <param name="member">值对象属性</param>
/// <returns></returns>
public override string GetComponentColumnPrefix(Member member)
{
//映射到数据库列名的前缀为空。默认生成的组件列列名是类名+属性名。例:CustomerCity
return "";
}
}
  • 添加IIdConvention接口的继承类IdConvention。指定主键列列名、主键生成策略。
     public class IdConvention : IIdConvention
{
public void Apply(IIdentityInstance instance)
{
instance.GeneratedBy.Native();
}
}

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

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

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

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

详细说明见代码中注释。

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

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

 using Demo.Auto.Entities.Domain;
using FluentNHibernate.Automapping;
using FluentNHibernate.Automapping.Alterations; namespace Demo.Auto.Entities.Mapping
{ }

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

     public class CustomerMappingOverride : IAutoMappingOverride<Customer>
{
public void Override(AutoMapping<Customer> mapping)
{
mapping.Map(x => x.CreditRating).CustomType<Enum.CustomerCreditRating>();
mapping.HasMany(x => x.Orders).Inverse().Cascade.AllDeleteOrphan().Fetch.Join();
}
} public class ProductMappingOverride : IAutoMappingOverride<Product>
{
public void Override(AutoMapping<Product> mapping)
{
mapping.Map(x => x.ProductCode).Not.Nullable().Length();
mapping.Map(x => x.ProductName).Not.Nullable().Length();
mapping.HasManyToMany(x => x.Orders).Cascade.AllDeleteOrphan();
}
} public class OrderMappingOverride : IAutoMappingOverride<Order>
{
public void Override(AutoMapping<Order> mapping)
{
mapping.References(x => x.Customer).Cascade.SaveUpdate();
}
}

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

 using Demo.Auto.Entities.Domain;
using Demo.Auto.Entities.Mapping;
using FluentNHibernate.Automapping;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate.Tool.hbm2ddl;
using System; namespace Demo.Auto.Entities
{
class Program
{
const string connString = "server=localhost;" + "database=NHibernateDemoDB;" + "integrated security=SSPI;";
static void Main(string[] args)
{
var cfg = new AutoMappingConfiguration();
var configuration = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString(connString))
.Mappings(m =>
m.AutoMappings.Add(AutoMap.AssemblyOf<Customer>(cfg)
.Conventions.Setup(c =>
{
c.Add<IdConvention>();
c.Add<DefaultStringLengthConvention>();
c.Add<HasOneToManyConvention>();
c.Add<HasManyToManyConvention>();
})
.UseOverridesFromAssemblyOf<CustomerMappingOverride>()
.UseOverridesFromAssemblyOf<ProductMappingOverride>()
.UseOverridesFromAssemblyOf<OrderMappingOverride>()
).ExportTo(@"c:\daniel"))
.BuildConfiguration(); var exporter = new SchemaExport(configuration);
exporter.Execute(true, false, false); Console.Write("Hit enter to exit:");
Console.ReadLine();
}
}
}
  • 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。
             var exporter = new SchemaExport(configuration);
exporter.Execute(true, false, false);

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

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

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

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

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

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

结语

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

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

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

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

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

    原文: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. 2016NOIP总结

    从暑假开始学OI到现在,也已经过了4个月.说实话真是快啊...感觉没学什么东西就要去比赛了.怎么说呢,感觉自己真的是个菜鸡啊为什么就要去比赛呢.当初来到这里,是凭着兴趣来的,第一天能打那么多道题(19 ...

  2. ArcGIS API ArcGISDynamicMapServiceLayer&period;setVisibleLayers对带有GroupLayer图层组的数据无效(针对LayerInfo)问题探讨

    首先看下setVisibleLayers方法: setVisibleLayers(ids, doNotRefresh?) Sets the visible layers of the exported ...

  3. Qt5&period;4静态编译方法

    静态编译,就是编译器在编译可执行文件的时候,将可执行文件需要调用的对应动态链接库(.so或.lib)中的部分提取出来,链接到可执行文件中去,使可执行文件在运行的时候不依赖于动态链接库.这样就可以发布单 ...

  4. c&plus;&plus;实用技巧

    原地址:http://www.cnblogs.com/easymind223/articles/2576904.html 晚上的时间总是习惯性的在cnblogs逛街,今天又看到了好文章,其c++味道浓 ...

  5. Tips on Building WebRTC on Windows

    Problem: Git ask me to input git user and password Solution: Set environment variable SET DEPOT_TOOL ...

  6. AspNetCore 目前不支持SMTP协议(基于开源组件开发邮件发送,它们分别是MailKit 和 FluentEmail )

    net所有的功能都要重新来一遍,集成众多类库,core任重道远,且发展且努力!! 我们都知道,很多的邮件发送都是基于这个SMTP协议,但现在的.net core对这方面还不太支持,所以我们选择这两个组 ...

  7. Redis扩展

    Redis扩展下载地址:https://windows.php.net/downloads/pecl/releases/redis/ PHP怎么安装redis扩展 http://www.php.cn/ ...

  8. 【纵谭 Python】系列直播(持续更新)

    老周最近录了一些跟 Python 有关的直播,可以在“一直播”中搜索 ID 号 139251129 关注,也可以在微博中查看,反正都一样,同步的. 第一集:简单胡扯一下相关环境搭建.安装 Python ...

  9. 文本框中的回车处理 js

    <input id="txtOrderID" onkeypress="getKey(event)" /> <button onclick=&q ...

  10. DNS&sol;BIND in Debian

    Debian official document:http://www.debian.org/doc/manuals/network-administrator/ch-bind.html Buildi ...