NHibernate 3.2 Mapping By Code 實作範例
繼Linq to SQL、Entity Framework之後,最近又碰了一套ORM就是"NHibernate"
在初步的認識他之後,覺得這套的擴充性與功能方面都十分強大。
當然目前對他的掌握度還沒有很高,因此可能寫不出內容豐富的文章
但這篇要介紹的是 NHibernate 3.2 內建的Mapping By Code,將過去資料庫與類別
對應的xml改為到cs檔裡面撰寫,除了有強型別好處外,也可讓開發人員
更容易設計自己的類別。
本篇利用ASP.NET MVC + NHibernate 3.2 實作
資料庫選用SQLite,並利用Code First在執行時期建立出資料庫
由於網路上目前NHibernate與Mapping By Code的中文文章相對的不多,因此會
著重NHibernate這個部分,並簡單的做出一個CRUD的功能。
首先建立一個新的ASP.NET MVC 2空白專案,並利用NeGet 抓取NHibernate 3.2 版
搜尋NHibernate,並且安裝
安裝好後,開始寫第一個Product類別
Product.cs
public class Product
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual int Price { get; set; }
public virtual int Quantity { get; set; }
public virtual int CreateUserId { get; set; }
public virtual DateTime CreateTime { get; set; }
public virtual int? EditUserId { get; set; }
public virtual DateTime? EditTime { get; set; }
}
注意!!所有成員都必須是virtual。
這個實體類別就是我們在程式中所使用的Product類別,我們可以依實際的需要設計這個類別
接著寫Product類跟資料庫要如何對應的ProductMapping類別
ProductMapping.cs
using System;
using NHibernate.Mapping.ByCode.Conformist;
using NHibernate.Mapping.ByCode;
namespace NHibernateMappingByCode.Models.Mapping
{
//繼承ClassMapping<T>
public class ProductMapping : ClassMapping<Product>
{
public ProductMapping()
{
/* 如果是共用的屬性可以自訂一個父類別去Mapping
* 就可以不用每個類別都寫重複的對應
*/
//主鍵,產生的類型選擇Identity
Id(p => p.Id, map => map.Generator(Generators.Identity));
Property(p => p.CreateTime, map => map.NotNullable(true));
Property(p => p.CreateUserId, map => map.NotNullable(true));
Property(p => p.EditTime);
Property(p => p.EditUserId);
Property(p => p.Name, map => { map.Length(50); map.NotNullable(true); });
Property(p => p.Price, map => map.NotNullable(true));
Property(p => p.Quantity, map => map.NotNullable(true));
}
}
}
這邊採用的是Conformist Mapping,另外還有一種方式是下面這種Specific Mapper,就不介紹了。
var mapper = new ModelMapper();
mapper.Class<TestClass>(cm =>
{
cm.Id(p => p.Id, map => map.Generator(Generators.Identity));
cm.Property(p => p.Something);
});
關於Mapping的設定還有非常多,像一對多、多對一、多對多、Cache等..設定
等研究透徹一點後再慢慢分享出來。
寫完了我們的實體類別跟對應之後,接著就來設定連線
NHibernateUtility.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using NHibernate.Cfg;
using NHibernate;
using NHibernate.Dialect;
using System.Data;
using NHibernate.Cfg.MappingSchema;
using NHibernate.Mapping.ByCode;
using System.Reflection;
using NHibernateMappingByCode.Models.Mapping;
namespace NHibernateMappingByCode.Models
{
public class NHibernateUtility
{
public static Configuration Configuration { get; private set; }
public static ISessionFactory SessionFactory { get; private set; }
public void Initialize()
{
Configuration = Configure();
SessionFactory = Configuration.BuildSessionFactory();
}
private Configuration Configure()
{
var configuration = new Configuration();
// 資料庫設定
// 這裡的東西可以改用xml的方式設定,增加修改的彈性
configuration.DataBaseIntegration(c =>
{
// 資料庫選用 SQLite
c.Dialect<SQLiteDialect>();
// 取用 .config 中的 "MyTestDB" 連線字串
c.ConnectionStringName = "MyTestDB";
// Schema 變更時的處置方式
c.SchemaAction = SchemaAutoAction.Update;
// 交易隔離等級
c.IsolationLevel = IsolationLevel.ReadCommitted;
});
// 取得Mapping
// 取代舊有的 *.hbm.xml
var mapping = GetMappings();
//加入Mapping
configuration.AddMapping(mapping);
return configuration;
}
private HbmMapping GetMappings()
{
var mapper = new ModelMapper();
//加入Mapping
//如果是多個 Type 可用 AddMappings
mapper.AddMapping(typeof(ProductMapping));
HbmMapping mapping = mapper.CompileMappingForAllExplicitlyAddedEntities();
return mapping;
}
}
}
Configuration就是以往NHibernate設置在hibernate.cfg.xml的連線設定
當然設定在xml中修改後不需要重新編譯,使用上較方便,但這邊介紹的是另外一種寫法。
由於是Code First,因此在開發時完全不需要考慮到資料庫,這是讓我覺得Code First最
棒的一點,如果需要更換資料庫的話,只需要更改上方的
//c.Dialect<SQLiteDialect>();
//更換成使用 SQL 2008
c.Dialect<MsSql2008Dialect>();
並將連線字串改為SQL 2008資料庫的設定,在Application重啟時,就會在對應的資料庫
中生成Table。
接著在Global.asax.cs中,Application_Start()時加入
protected void Application_Start()
{
NHibernateUtility utility = new NHibernateUtility();
utility.Initialize();
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
}
然後運行時就會把Table建出來了。
另外還有一點要注意的是,這邊SchemaActoin設定是Update
也就是Product類別與ProductMapping對應有改變時,就會Update資料庫的Schema
當系統上線後,不妨把這一行拿掉,以免不小心被誰改到了結果資料庫整個亂掉
到這邊為止我們已經有了一張Product Table
為了讓範例完整一點,就寫一個ProductService做出CRUD的功能
(這邊沒有特別去管理Session與實作Repository,因此僅供參考就好)
ProductService.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using NHibernate;
using NHibernate.Linq;
namespace NHibernateMappingByCode.Models
{
public class ProductService
{
public IEnumerable<Product> GetAll()
{
IEnumerable<Product> model;
using (var session = NHibernateUtility.SessionFactory.OpenSession())
{
model = session.Query<Product>().ToList();
}
return model;
}
public Product GetSingle(int id)
{
Product model;
using (var session = NHibernateUtility.SessionFactory.OpenSession())
{
model = session.Get<Product>(id);
}
return model;
}
public void Insert(Product model)
{
model.CreateTime = DateTime.Now;
model.CreateUserId = 1;
//如同時處理好幾張表,可加入交易避免例外發生時產生髒資料
using (var session = NHibernateUtility.SessionFactory.OpenSession())
using (var trans = session.BeginTransaction())
{
try
{
session.Save(model);
trans.Commit();
}
catch (Exception ex)
{
// Log...
trans.Rollback();
}
}
}
public void Update(int id, Product model)
{
var soucre = GetSingle(id);
if (soucre != null)
{
soucre.Name = model.Name;
soucre.Price = model.Price;
soucre.Quantity = model.Quantity;
soucre.EditTime = DateTime.Now;
soucre.EditUserId = 1;
//如同時處理好幾張表,可加入交易避免例外發生時產生髒資料
using (var session = NHibernateUtility.SessionFactory.OpenSession())
using (var trans = session.BeginTransaction())
{
try
{
session.SaveOrUpdate(soucre);
trans.Commit();
}
catch (Exception ex)
{
// Log...
trans.Rollback();
}
}
}
}
public void Delete(int id)
{
var model = GetSingle(id);
if (model != null)
{
using (var session = NHibernateUtility.SessionFactory.OpenSession())
using (var trans = session.BeginTransaction())
{
try
{
session.Delete(model);
trans.Commit();
}
catch (Exception ex)
{
// Log...
trans.Rollback();
}
}
}
}
}
}
HomeController.cs
namespace NHibernateMappingByCode.Controllers
{
public class HomeController : Controller
{
ProductService _prodcutService;
public HomeController()
{
_prodcutService = new ProductService();
}
public ActionResult Index()
{
var model = _prodcutService.GetAll();
return View(model);
}
public ActionResult Insert()
{
return View();
}
[HttpPost]
public ActionResult Insert(Product model)
{
_prodcutService.Insert(model);
return RedirectToAction("Index");
}
public ActionResult Update(int id)
{
var model = _prodcutService.GetSingle(id);
return View(model);
}
[HttpPost]
public ActionResult Update(int id, Product model)
{
_prodcutService.Update(id, model);
return RedirectToAction("Index");
}
public ActionResult Delete(int id)
{
_prodcutService.Delete(id);
return RedirectToAction("Index");
}
}
}
範例畫面
隨文附上範例下載
SQLite的dll是用x86版本的,因此如果是x64的機器測試時要改一下