NHibernate做.Net應該都不陌生,今天我們就算是溫故下這個技術,概念性的東西就不說了,這次主要說本人在實際使用的遇到的問題,比較費解現在就當是記錄下,避免以后再犯。本次主要使用的情況是1對N多表關聯操作,具體情況如下(給出主要代碼):
一、NHibernate配置
(1) 引入動態庫
Antlr3.Runtime.dll、NHibernate.dll、Newtonsoft.Json.dll、Iesi.Collections.dll相關動態庫,用NuGet導入即可
(2)配置文件
configSections加入NHibernate配置信息
<configSections> <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 --> <section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" /> <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" /> </configSections>
Nhibernate相關配置信息及數據鏈接配置
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2"> <session-factory name="Sys.Dal"> <property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property> <property name="connection.connection_string">Data Source=.;Initial Catalog=AttendanceDB;User ID=sa;Password=******;</property> <property name="show_sql">false</property> <property name="query.substitutions">true 1, false 0, yes 'Y', no 'N'</property> <property name="dialect">NHibernate.Dialect.MsSql2012Dialect</property> <property name="proxyfactory.factory_class">NHibernate.Bytecode.DefaultProxyFactoryFactory, NHibernate</property> <property name="hbm2ddl.keywords">none</property> <mapping assembly="Sys.Model" /> </session-factory> </hibernate-configuration>
(3)創建創建session工場,方便后面直接使用
public class SessionManager { private static Configuration _cfg = new Configuration().AddAssembly("Sys.Model"); private static ISessionFactory _sessionFactory = _cfg.BuildSessionFactory(); private SessionManager() { } public static ISession GetSession() { return _sessionFactory.OpenSession(); } public static IStatelessSession GetStateLessSession() { return _sessionFactory.OpenStatelessSession(); } }
二、實際使用
(1)相關實體和映射文件
User.cs
public class User { public virtual int ID { get; set; } public virtual string UserName { get; set; } public virtual string UserPwd { get; set; } public virtual int UserType { get; set; } public virtual string Telephone { get; set; } public virtual string CreateDate { get; set; } public virtual string UpdateDate { get; set; } public virtual int Status { get; set; } public virtual string Remark { get; set; } public virtual ISet<AttendanceInfo> listAttendanceInfo { get; set; } }
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"> <class name="Sys.Model.User, Sys.Model" table="Tb_User" lazy="true"> <id name="ID" column="ID" type="Int32"> <generator class="identity" /> </id> <property name="UserName" type="String"> <column name="UserName"/> </property> <property name="UserPwd" type="String"> <column name="UserPwd"/> </property> <property name="UserType" type="Int32"> <column name="UserType"/> </property> <property name="Telephone" type="String"> <column name="Telephone"/> </property> <property name="CreateDate" type="String"> <column name="CreateDate"/> </property> <property name="UpdateDate" type="String"> <column name="UpdateDate"/> </property> <property name="Status" type="Int32"> <column name="Status"/> </property> <property name="Remark" type="String"> <column name="Remark"/> </property> <!-- 一個User有多個Attendance --> <set name="listAttendanceInfo" table="Tb_AttendanceInfo" generic="true" inverse="true" > <key column="ID" foreign-key="FK_Att_User"/> <one-to-many class="Sys.Model.AttendanceInfo, Sys.Model"/> </set> </class> </hibernate-mapping>
AttendanceInfo.cs
public class AttendanceInfo { public virtual int ID { get; set; } public virtual int UID { get; set; } public virtual string CheckInTime { get; set; } public virtual string CheckOutTime { get; set; } public virtual int Status { get; set; } public virtual int ConfirmUID { get; set; } public virtual string ConfirmTime { get; set; } public virtual string Remark { get; set; } public virtual User UserInfo { get; set; } }
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"> <class name="Sys.Model.AttendanceInfo, Sys.Model" table="Tb_AttendanceInfo" lazy="true"> <id name="ID" column="ID" type="Int32"> <generator class="identity" /> </id> <property name="UID" type="Int32"> <column name="UID"/> </property> <property name="CheckInTime" type="String"> <column name="CheckInTime"/> </property> <property name="CheckOutTime" type="String"> <column name="CheckOutTime"/> </property> <property name="Status" type="Int32"> <column name="Status"/> </property> <property name="ConfirmUID" type="Int32"> <column name="ConfirmUID"/> </property> <property name="ConfirmTime" type="String"> <column name="ConfirmTime"/> </property> <property name="Remark" type="String"> <column name="Remark"/> </property> <many-to-one name="UserInfo" column="UID" not-null="true" class="Sys.Model.User, Sys.Model" foreign-key="FK_Att_User" /> </class> </hibernate-mapping>
(2)獲取數據——原意是獲取AttendanceInfo帶出User上面的個別字段
public List<AttendanceInfo> GetAttendanceInfoByWhere(string uname) { using (ISession session = SessionManager.GetSession()) { ICriteria criteria = session.CreateCriteria<AttendanceInfo>(); List<AttendanceInfo> lsAtt = criteria.List<AttendanceInfo>().ToList(); return lsAtt; } }
調用方法:
public JsonResult GetUserAttInfo() { if (Session["uid"] != null && Session["uType"] != null) { string strWhe = Request["strWhe"].ToString(); Bll_AttendanceInfo bllAtt = new Bll_AttendanceInfo(); string uname = string.Empty; if (Session["uType"].ToString().Equals("1")) uname = Session["uname"].ToString(); else uname = strWhe; List<AttendanceInfo> lsAtt = bllAtt.GetAttendanceInfoByWhere(uname); if (lsAtt != null && lsAtt.Count > 0) { return Json(JsonHeleper.ObjectToJSON(lsAtt)); } return Json("{\"respCode\":\"faile\",\"respMsg\":\"未獲取到任何行,請稍后重試!\"}"); } else { return Json("{\"respCode\":\"faile\",\"respMsg\":\"Session丟失,請稍后重試!\"}"); } }
public static string ObjectToJSON<T>(T obj) { return JsonConvert.SerializeObject(obj); }
好,到這里看上去配置映射關系好像沒多大問題,但是實際上問題就出來了
問題一:在調用的GetAttendanceInfoByWhere的時候會出現:no session or session was closed...
其實是因為做了映射關系先去子表在去主表數據,session釋放掉了;需要在子表的映射文件加上個lazy="false",把延遲加載關掉使用使用代理延遲,那么這個問題解決了。
上面的問題是在去取值的時候會拋出異常,接着問題又來了,問題二:Json(JsonHeleper.ObjectToJSON(lsAtt))也就是到最后的JsonConvert.SerializeObject(obj),json序列化出錯,怎么可能系統紫的json序列化方法會出錯,而且這里報的錯和上面的那個錯誤信息基本一致,字面意思也是session不存在或者關掉,納悶,剛剛明明已經處理掉這個問題,為毛在取值的地方沒出現,在返回的json序列化出錯,找了很久其實有時映射文件配置問題,在主表也就是User的映射文件中加上了cascade="all" generic="true",以及在子表映射文件加上 not-found= "ignore" ,就ok了
緊接着第三個問題,還是在返回json序列化的時候異常:Self referencing loop detected for property 'ParentClassify' with type...字面意思就是自引用循環,就是主類新加子類的屬性到實體,子類新增主類的屬性到實體,就出了這種問題,怎么解決?調整了下最終的對象轉json的方法ObjectToJSON,需要導入包文件
using Newtonsoft.Json;
public static string ObjectToJSON<T>(T obj) {
//return JsonConvert.SerializeObject(obj); JsonSerializerSettings settings = new JsonSerializerSettings(); settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; return JsonConvert.SerializeObject(obj, settings); }
到此為止,關聯查詢算是沒問題了,但是不要慶幸,這只是查詢,接下來做了下子表的新增和修改操作,結果讓人奔潰,異常!!!!!!
錯誤信息:Error dehydrating property value for...
最終導致這個問題是因為數據庫字段和映射文件字段不一致,對應不起來,因為映射文件加了個關聯對象。需要在加的關聯對象的hbm.xml文件里面加上insert="false" update="false",讓新增和修改的時候忽略掉。開發工具卻一直報但是它一直拋Error dehydrating property value for...誤導排錯方向,這個坑,讓人無語。
最后順便提一嘴,在使用Nhibernate多表關聯的時候,mapping配置還真的小心點,一不小心就是個坑,而且資料不是很好找。
最終兩個hbm.xml的關聯關系配置變成如下:
User.hbm.xml
<!-- 一個User有多個Attendance --> <set name="listAttendanceInfo" table="Tb_AttendanceInfo" cascade="all" generic="true" inverse="true" lazy="false" > <key column="ID" foreign-key="FK_Att_User" not-null="false"/> <one-to-many class="Sys.Model.AttendanceInfo, Sys.Model" not-found= "ignore" /> </set>
AttendanceInfo.hbm.xml
<many-to-one name="UserInfo" column="UID" not-null="false" class="Sys.Model.User, Sys.Model" cascade="none" not-found="ignore" insert="false" update="false" foreign-key="FK_Att_User" />
到此幾個坑基本解決,表示很心累。寫出來也后來遇到的人參考,有何不對的地方也歡迎大家指教。
(另外,需要注意的是建立one-to-many或者many-to-on的欄位需要是主鍵,所以需要留意xml映射文件的書寫)
三、關聯表配置的映射屬性
上面也提到了,好幾個問題都是在做關聯關系配置的時候出的問題,順便附上關聯表配置的映射屬性拱參考
- access(默認property):可選field、property、nosetter、ClassName值。NHibernate訪問屬性的策略。
- cascade(可選):指明哪些操作會從父對象級聯到關聯的對象。可選all、save-update、delete、none值。除none之外其它將使指定的操作延伸到關聯的(子)對象。
- class(默認通過反射得到屬性類型):關聯類的名字。
- column(默認屬性名):列名。
- fetch(默認select):可選select和join值,select:用單獨的查詢抓取關聯;join:總是用外連接抓取關聯。
- foreign-key:外鍵名稱,使用SchemaExport工具生成的名稱。
- index:......
- update,insert(默認true):指定對應的字段是否包含在用於UPDATE或INSERT 的SQL語句中。如果二者都是false,則這是一個純粹的 “外源性(derived)”關聯,它的值是通過映射到同一個(或多個)字段的某些其他特性得到或者通過觸發器其他程序得到。
- lazy:可選false和proxy值。是否延遲,不延遲還是使用代理延遲。
- name:屬性名稱propertyName。
- not-found:可選ignore和exception值。找不到忽略或者拋出異常。
- not-null:可選true和false值。
- outer-join:可選auto、true、false值。
- property-ref(可選):指定關聯類的一個屬性名稱,這個屬性會和外鍵相對應。如果沒有指定,會使用對方關聯類的主鍵。這個屬性通常在遺留的數據庫系統使用,可能有外鍵指向對方關聯表的某個非主鍵字段(但是應該是一個唯一關鍵字)的情況下,是非常不好的關系模型。比如說,假設Customer類有唯一的CustomerId,它並不是主鍵。這一點在NHibernate源碼中有了充分的體驗。
- unique:可選true和false值。控制NHibernate通過SchemaExport工具生成DDL的過程。
- unique-key(可選):使用DDL為外鍵字段生成一個唯一約束。