Entity Framework 實體框架的形成之旅--數據傳輸模型DTO和實體模型Entity的分離與聯合


在使用Entity Framework 實體框架的時候,我們大多數時候操作的都是實體模型Entity,這個和數據庫操作上下文結合,可以利用LINQ等各種方便手段,實現起來非常方便,一切看起來很美好。但是如果考慮使用WCF的時候,可能就會碰到很多相關的陷阱或者錯誤了。因為實體模型Entity的對象可能包括了其他實體的引用,在WCF里面就無法進行序列化,出現錯誤;而且基於WCF的時候,可能無法有效利用Express表達式,無法直接使用LINQ等問題都一股腦出現了。本文基於上面的種種問題,闡述了我的整個Entity Framework 實體框架的解決思路,並且在其中引入了數據傳輸模型DTO來解決問題,本文主要介紹數據傳輸模型DTO和實體模型Entity的分離與聯合,從而實現我們通暢、高效的WCF應用框架。

1、實體模型Entity無法在WCF中序列化

例如,我們定義的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 請求上下文(可能由於服務關閉)所致。有關詳細信息,請參見服務器日志。

默認情況下,Entity Framework為了支持它的一些高級特性(延遲加載等),默認將自動生成代理類是設置為true。如果我們需要禁止自動生成代理類,那么可以在數據庫操作上下文DbContext里面進行處理設置。

Configuration.ProxyCreationEnabled = false;

如果設置為false,那么WCF服務可以工作正常,但是實體類對象里面的其他對象集合則為空了,也就是WCF無法返回這些引用的內容。

同時,在Entity Framework框架里面,這種把實體類貫穿各個層里面,也是一種不推薦的做法,由於WCF里面傳輸的數據都是序列號過的數據,也無法像本地一樣利用LINQ來實現數據的處理操作的。

那么我們應該如何構建基於WCF引用的Entity Framework實體框架呢?

2、數據傳輸對象DTO的引入

前面介紹了直接利用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>();
        }

 

3、Entity Framework 實體框架結構

基於方便管理的目的,每個模塊都可以采用一種固定分層的方式來組織模塊的業務內容,每個模塊都是以麻雀雖小、五臟俱全的方針實施。實例模塊的整個業務邏輯層的項目結構如下所示。

如果考慮使用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)

Entity Framework 實體框架的形成之旅--數據傳輸模型DTO和實體模型Entity的分離與聯合

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM