http://blog.csdn.net/studyzy/article/details/11524591 引用html
因为在项目中使用了NHibernate来做为ORMapping构建数据访问层,那么就必需要配置Object和DataTable的映射。最先的项目中,咱们使用了最传统的XML配置文件的方式编写映射关系,可是这样太麻烦,每次修改class和表时都要去修改对应的XML文件,并且还容易出错,必定有疏忽遗漏的地方,还不容易找出错误,因此在第二个项目中,咱们使用了Fluent NHibernate的Mapping方式代替XML配置。使用Fluent NHibernate的最大好处是下降了出错的机会,由于Fluent Nhibernate的配置是使用C#来编写,能够智能感知,并且还能编译,不像原始的XML配置,写错了都不知道。数据库
public sealed class ConfigMapping : ClassMap<Config> { public ConfigMapping() { Table("CONFIG"); Id(x => x.Id, "CONFIG_ID").GeneratedBy.HiLo("1000000000"); Map(x => x.ConfigKey, "CONFIG_KEY"); Map(x => x.ConfigValue, "CONFIG_VALUE"); } }
可是使用Fluent NHibernate的配置方式仍然是须要编写Mapping代码的,也就意味着,若是我更改class或者DataTable的时候,还要对应的更改该Mapping文件。更多的修改意味着更多的风险,为了减小这方面的风险,同时为了减小配置的工做量,因此在最新的项目中采用了Fluent NHibernate中的Automapping。咱们只须要定义好映射的规则,就能够不对每一个表和类分别编写映射配置,而是按照规则进行自动的Mapping工做。这样在修改class或者DataTable时,只须要修改类和表便可,不须要再修改配置文件。app
要作到Automapping,就必定要定义好严格的命名规范,而后按照规范编写Automapping规则,实现自动化的映射。好比咱们能够定义以下的规则:dom
下面就来编写Automapping的转换规则,首先对String写一个扩展方法,实现CostCenter到COST_CENTER的转换:ide
static string ToDatabaseName(this string s) { return Regex.Replace(s, @"\B[A-Z]", match => "_" + match.ToString()).ToUpper(); }
对于1,须要实现IClassConvention实现以下:ui
public class ClassNameConvention : IClassConvention { public virtual void Apply(IClassInstance instance) { var tableName = instance.EntityType.Name.ToDatabaseName(); instance.Table(tableName); } }
同时对于列,须要使用IPropertyConvention接口,实现以下:this
public class PropertyConvention : IPropertyConvention { public void Apply(IPropertyInstance instance) { instance.Column(instance.Name.ToDatabaseName()); } }
对于2,须要实现IIdConvention接口,另外咱们采用的是Hilo值的主键生成方式,使用一个表HIBERNATE_UNIQUE_KEY存储每一个表的流水。具体实现以下:spa
public class PrimaryKeyConvention : IIdConvention { public const string NextHiValueColumnName = "VALUE"; public const string NHibernateHiLoIdentityTableName = "HIBERNATE_UNIQUE_KEY"; public const string TableColumnName = "TABLE_NAME"; public virtual void Apply(IIdentityInstance instance) { var tableName = instance.EntityType.Name.ToDatabaseName(); instance.Column(tableName + "_ID");//这里设置主键的命名为表名+“_ID” if (instance.Type == typeof(long))//接下来设置主键的生成方式为HiLo值方式 { instance.GeneratedBy.HiLo( NHibernateHiLoIdentityTableName, NextHiValueColumnName, "1000000000", builder => builder.AddParam("where", string.Format("{0} = '{1}'", TableColumnName, tableName))); } } }
对于3,一对多的状况,须要设置“一”的一方的Collection和“多”的一方的Reference,具体以下:.net
public class CollectionConvention : ICollectionConvention { public void Apply(ICollectionInstance instance) { string colName; var entityType = instance.EntityType; var childType = instance.ChildType; if (entityType == childType)//这里是专门对自身关联一对多的状况进行特殊处理,统一使用PARENT_ID做为外键列 colName = "PARENT_ID"; else { colName = entityType.Name.ToDatabaseName() + "_ID"; } instance.Key.Column(colName); instance.Cascade.AllDeleteOrphan(); } }
public class ReferenceConvention : IReferenceConvention { public void Apply(IManyToOneInstance instance) { string colName = null; var referenceType = instance.Class.GetUnderlyingSystemType(); var entityType = instance.EntityType; var propertyName = instance.Property.Name; //Self association if (referenceType == entityType) colName = "PARENT_ID"; else colName = propertyName.ToDatabaseName() + "_ID"; instance.Column(colName); } }
对于4SubClass的处理,须要涉及到指定要进行Discriminate的类,还有DiscriminateColumn,而后指定DiscriminateColumn中如何对Subclass进行Mapping。hibernate
这里就须要重写DefaultAutomappingConfiguration类,在该类中指定主键、Discriminate的类等,具体代码以下:
public class AutoMapConfiguration : DefaultAutomappingConfiguration { public override bool ShouldMap(Type type) { return (type.IsClass && type.Namespace.StartsWith("OurProject.Core.Model"));//指定了哪些类是要进行AutoMapping的 } public override bool IsId(Member member) { return member.Name == "Id";//指定了每一个类中的Id属性就是该类的主键 } public override bool IsDiscriminated(Type type)//指定了哪些类是须要进行SubClass继承,将其SubClass都存放在一个表中的。 { return type.In( typeof(Client), typeof (Classification), typeof (MultiClassification) ); } public override string GetDiscriminatorColumn(Type type) { return "TYPE";//指定了SubClass的区分列就是有一个叫作TYPE的列 } }
而后就是关于DiscriminateColumn中的值如何映射成对应的Subclass,须要实现ISubclassConvention接口,代码以下:
public class SubclassConvention : ISubclassConvention { public void Apply(ISubclassInstance instance) { instance.DiscriminatorValue(instance.EntityType.Name); } }
对于5多对多,就须要实现IHasManyToManyConvention接口,在这个接口中对两个表名进行排序,而后进行链接表示中间表。具体代码以下:
public class HasManyToManyConvention : IHasManyToManyConvention { public void Apply(IManyToManyCollectionInstance instance) { var entityDatabaseName = instance.EntityType.Name.ToDatabaseName(); var childDatabaseName = instance.ChildType.Name.ToDatabaseName(); var name = GetTableName(entityDatabaseName, childDatabaseName);//对两个表名进行排序,而后链接组成中间表名 instance.Table(name); instance.Key.Column(entityDatabaseName + "_ID"); instance.Relationship.Column(childDatabaseName + "_ID"); } private string GetTableName(string a, string b) { var r = System.String.CompareOrdinal(a, b); if (r > 0) { return "{0}_{1}".Fill(b, a); } else { return "{0}_{1}".Fill(a, b); } } }
对于6枚举的处理,须要指定枚举为UserType,实现接口IUserTypeConvention,具体代码以下:
public class EnumConvention : IUserTypeConvention { public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria) { criteria.Expect(x => x.Property.PropertyType.IsEnum); } public void Apply(IPropertyInstance instance) { instance.CustomType(instance.Property.PropertyType); } }
实现了以上这几个接口,那么大部分状况下的Automapping均可以实现了。最后是将这些接口通知给FluentNhibernate,让其应用这些接口,导入指定Assembly中的DomainModel,具体的实现方法是:
public virtual AutoPersistenceModel Generate(string[] domainAssemblies, string[] dalAssemblies) { var mappings = AutoMap.Assemblies( new AutoMapConfiguration(), domainAssemblies.Select(Assembly.LoadFrom).ToArray()); foreach (var ignoredBaseType in IgnoredBaseTypes) { mappings.IgnoreBase(ignoredBaseType); } foreach (var includeBaseType in IncludeBaseTypes) { mappings.IncludeBase(includeBaseType); } mappings.Conventions.Setup(GetConventions());//指定了Automapping转换的接口实现 foreach (var dalAssembly in dalAssemblies) { mappings.UseOverridesFromAssembly(Assembly.LoadFrom(dalAssembly)); } return mappings; } public static IList<Type> IgnoredBaseTypes = new List<Type>//这里列出的类都是些Base类,不会Mapping到具体某个表 { typeof (Entity) //该对象其实就只有Id这个属性,做为全部要Mapping的类的父类 }; public static IList<Type> IncludeBaseTypes = new List<Type>//默认状况下抽象类是不会Mapping成表的,因此这里须要指明这些类是要Mapping成表的 { typeof (Classification), typeof (MultiClassification), typeof(Client) }; protected Action<IConventionFinder> GetConventions() { return finder => { finder.Add<ClassNameConvention>(); finder.Add<PrimaryKeyConvention>(); finder.Add<CollectionConvention>(); finder.Add<ReferenceConvention>(); finder.Add<HasManyConvention>(); finder.Add<SubClassNameConvention>(); finder.Add<SubclassConvention>(); finder.Add<PropertyConvention>(); finder.Add<EnumConvention>(); finder.Add<HasManyToManyConvention>(); }; }
该方法返回了一个AutoPersistenceModel,使用这个对象注册到NHibernate中便可。
PS:以上代码主要都是同事在前期实现的,我只是在后期接手了该工做,在此基础上作了一些简单的维护和修改。