在使用Entity Framework
实体框架的时候,我们大多数时候操作的都是实体模型Entity,这个和数据库操作上下文结合,可以利用LINQ等各种方便手段,实现起来非常方便,一切看起来很美好。但是如果考虑使用WCF的时候,可能就会碰到很多相关的陷阱或者错误了。因为实体模型Entity的对象可能包括了其他实体的引用,在WCF里面就无法进行序列化,出现错误;而且基于WCF的时候,可能无法有效利用Express表达式,无法直接使用LINQ等问题都一股脑出现了。本文基于上面的种种问题,阐述了我的整个Entity
Framework
实体框架的解决思路,并且在其中引入了数据传输模型DTO来解决问题,本文主要介绍数据传输模型DTO和实体模型Entity的分离与联合,从而实现我们通畅、高效的WCF应用框架。

在上篇随笔《Entity Framework
实体框架的形成之旅–数据传输模型DTO和实体模型Entity的分离与联合》里面,介绍了在Entity
Framework
实体框架里面引入了DTO的对象,通过数据传输模型DTO和实体模型Entity的分离与联合,很好的隔离了它们的关系,使得即使是复杂的实体模型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 响应时发生错误。这可能是由于服务终结点绑定未使用 HTTP
协议造成的。这还可能是由于服务器中止了 HTTP
请求上下文(可能由于服务关闭)所致。有关详细信息,请参见服务器日志。

默认情况下,Entity
Framework为了支持它的一些高级特性(延迟加载等),默认将自动生成代理类是设置为true。如果我们需要禁止自动生成代理类,那么可以在数据库操作上下文DbContext里面进行处理设置。

Configuration.ProxyCreationEnabled = false;

如果设置为false,那么WCF服务可以工作正常,但是实体类对象里面的其他对象集合则为空了,也就是WCF无法返回这些引用的内容。

同时,在Entity
Framework框架里面,这种把实体类贯穿各个层里面,也是一种不推荐的做法,由于WCF里面传输的数据都是序列号过的数据,也无法像本地一样利用LINQ来实现数据的处理操作的。

那么我们应该如何构建基于WCF引用的Entity
Framework实体框架呢?

1、常规业务的增加、更新操作

对于业务对象的增加,由于我们引入了DTO对象,因此在界面的处理端,肯定也是利用了DTO对象进行的,如下代码是增加、修改的处理操作处理。

       public bool SaveAddNew()
        {
            DictDataInfo info = new DictDataInfo();
            SetInfo(info);

            try
            {
                bool succeed = CallerFactory<IDictDataService>.Instance.Insert(info);
                if (succeed)
                {
                    int intSeq = 0;
                    string seqValue = this.txtSeq.Text;
                    if (int.TryParse(seqValue, out intSeq))
                    {
                        this.txtSeq.Text = (intSeq + 1).ToString().PadLeft(seqValue.Trim().Length, '0');
                    }
                    this.txtName.Focus();
                    this.txtName.SelectAll();
                }
                return succeed;
            }
            catch (Exception ex)
            {
                LogTextHelper.Error(ex);
                MessageDxUtil.ShowError(ex.Message);
            }
            return false;
        }

        public override bool SaveUpdated()
        {
            DictDataInfo info = CallerFactory<IDictDataService>.Instance.FindByID(ID);

            if (info != null)
            {
                SetInfo(info);

                try
                {
                    bool succeed = CallerFactory<IDictDataService>.Instance.Update(info, info.ID.ToString());
                    return succeed;
                }
                catch (Exception ex)
                {
                    LogTextHelper.Error(ex);
                    MessageDxUtil.ShowError(ex.Message);
                }
            }

            return false;
        }

上面的操作,和我之前的混合框架的使用代码是差不多的,原来的基于EnterpriseLibrary架构的框架,实体类采用的就是
“表名+Info”
的方式,虽然这里的**Info代表DTO对象,是实体框架的Entity对象的映射类,不过总体业务上的处理代码是差不多的了,这也是我希望看到比较平滑过渡和容易理解的改变之一。

2、数据传输对象DTO的引入

前面介绍了直接利用Entity
Framework实体类对象的弊端,并且如果是一路到底都使用这个实体类,里面的很多对象引用都是空的,对我们在界面层使用不便,而且也可能引发了很多WCF框架里面的一些相关问题。

我们根据上面的问题,引入了一个DTO(数据传输对象)的东西。

数据传输对象(DTO)是没有行为的POCO对象,它的目的只是为了对领域对象进行数据封装,实现层与层之间的数据传递,界面表现层与应用层之间是通过数据传输对象(DTO)进行交互的。数据传输对象DTO本身并不是业务对象,数据传输对象是根据UI的需求进行设计的。

这个对象和具体数据存储的实体类是独立的,它可以说是实体类的一个映射体,名称可以和实体类不同,属性数量也可以实体类不一致。那么既然在实体对象层外引入了另外一个DTO对象层,那么相互转换肯定是避免不了的了,我们为了避免手工的映射方式,引入了另外一个强大的自动化映射的工具AutoMapper,来帮助我们快速、高效、智能的实现两个层对象的映射处理。

图片 1

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>();
        }

 

2、基于DTO表达式的查询处理

如果对于查询,我们知道,如果使用字符串的条件表达式,一般也是可以实现处理操作的,不过就是需要硬编码SQL语句,对于一些安全性高一点的处理,可能不太好,由于实体框架可以采用Lamda表达式来进行查询,那么我们是否也可以在界面采用Lamda表达式来替代条件的SQL语句呢?

我们知道,上篇随笔已经介绍了引入DTO对象,用来解耦实体框架的对象模型,如下所示的模块分层场景。

图片 2

这样我们在设计BLL业务逻辑层的时候,肯定还是可以使用实体框架的Expression<Func<T,
bool>>表达式的,如IBLL层的接口定义对于Expression表达式的使用接口如下所示。

        /// <summary>
        /// 根据条件查询数据库,并返回对象集合
        /// </summary>
        /// <param name="match">条件表达式</param>
        /// <returns></returns>
        IList<T> Find(Expression<Func<T, bool>> match);

        /// <summary>
        /// 根据条件表达式返回可查询的记录源
        /// </summary>
        /// <param name="match">查询条件</param>
        /// <param name="sortPropertyName">排序属性名称</param>
        /// <param name="isDescending">如果为true则为降序,否则为升序</param>
        /// <returns></returns>
        IQueryable<T> GetQueryable(Expression<Func<T, bool>> match, string sortPropertyName, bool isDescending = true);

不过在门面层Facade层就不能继续使用了这种Expression<Func<T,
bool>>表达式的了,同时也不能在Facade层使用IQueryable<T>接口,因为WCF服务无法序列化这个接口的。

那基于这个原因,我们应该如何传递Expression<Func<T, bool>>
match这个条件参数的表达式呢,答案是引入Serialize.Linq组件,使用ExpressionNode对象进行承载,最后再把它解析为Expression<Func<T,
bool>> match进行处理就可以了。

        /// <summary>
        /// 根据条件查询数据库,并返回对象集合
        /// </summary>
        /// <param name="match">条件表达式</param>
        /// <returns></returns>
        [OperationContract(Name = "Find")]
        IList<DTO> Find(ExpressionNode match);

        /// <summary>
        /// 根据条件查询数据库,并返回对象集合(异步)
        /// </summary>
        /// <param name="match">条件表达式</param>
        /// <returns></returns>
        [OperationContract(Name = "FindAsync")]
        Task<IList<DTO>> FindAsync(ExpressionNode match);

我们在客户端界面里面处理的话,就需要构建一个ExpressionNode对象,查询处理代码如下所示。

这里主要需要先从Expression<Function<T,boo>>到ExpressionNode,通过调用expression.ToExpressionNode();进行处理得到,如下代码所示。

        private ExpressionNode GetCondtionSql()
        {
            Expression<Func<DictDataInfo, bool>> expression = p => p.DictType_ID == this.lblDictType.Tag.ToString();
            var queryNode = expression.ToExpressionNode();
            return queryNode;
        }

        private void BindData()
        {
            #region 添加别名解析
            this.winGridViewPager1.DisplayColumns = "Name,Value,Seq,Remark,EditTime";
            this.winGridViewPager1.AddColumnAlias("ID", "编号");
            this.winGridViewPager1.AddColumnAlias("DictType_ID", "字典大类");
            this.winGridViewPager1.AddColumnAlias("Name", "项目名称");
            this.winGridViewPager1.AddColumnAlias("Value", "项目值");
            this.winGridViewPager1.AddColumnAlias("Seq", "字典排序");
            this.winGridViewPager1.AddColumnAlias("Remark", "备注");
            this.winGridViewPager1.AddColumnAlias("Editor", "修改用户");
            this.winGridViewPager1.AddColumnAlias("EditTime", "更新日期");
            #endregion

            if (this.lblDictType.Tag != null)
            {
                ExpressionNode condition = GetCondtionSql();
                WHC.Pager.Entity.PagerInfo pagerInfo = this.winGridViewPager1.PagerInfo;
                IList<DictDataInfo> list = CallerFactory<IDictDataService>.Instance.FindWithPager(condition, ref pagerInfo);
                //this.winGridViewPager1.PagerInfo.RecordCount = pagerInfo.RecordCount;
                this.winGridViewPager1.DataSource = new WHC.Pager.WinControl.SortableBindingList<DictDataInfo>(list);
            }
        }

我们在Facade接口实现端,就需要把ExpressionNode反过来变成Expression<Function<T,boo>>对象。

        /// <summary>
        /// 根据条件查询数据库,并返回对象集合
        /// </summary>
        /// <param name="match">条件表达式</param>
        /// <returns></returns>
        public virtual IList<DTO> Find(ExpressionNode match)
        {
            Expression<Func<Entity, bool>> mappedSelector = ConvertExpression(match);

            IList<Entity> tList = baseBLL.Find(mappedSelector);
            return tList.MapToList<Entity, DTO>();
        }

        /// <summary>
        /// 根据条件查询数据库,并返回对象集合(异步)
        /// </summary>
        /// <param name="match">条件表达式</param>
        /// <returns></returns>
        public virtual async Task<IList<DTO>> FindAsync(ExpressionNode match)
        {
            Expression<Func<Entity, bool>> mappedSelector = ConvertExpression(match);

            IList<Entity> tList = await baseBLL.FindAsync(mappedSelector);

            IList<DTO> collection = tList.MapToList<Entity, DTO>();
            return await Task<IList<DTO>>.FromResult(collection);
        }

这样我们就可以很好利用Entity Framework 实体框架的LINQ表达式进行查询了。

3、Entity Framework 实体框架结构

基于方便管理的目的,每个模块都可以采用一种固定分层的方式来组织模块的业务内容,每个模块都是以麻雀虽小、五脏俱全的方针实施。实例模块的整个业务逻辑层的项目结构如下所示。

图片 3

如果考虑使用WCF,那么整体的结构和我之前的混合框架差不多,各个模块的职责基本没什么变化,不过由原先在DAL层分开的各个实现层,变化为各个数据库的Mapping层了,而模型增加了DTO,具体项目结构如下所示。

图片 4

具体的项目说明如下所示:

EFRelationship

系统的业务模块及接口、数据库访问模块及接口、DTO对象、实体类对象、各种数据库映射Mapping类等相关内容。该模块内容紧密结合Database2Sharp强大代码生成工具生成的代码、各层高度抽象继承及使用泛型支持多数据库。

EFRelationship.WCFLibrary

系统的WCF服务的业务逻辑模块,该模块通过引用文件方式,把业务管理逻辑放在一起,方便WCF服务部署及调用。

EFRelationshipService

框架WCF服务模块,包括基础服务模块BaseWcf和业务服务模块,他们为了方便,分开管理发布。

EFRelationship.Caller

定义了具体业务模块实现的Façade应用接口层,并对Winform调用方式和WCF调用方式进行包装的项目。

具体我们以一个会员系统设计为例,它的程序集关系如下所示。

图片 5

我们来看看整个架构的设计效果如下所示。

图片 6

其中业务逻辑层模块(以及其它应用层)我们提供了很多基于实体框架的公用类库(WHC.Framework.EF),其中的继承关系我们将它放大,了解其中的继承细节关系,效果如下所示。

图片 7

上图很好的概述了我的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的分离与联合

 

3、多条件的处理方式

上面的查询代码里面,我们注意到了,条件里面只有一个条件,如下代码。

        private ExpressionNode GetCondtionSql()
        {
            Expression<Func<DictDataInfo, bool>> expression = p => p.DictType_ID == this.lblDictType.Tag.ToString();
            var queryNode = expression.ToExpressionNode();
            return queryNode;
        }

那么对于有多个条件的话,处理就需要特殊处理了,否则就没法组合多个条件进行查询了,多个条件的处理是如何的呢?

如对于日志查询界面来说,如果是采用条件语句的方式,需要使用下面的代码组装语句,然后通过接口方法进行获取数据。

        /// <summary>
        /// 根据查询条件构造查询语句
        /// </summary> 
        private string GetConditionSql()
        {
            SearchCondition condition = new SearchCondition();
            condition.AddCondition("LoginName", this.txtLoginName.Text, SqlOperator.Like);
            condition.AddCondition("FullName", this.txtRealName.Text, SqlOperator.Like);
            condition.AddCondition("Note", this.txtNote.Text, SqlOperator.Like);
            condition.AddCondition("IPAddress", this.txtIPAddress.Text, SqlOperator.Like);
            condition.AddCondition("MacAddress", this.txtMacAddress.Text, SqlOperator.Like);

            if (dateTimePicker1.Text.Length > 0)
            {
                condition.AddCondition("LastUpdated", Convert.ToDateTime(dateTimePicker1.DateTime.ToString("yyyy-MM-dd")), SqlOperator.MoreThanOrEqual);
            }
            if (dateTimePicker2.Text.Length > 0)
            {
                condition.AddCondition("LastUpdated", Convert.ToDateTime(dateTimePicker2.DateTime.AddDays(1).ToString("yyyy-MM-dd")), SqlOperator.LessThanOrEqual);
            }

            string systemType = this.txtSystemType.GetComboBoxValue();
            if (!string.IsNullOrEmpty(systemType))
            {
                condition.AddCondition("SystemType_ID", systemType, SqlOperator.Equal);
            }

            //如果是公司管理员,增加公司标识
            if (Portal.gc.UserInRole(RoleInfo.CompanyAdminName))
            {
                condition.AddCondition("Company_ID", Portal.gc.UserInfo.Company_ID, SqlOperator.Equal);
            }

            string where = condition.BuildConditionSql().Replace("Where", "");
            //如果是单击节点得到的条件,则使用树列表的,否则使用查询条件的
            if (!string.IsNullOrEmpty(treeConditionSql))
            {
                where = treeConditionSql;
            }
            return where;
        }

这里有很多条件,通过 SearchCondition
对象,我们能够很方便组合多个条件的查询,然后生成所需的条件语句就可以了,那么对于实体框架里面,我们需要采用Lamda表达式的话,应该如何构建对象并传入给接口方法呢,代码如下所示。

        /// <summary>
        /// 根据查询条件构造查询语句
        /// </summary> 
        private ExpressionNode GetConditionSql()
        {
            Expression<Func<LoginLogInfo, bool>> expression = p => true;
            if (!string.IsNullOrEmpty(this.txtLoginName.Text))
            {
                expression = expression.And(x => x.LoginName.Contains(this.txtLoginName.Text));
            }
            if (!string.IsNullOrEmpty(this.txtRealName.Text))
            {
                expression = expression.And(x => x.FullName.Contains(this.txtRealName.Text));
            }
            if (!string.IsNullOrEmpty(this.txtNote.Text))
            {
                expression = expression.And(x => x.Note.Contains(this.txtNote.Text));
            }
            if (!string.IsNullOrEmpty(this.txtIPAddress.Text))
            {
                expression = expression.And(x => x.IPAddress.Contains(this.txtIPAddress.Text));
            }
            if (!string.IsNullOrEmpty(this.txtMacAddress.Text))
            {
                expression = expression.And(x => x.MacAddress.Contains(this.txtMacAddress.Text));
            }

            if (dateTimePicker1.Text.Length > 0)
            {
                expression = expression.And(x => x.LastUpdated >= Convert.ToDateTime(dateTimePicker1.DateTime.ToString("yyyy-MM-dd")));
            }
            if (dateTimePicker2.Text.Length > 0)
            {
                expression = expression.And(x => x.LastUpdated <= Convert.ToDateTime(dateTimePicker2.DateTime.AddDays(1).ToString("yyyy-MM-dd")));
            }

            string systemType = this.txtSystemType.GetComboBoxValue();
            if (!string.IsNullOrEmpty(systemType))
            {
                expression = expression.And(x => x.SystemType_ID == systemType);
            }

            //如果是公司管理员,增加公司标识
            if (Portal.gc.UserInRole(RoleInfo.CompanyAdminName))
            {
                expression = expression.And(x => x.Company_ID == Portal.gc.UserInfo.Company_ID);
            }

            //如果是单击节点得到的条件,则使用树列表的,否则使用查询条件的
            if (treeCondition != null)
            {
                expression = treeCondition;
            }
            return expression.ToExpressionNode();
        }

这里我们注意到expression.And或者expression.Or函数,它不是这个expression对象的方法的,是我们针对这个做的一个扩展类函数,它专门处理 Lamda-Expression表达式的扩展,方便组合多个条件,如两个表达式条件可以组合为AND或者OR条件方式。

图片 8

这样我们在界面处理的时候,绑定数据的处理方法就可以如下所示了。

        public void BindData()
        {
            #region 添加别名解析
            this.winGridViewPager1.DisplayColumns = "ID,User_ID,LoginName,FullName,Company_ID,CompanyName,Note,IPAddress,MacAddress,SystemType_ID,LastUpdated";
            this.winGridViewPager1.ColumnNameAlias = CallerFactory<ILoginLogService>.Instance.GetColumnNameAlias();//字段列显示名称转义

            #endregion

            ExpressionNode where = GetConditionSql();
            PagerInfo PagerInfo = this.winGridViewPager1.PagerInfo;
            IList<LoginLogInfo> list = CallerFactory<ILoginLogService>.Instance.FindWithPager(where, ref PagerInfo);
            this.winGridViewPager1.DataSource = new WHC.Pager.WinControl.SortableBindingList<LoginLogInfo>(list);
        }

以上就是我对于混合型的Entity
Framework 实体框架的界面操作,总结的几种分析场景,希望对大家理解在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的分离与联合

Entity Framework
实体框架的形成之旅–界面操作的几个典型的处理(8)

相关文章