在使用Entity Framework 實(shí)體框架的時(shí)候崎溃,我們大多數(shù)時(shí)候操作的都是實(shí)體模型Entity,這個(gè)和數(shù)據(jù)庫(kù)操作上下文結(jié)合蠕蚜,可以利用LINQ等各種方便手段梢灭,實(shí)現(xiàn)起來非常方便,一切看起來很美好幕侠。但是如果考慮使用WCF的時(shí)候帝美,可能就會(huì)碰到很多相關(guān)的陷阱或者錯(cuò)誤了。因?yàn)閷?shí)體模型Entity的對(duì)象可能包括了其他實(shí)體的引用晤硕,在WCF里面就無(wú)法進(jìn)行序列化悼潭,出現(xiàn)錯(cuò)誤;而且基于WCF的時(shí)候舞箍,可能無(wú)法有效利用Express表達(dá)式舰褪,無(wú)法直接使用LINQ等問題都一股腦出現(xiàn)了。本文基于上面的種種問題疏橄,闡述了我的整個(gè)Entity Framework 實(shí)體框架的解決思路占拍,并且在其中引入了數(shù)據(jù)傳輸模型DTO來解決問題,本文主要介紹數(shù)據(jù)傳輸模型DTO和實(shí)體模型Entity的分離與聯(lián)合捎迫,從而實(shí)現(xiàn)我們通暢晃酒、高效的WCF應(yīng)用框架。
1窄绒、實(shí)體模型Entity無(wú)法在WCF中序列化
例如贝次,我們定義的Entity Framework 實(shí)體類里面包含了其他對(duì)象的引用,例如有一個(gè)Role對(duì)象彰导,有和其他表的關(guān)聯(lián)關(guān)系的蛔翅,默認(rèn)使用傳統(tǒng)方式,在實(shí)體類里面添加[DataContract]方式位谋。
/// <summary>
/// 角色
/// </summary>
[DataContract(IsReference = true)]
public class Role
{
/// <summary>
/// 默認(rèn)構(gòu)造函數(shù)(需要初始化屬性的在此處理)
/// </summary>
public Role()
{
this.ID= System.Guid.NewGuid().ToString();
//Children = new HashSet<Role>();
//Users = new HashSet<User>();
}
#region Property Members
[DataMember]
public virtual string ID { get; set; }
/// <summary>
/// 角色名稱
/// </summary>
[DataMember]
public virtual string Name { get; set; }
/// <summary>
/// 父ID
/// </summary>
[DataMember]
public virtual string ParentID { get; set; }
[DataMember]
public virtual ICollection<Role> Children { get; set; }
[DataMember]
public virtual Role Parent { get; set; }
[DataMember]
public virtual ICollection<User> Users { get; set; }
#endregion
}
在WCF服務(wù)接口里面使用代碼如下所示山析。
public class Service1 : IService1
{
public List<Role> GetAllRoles()
{
return IFactory.Instance<IRoleBLL>().GetAll().ToList();
}
.........
那么我們?cè)赪CF里面使用的時(shí)候,會(huì)得到下面的提示倔幼。
接收對(duì) http://localhost:11229/Service1.svc 的 HTTP 響應(yīng)時(shí)發(fā)生錯(cuò)誤盖腿。這可能是由于服務(wù)終結(jié)點(diǎn)綁定未使用 HTTP 協(xié)議造成的。這還可能是由于服務(wù)器中止了 HTTP 請(qǐng)求上下文(可能由于服務(wù)關(guān)閉)所致损同。有關(guān)詳細(xì)信息翩腐,請(qǐng)參見服務(wù)器日志。
默認(rèn)情況下膏燃,Entity Framework為了支持它的一些高級(jí)特性(延遲加載等)茂卦,默認(rèn)將自動(dòng)生成代理類是設(shè)置為true。如果我們需要禁止自動(dòng)生成代理類组哩,那么可以在數(shù)據(jù)庫(kù)操作上下文DbContext里面進(jìn)行處理設(shè)置等龙。
Configuration.ProxyCreationEnabled = false;
如果設(shè)置為false,那么WCF服務(wù)可以工作正常处渣,但是實(shí)體類對(duì)象里面的其他對(duì)象集合則為空了,也就是WCF無(wú)法返回這些引用的內(nèi)容蛛砰。
同時(shí)罐栈,在Entity Framework框架里面,這種把實(shí)體類貫穿各個(gè)層里面泥畅,也是一種不推薦的做法荠诬,由于WCF里面?zhèn)鬏數(shù)臄?shù)據(jù)都是序列號(hào)過的數(shù)據(jù),也無(wú)法像本地一樣利用LINQ來實(shí)現(xiàn)數(shù)據(jù)的處理操作的位仁。
那么我們應(yīng)該如何構(gòu)建基于WCF引用的Entity Framework實(shí)體框架呢柑贞?
2、數(shù)據(jù)傳輸對(duì)象DTO的引入
前面介紹了直接利用Entity Framework實(shí)體類對(duì)象的弊端聂抢,并且如果是一路到底都使用這個(gè)實(shí)體類钧嘶,里面的很多對(duì)象引用都是空的,對(duì)我們?cè)诮缑鎸邮褂貌槐懔帐瑁乙部赡芤l(fā)了很多WCF框架里面的一些相關(guān)問題有决。
我們根據(jù)上面的問題,引入了一個(gè)DTO(數(shù)據(jù)傳輸對(duì)象)的東西轿亮。
數(shù)據(jù)傳輸對(duì)象(DTO)是沒有行為的POCO對(duì)象疮薇,它的目的只是為了對(duì)領(lǐng)域?qū)ο筮M(jìn)行數(shù)據(jù)封裝,實(shí)現(xiàn)層與層之間的數(shù)據(jù)傳遞我注,界面表現(xiàn)層與應(yīng)用層之間是通過數(shù)據(jù)傳輸對(duì)象(DTO)進(jìn)行交互的按咒。數(shù)據(jù)傳輸對(duì)象DTO本身并不是業(yè)務(wù)對(duì)象,數(shù)據(jù)傳輸對(duì)象是根據(jù)UI的需求進(jìn)行設(shè)計(jì)的但骨。
這個(gè)對(duì)象和具體數(shù)據(jù)存儲(chǔ)的實(shí)體類是獨(dú)立的励七,它可以說是實(shí)體類的一個(gè)映射體,名稱可以和實(shí)體類不同奔缠,屬性數(shù)量也可以實(shí)體類不一致掠抬。那么既然在實(shí)體對(duì)象層外引入了另外一個(gè)DTO對(duì)象層,那么相互轉(zhuǎn)換肯定是避免不了的了校哎,我們?yōu)榱吮苊馐止さ挠成浞绞搅讲ǎ肓肆硗庖粋€(gè)強(qiáng)大的自動(dòng)化映射的工具AutoMapper,來幫助我們快速闷哆、高效腰奋、智能的實(shí)現(xiàn)兩個(gè)層對(duì)象的映射處理。
AutoMapper的使用比較簡(jiǎn)單抱怔,一般如果對(duì)象屬性一直劣坊,他們會(huì)實(shí)現(xiàn)屬性自動(dòng)映射了,如下所示屈留。
Mapper.CreateMap<RoleInfo, Role>();
如果兩者的屬性名稱不一致局冰,那么可以通過ForMember方式指定测蘑,類似下面代碼所示。
AutoMapper.Mapper.CreateMap<BlogEntry, BlogPostDto>()
.ForMember(dto => dto.PostId, opt => opt.MapFrom(entity => entity.ID));
AutoMapper也可以把映射信息寫到一個(gè)類里面康二,然后統(tǒng)一進(jìn)行加載碳胳。
Mapper.Initialize(cfg => { cfg.AddProfile<OrganizationProfile>();});
那么基于上面的圖示模式,由于我們采用代碼生成工具自動(dòng)生成的DTO和Entity赠摇,他們屬性名稱是保持一致的固逗,那么我們只需要在應(yīng)用層對(duì)它們兩者對(duì)象進(jìn)行相互映射就可以了浅蚪。
public class RoleService : BaseLocalService<RoleInfo, Role>, IRoleService
{
private IRoleBLL bll = null;
public RoleService() : base(IFactory.Instance<IRoleBLL>())
{
bll = baseBLL as IRoleBLL;
//DTO和Entity模型的相互映射
Mapper.CreateMap<RoleInfo, Role>();
Mapper.CreateMap<Role, RoleInfo>();
}
}
基于這個(gè)內(nèi)部對(duì)接的映射關(guān)系藕帜,我們就可以在Facade接口層提供統(tǒng)一的DTO對(duì)象服務(wù),而業(yè)務(wù)邏輯層(也就是利用Entity Framework 實(shí)體框架的處理成)則依舊使用它的Entity對(duì)象來傳遞惜傲。下面我提供幾個(gè)封裝好的基類接口供了解DTO和Entity的相互銜接處理洽故。
1)傳入DTO對(duì)象,并轉(zhuǎn)換為Entity對(duì)象盗誊,使用EF對(duì)象插入时甚。
/// <summary>
/// 插入指定對(duì)象到數(shù)據(jù)庫(kù)中
/// </summary>
/// <param name="dto">指定的對(duì)象</param>
/// <returns>執(zhí)行成功返回<c>true</c>,否則為<c>false</c></returns>
public virtual bool Insert(DTO dto)
{
Entity t = dto.MapTo<Entity>();
return baseBLL.Insert(t);
}
2)根據(jù)條件從EF框架中獲取Entity對(duì)象哈踱,并轉(zhuǎn)換后返回DTO對(duì)象
/// <summary>
/// 查詢數(shù)據(jù)庫(kù),返回指定ID的對(duì)象
/// </summary>
/// <param name="id">ID主鍵的值</param>
/// <returns>存在則返回指定的對(duì)象,否則返回Null</returns>
public virtual DTO FindByID(object id)
{
Entity t = baseBLL.FindByID(id);
return t.MapTo<DTO>();
}
3)根據(jù)條件從EF框架中獲取Entity集合對(duì)象荒适,并轉(zhuǎn)換為DTO列表對(duì)象
/// <summary>
/// 返回?cái)?shù)據(jù)庫(kù)所有的對(duì)象集合
/// </summary>
/// <returns></returns>
public virtual ICollection<DTO> GetAll()
{
ICollection<Entity> tList = baseBLL.GetAll();
return tList.MapToList<Entity, DTO>();
}
3、Entity Framework 實(shí)體框架結(jié)構(gòu)
基于方便管理的目的开镣,每個(gè)模塊都可以采用一種固定分層的方式來組織模塊的業(yè)務(wù)內(nèi)容刀诬,每個(gè)模塊都是以麻雀雖小、五臟俱全的方針實(shí)施邪财。實(shí)例模塊的整個(gè)業(yè)務(wù)邏輯層的項(xiàng)目結(jié)構(gòu)如下所示陕壹。
如果考慮使用WCF,那么整體的結(jié)構(gòu)和我之前的混合框架差不多树埠,各個(gè)模塊的職責(zé)基本沒什么變化糠馆,不過由原先在DAL層分開的各個(gè)實(shí)現(xiàn)層,變化為各個(gè)數(shù)據(jù)庫(kù)的Mapping層了怎憋,而模型增加了DTO又碌,具體項(xiàng)目結(jié)構(gòu)如下所示。
具體的項(xiàng)目說明如下所示:
EFRelationship
系統(tǒng)的業(yè)務(wù)模塊及接口绊袋、數(shù)據(jù)庫(kù)訪問模塊及接口毕匀、DTO對(duì)象、實(shí)體類對(duì)象愤炸、各種數(shù)據(jù)庫(kù)映射Mapping類等相關(guān)內(nèi)容期揪。該模塊內(nèi)容緊密結(jié)合Database2Sharp強(qiáng)大代碼生成工具生成的代碼、各層高度抽象繼承及使用泛型支持多數(shù)據(jù)庫(kù)规个。
EFRelationship.WCFLibrary
系統(tǒng)的WCF服務(wù)的業(yè)務(wù)邏輯模塊凤薛,該模塊通過引用文件方式姓建,把業(yè)務(wù)管理邏輯放在一起,方便WCF服務(wù)部署及調(diào)用缤苫。
EFRelationshipService
框架WCF服務(wù)模塊速兔,包括基礎(chǔ)服務(wù)模塊BaseWcf和業(yè)務(wù)服務(wù)模塊,他們?yōu)榱朔奖慊盍幔珠_管理發(fā)布涣狗。
EFRelationship.Caller
定義了具體業(yè)務(wù)模塊實(shí)現(xiàn)的Fa?ade應(yīng)用接口層,并對(duì)Winform調(diào)用方式和WCF調(diào)用方式進(jìn)行包裝的項(xiàng)目舒憾。
具體我們以一個(gè)會(huì)員系統(tǒng)設(shè)計(jì)為例镀钓,它的程序集關(guān)系如下所示。
我們來看看整個(gè)架構(gòu)的設(shè)計(jì)效果如下所示镀迂。
其中業(yè)務(wù)邏輯層模塊(以及其它應(yīng)用層)我們提供了很多基于實(shí)體框架的公用類庫(kù)(WHC.Framework.EF)丁溅,其中的繼承關(guān)系我們將它放大,了解其中的繼承細(xì)節(jié)關(guān)系探遵,效果如下所示窟赏。
上圖很好的概述了我的EF實(shí)體框架的設(shè)計(jì)思路,這些層最終還是通過代碼生成工具Database2Sharp進(jìn)行一體化的生成箱季,以提高快速生產(chǎn)的目的涯穷,并且統(tǒng)一所有的命名規(guī)則。后面有機(jī)會(huì)再寫一篇隨筆介紹代碼生成的邏輯部分藏雏。
上圖左邊突出的兩個(gè)工廠類拷况,一個(gè)IFactory是基于本地直連方式,也就是直接使用EF框架的對(duì)象進(jìn)行處理诉稍;一個(gè)CallerFactory是基于Facade層實(shí)現(xiàn)的接口蝠嘉,根據(jù)配置指向WCF數(shù)據(jù)服務(wù)對(duì)象,或者直連對(duì)象進(jìn)行數(shù)據(jù)的操作處理杯巨。