在使用Entity Framework 實體框架的時候,咱們大多數時候操做的都是實體模型Entity,這個和數據庫操做上下文結合,能夠利用LINQ等各類方便手段,實現起來很是方便,一切看起來很美好。可是若是考慮使用WCF的時候,可能就會碰到不少相關的陷阱或者錯誤了。由於實體模型Entity的對象可能包括了其餘實體的引用,在WCF裏面就沒法進行序列化,出現錯誤;並且基於WCF的時候,可能沒法有效利用Express表達式,沒法直接使用LINQ等問題都一股腦出現了。本文基於上面的種種問題,闡述了個人整個Entity Framework 實體框架的解決思路,而且在其中引入了數據傳輸模型DTO來解決問題,本文主要介紹數據傳輸模型DTO和實體模型Entity的分離與聯合,從而實現咱們通暢、高效的WCF應用框架。html
例如,咱們定義的Entity Framework 實體類裏面包含了其餘對象的引用,例若有一個Role對象,有和其餘表的關聯關係的,默認使用傳統方式,在實體類裏面添加[DataContract]方式。數據庫
/// <summary> /// 角色 /// </summary> [DataContract(IsReference = true)] public class Role { /// <summary> /// 默認構造函數(須要初始化屬性的在此處理) /// </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服務接口裏面使用代碼以下所示。服務器
public class Service1 : IService1 { public List<Role> GetAllRoles() { return IFactory.Instance<IRoleBLL>().GetAll().ToList(); } .........
那麼咱們在WCF裏面使用的時候,會獲得下面的提示。架構
接收對 http://localhost:11229/Service1.svc 的 HTTP 響應時發生錯誤。這多是因爲服務終結點綁定未使用 HTTP 協議形成的。這還多是因爲服務器停止了 HTTP 請求上下文(可能因爲服務關閉)所致。有關詳細信息,請參見服務器日誌。app
默認狀況下,Entity Framework爲了支持它的一些高級特性(延遲加載等),默認將自動生成代理類是設置爲true。若是咱們須要禁止自動生成代理類,那麼能夠在數據庫操做上下文DbContext裏面進行處理設置。框架
Configuration.ProxyCreationEnabled = false;
若是設置爲false,那麼WCF服務能夠工做正常,可是實體類對象裏面的其餘對象集合則爲空了,也就是WCF沒法返回這些引用的內容。異步
同時,在Entity Framework框架裏面,這種把實體類貫穿各個層裏面,也是一種不推薦的作法,因爲WCF裏面傳輸的數據都是序列號過的數據,也沒法像本地同樣利用LINQ來實現數據的處理操做的。函數
那麼咱們應該如何構建基於WCF引用的Entity Framework實體框架呢?工具
前面介紹了直接利用Entity Framework實體類對象的弊端,而且若是是一路到底都使用這個實體類,裏面的不少對象引用都是空的,對咱們在界面層使用不便,並且也可能引起了不少WCF框架裏面的一些相關問題。優化
咱們根據上面的問題,引入了一個DTO(數據傳輸對象)的東西。
數據傳輸對象(DTO)是沒有行爲的POCO對象,它的目的只是爲了對領域對象進行數據封裝,實現層與層之間的數據傳遞,界面表現層與應用層之間是經過數據傳輸對象(DTO)進行交互的。數據傳輸對象DTO自己並非業務對象,數據傳輸對象是根據UI的需求進行設計的。
這個對象和具體數據存儲的實體類是獨立的,它能夠說是實體類的一個映射體,名稱能夠和實體類不一樣,屬性數量也能夠實體類不一致。那麼既然在實體對象層外引入了另一個DTO對象層,那麼相互轉換確定是避免不了的了,咱們爲了不手工的映射方式,引入了另一個強大的自動化映射的工具AutoMapper,來幫助咱們快速、高效、智能的實現兩個層對象的映射處理。
AutoMapper的使用比較簡單,通常若是對象屬性一直,他們會實現屬性自動映射了,以下所示。
Mapper.CreateMap<RoleInfo, Role>();
若是二者的屬性名稱不一致,那麼能夠經過ForMember方式指定,相似下面代碼所示。
AutoMapper.Mapper.CreateMap<BlogEntry, BlogPostDto>()
.ForMember(dto => dto.PostId, opt => opt.MapFrom(entity => entity.ID));
AutoMapper也能夠把映射信息寫到一個類裏面,而後統一進行加載。
Mapper.Initialize(cfg => { cfg.AddProfile<OrganizationProfile>(); });
那麼基於上面的圖示模式,因爲咱們採用代碼生成工具自動生成的DTO和Entity,他們屬性名稱是保持一致的,那麼咱們只須要在應用層對它們二者對象進行相互映射就能夠了。
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>(); } }
基於這個內部對接的映射關係,咱們就能夠在Facade接口層提供統一的DTO對象服務,而業務邏輯層(也就是利用Entity Framework 實體框架的處理成)則依舊使用它的Entity對象來傳遞。下面我提供幾個封裝好的基類接口供瞭解DTO和Entity的相互銜接處理。
1)傳入DTO對象,並轉換爲Entity對象,使用EF對象插入。
/// <summary> /// 插入指定對象到數據庫中 /// </summary> /// <param name="dto">指定的對象</param> /// <returns>執行成功返回<c>true</c>,不然爲<c>false</c></returns> public virtual bool Insert(DTO dto) { Entity t = dto.MapTo<Entity>(); return baseBLL.Insert(t); }
2)根據條件從EF框架中獲取Entity對象,並轉換後返回DTO對象
/// <summary> /// 查詢數據庫,返回指定ID的對象 /// </summary> /// <param name="id">ID主鍵的值</param> /// <returns>存在則返回指定的對象,不然返回Null</returns> public virtual DTO FindByID(object id) { Entity t = baseBLL.FindByID(id); return t.MapTo<DTO>(); }
3)根據條件從EF框架中獲取Entity集合對象,並轉換爲DTO列表對象
/// <summary> /// 返回數據庫全部的對象集合 /// </summary> /// <returns></returns> public virtual ICollection<DTO> GetAll() { ICollection<Entity> tList = baseBLL.GetAll(); return tList.MapToList<Entity, DTO>(); }
基於方便管理的目的,每一個模塊均可以採用一種固定分層的方式來組織模塊的業務內容,每一個模塊都是以麻雀雖小、五臟俱全的方針實施。實例模塊的整個業務邏輯層的項目結構以下所示。
若是考慮使用WCF,那麼總體的結構和我以前的混合框架差很少,各個模塊的職責基本沒什麼變化,不過由原先在DAL層分開的各個實現層,變化爲各個數據庫的Mapping層了,而模型增長了DTO,具體項目結構以下所示。
具體的項目說明以下所示:
EFRelationship |
系統的業務模塊及接口、數據庫訪問模塊及接口、DTO對象、實體類對象、各類數據庫映射Mapping類等相關內容。該模塊內容緊密結合Database2Sharp強大代碼生成工具生成的代碼、各層高度抽象繼承及使用泛型支持多數據庫。 |
EFRelationship.WCFLibrary |
系統的WCF服務的業務邏輯模塊,該模塊經過引用文件方式,把業務管理邏輯放在一塊兒,方便WCF服務部署及調用。 |
EFRelationshipService |
框架WCF服務模塊,包括基礎服務模塊BaseWcf和業務服務模塊,他們爲了方便,分開管理髮布。 |
EFRelationship.Caller |
定義了具體業務模塊實現的Façade應用接口層,並對Winform調用方式和WCF調用方式進行包裝的項目。 |
具體咱們以一個會員系統設計爲例,它的程序集關係以下所示。
咱們來看看整個架構的設計效果以下所示。
其中業務邏輯層模塊(以及其它應用層)咱們提供了不少基於實體框架的公用類庫(WHC.Framework.EF),其中的繼承關係咱們將它放大,瞭解其中的繼承細節關係,效果以下所示。
上圖很好的概述了個人EF實體框架的設計思路,這些層最終仍是經過代碼生成工具Database2Sharp進行一體化的生成,以提升快速生產的目的,而且統一全部的命名規則。後面有機會再寫一篇隨筆介紹代碼生成的邏輯部分。
上圖左邊突出的兩個工廠類,一個IFactory是基於本地直連方式,也就是直接使用EF框架的對象進行處理;一個CallerFactory是基於Facade層實現的接口,根據配置指向WCF數據服務對象,或者直連對象進行數據的操做處理。
這個系列文章以下所示:
Entity Framework 實體框架的造成之旅--基於泛型的倉儲模式的實體框架(1)
Entity Framework 實體框架的造成之旅--利用Unity對象依賴注入優化實體框架(2)
Entity Framework 實體框架的造成之旅--基類接口的統一和異步操做的實現(3)
Entity Framework 實體框架的造成之旅--實體數據模型 (EDM)的處理(4)
Entity Framework 實體框架的造成之旅--Code First的框架設計(5)
Entity Framework 實體框架的造成之旅--Code First模式中使用 Fluent API 配置(6)