fluentnhibernet auto mapping

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

  1. 類名和字段名採用每一個單詞首字母大寫的方式而數據庫表名和列名使用所有大寫,單詞之間下劃線分割的方式。(好比CostCenter類對應表COST_CENTER)
  2. 類中的主鍵使用Id命名,表中的主鍵使用表名+「_ID」的命名方式。(好比CostCenter中有public virtual long Id{get;set;},對應表中的列COST_CENTER_ID)
  3. 對於一對多的關係,使用父方的類名做爲屬性名,表中使用父表的主鍵列名做爲對應的外鍵列的列名。(好比一個班對應多個學生,在Class類中就有public virtual IList<Student> Students{get;set;},而在Student類中就必須使用Class做爲屬性名:public virtual Class Class{get;set;})
  4. 對於SubClass,採用將多個子對象都存在同一個表中的方式實現,使用「TYPE」列做爲DiscriminatorColumn,使用之類的類名做爲子類的惟一標識。
  5. 對於多對多的關係,把兩個類對應的表名進行排序,將小的排前面,而後將兩個表名鏈接起來,中間使用「_」分割。(好比Course和Student是多對多關係,那麼產生的中間表表名爲COURSE_STUDENT)
  6. 對於枚舉,在數據庫中使用tinyint也就是一個Byte來存儲,枚舉在Automapping中做爲UserType進行處理。

下面就來編寫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:以上代碼主要都是同事在前期實現的,我只是在後期接手了該工做,在此基礎上作了一些簡單的維護和修改。

相關文章
相關標籤/搜索