Entity Framework - 一個組織階層的ORM範例(上)
相信很多人的公司的組織階層在人數越來越多時架構上也越來越複雜,這種結構的問題在資料處理上相當麻煩,解決方案很多每種狀況都有不同的做法.
而最常遇到的問題就是在階層關係上如何有效率的存取各種需求,底下為一個常見的組織表.
而使用者需求常常會多變.
譬如說
查詢台中分公司下的員工有哪些?
各分公司的員工有哪些?
陳大王屬於哪個公司?
整體企業所有的部門有哪些,各掛於哪些公司下?
公司結構除了部門人員外現在又需新增事業處這種單位.
零零總總的需求,如何有效率且能夠達成需求?
在物件導向規劃中這問題並不難解決,但在關聯式資料庫卻相當麻煩.所以如何透過EF來解決這個問題為這篇文章的主要目的.
在開始前,由於資料庫的規劃需要特別處理,底下這篇文章為本篇文章選定的解決方案,所以再繼續研讀前,請先了解下面連結文章內的做法.
http://articles.sitepoint.com/article/hierarchical-data-database
了解了資料庫的方式後,會發現這種結構雖然可以解決很多問題,但對於資料查詢與異動卻要求非常嚴謹且必須小心,如不小處理很容易導致資料大亂,所以除非很有組織的開發模式不然很難實現,而底下就開始介紹透過EF來解決這個難題.
以上面的需求在程式物件設計上理所當然的會有Company,Department與Employee等物件.而因為需要套用上述的資料結構故又新增了一個抽象類別Hierarchical作為紀錄階層關係的類別,而所有物件都繼承於此.
因此最後的結果為如下圖所示
資料庫結構
之所以採取這種紀錄模式是因為考量1.各Table通常在Quwey時很少會同時 2.需求上有很大的機會會再增加某種單位組織,譬如上述中的需求事業處,能夠很方便且容易的加入結構中,而不會導致原資料表有大幅度修改.
了解資料庫方式後,EF的設計就簡單了
如上面所描述Company,Department與Employee都是繼承於Hierarchical如同資料表中的方式.
物件與資料庫的架構已經完成,但還有個難題,因為採用的資料結構關係會導致存取或異動資料時相當複雜,故必須要封裝好這些動作來確保資料完整性.
因此底下的程式透過OO design pattern的templete樣式使用交易類別方式處理這個問題.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Common
{
abstract class CreateHierarchicalTransaction : ITransaction<Hierarchical>
{
private string _name;
private int? _parentId;
public CreateHierarchicalTransaction(string name, int? parentId = null)
{
this._name = name;
this._parentId = parentId;
}
protected abstract Hierarchical GetTypeInstance();
#region ITransaction<Hierarchical> Members
public Hierarchical Execute()
{
using (CommonEntities dc = new CommonEntities())
{
Hierarchical instance = GetTypeInstance();
instance.Name = this._name;
instance.CreateTime = DateTime.Now;
instance.ParentId = this._parentId;
instance.lft = 1;
instance.rgt = 2;
if(this._parentId.HasValue)
{
int number = dc.Hierarchical.First(h => h.Id == this._parentId).lft;
instance.lft = number + 1;
instance.rgt = number + 2;
UpdateRange(dc, number);
}
dc.AddToHierarchical(instance);
dc.SaveChanges();
return instance;
}
}
private void UpdateRange(CommonEntities dc, int number)
{
dc.ExecuteStoreCommand("UPDATE tb_Hierarchical SET lft=lft+2 WHERE lft>{0}", number);
dc.ExecuteStoreCommand("UPDATE tb_Hierarchical SET rgt=rgt+2 WHERE rgt>{0}", number);
}
#endregion
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Common
{
class CreateCompanyTransaction : CreateHierarchicalTransaction
{
private string _tel;
public CreateCompanyTransaction(string name, string tel, int? parentId = null)
: base(name, parentId)
{
this._tel = tel;
}
protected override Hierarchical GetTypeInstance()
{
Company c = new Company();
c.Tel = this._tel;
return c;
}
}
}
這邊僅列出Company與Hierarchical的做法,其它的大同小異依樣畫葫蘆即可.
所以使用端就可以透過這些交易類別來新增資料而不需要管如何處理資料,底下程式碼建立一些測試資料,如同剛開始的那張圖
CreateCompanyTransaction ctran = new CreateCompanyTransaction("台北總公司", "02xxxx");
int pcmpId = ctran.Execute().Id;
ctran = new CreateCompanyTransaction("桃園子公司", "03xxxx", pcmpId);
int cmp1Id = ctran.Execute().Id;
ctran = new CreateCompanyTransaction("台中子公司", "04xxxx", pcmpId);
int cmp2Id = ctran.Execute().Id;
CreateEmployeeTransaction etran = new CreateEmployeeTransaction("張美美", "xxx@xxx.com", pcmpId);
etran.Execute();
etran = new CreateEmployeeTransaction("王小明", "xxx@xxx.com", cmp1Id);
etran.Execute();
CreateDepartmentTransaction dtran = new CreateDepartmentTransaction("台中業務部", "SALE002", cmp2Id);
int dep1Id = dtran.Execute().Id;
etran = new CreateEmployeeTransaction("陳大王", "xxx@xxx.com", dep1Id);
etran.Execute();
etran = new CreateEmployeeTransaction("黃小王", "xxx@xxx.com", dep1Id);
etran.Execute();
最終資料庫就會產生如下資料
tb_Hierarchical
tb_Company
tb_Employee
tb_Department
而查詢方式可以如下做法
查詢台中分公司底下所有員工
using (CommonEntities dc = new CommonEntities())
{
Company c = (Company)dc.Hierarchical.First(h => h.Id == 3);
foreach (Employee e in dc.Hierarchical.OfType<Employee>().Where(h => h.lft > c.lft && h.lft < c.rgt))
{
Console.WriteLine(e.Name);
}
}
查詢陳大王的公司階層有哪些
using (CommonEntities dc = new CommonEntities())
{
Employee e = (Employee)dc.Hierarchical.First(h => h.Id == 7);
foreach (Company c in dc.Hierarchical.OfType<Company>().Where(h => h.lft < e.lft && h.rgt > e.rgt))
{
Console.WriteLine(c.Name);
}
}
然而這種做法使用端還是要清楚該如何查詢所需資料的方式,故最好的方式應該如同交易類別般封裝查詢方式,而方式是有的,將會在後續下篇文章中介紹.