Entity Framework 实体框架的形成之旅–实体框架的支出的多少个经验总计

在前阵子,我对实体框架举办了必然的钻研,然后把整个学习的进程开了一个多级,以渐渐深切的不二法门解读实体框架的有关技术,期间经常遇到一些新的题目亟待潜入研讨。本文继续前边的主旨介绍,着重从全部性的来总结一下实体框架的一些下面,希望针对这几个实际问题,和我们进行学习互换。

我的方方面面实体框架的求学和讨论,是以本人的Winform框架顺利晋级到那一个实体框架基础上为一个阶段截止,那多少个等级工作很多,从初始客运联网售票的WebAPI平台的支出,到微软实体框架的尖锐钻研,以及《基于Metronic的Bootstrap开发框架经验总计》的大旨学习和享受等等方面,都混到一起来了,两个主旨之间穿插着写一些小说,也是目的在于把温馨的上学过程举行记录总计,不用等到结尾全体遗忘了。

1、实体框架主键的类型约束问题

 在我们搭建整个实体框架的过程中,咱们一般都是空洞封装处理很多基础的增删改查、分页等科普的多少处理效果,如下所示。

        /// <summary>
        /// 更新对象属性到数据库中
        /// </summary>
        /// <param name="t">指定的对象</param>
        /// <param name="key">主键的值</param>
        /// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns>
        bool Update(T t, object key);

        /// <summary>
        /// 更新对象属性到数据库中(异步)
        /// </summary>
        /// <param name="t">指定的对象</param>
        /// <param name="key">主键的值</param>
        /// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns>
        Task<bool> UpdateAsync(T t, object key);

        /// <summary>
        /// 根据指定对象的ID,从数据库中删除指定对象
        /// </summary>
        /// <param name="id">对象的ID</param>
        /// <returns>执行成功返回<c>true</c>,否则为<c>false</c>。</returns>
        bool Delete(object id);

        /// <summary>
        /// 根据指定对象的ID,从数据库中删除指定对象(异步)
        /// </summary>
        /// <param name="id">对象的ID</param>
        /// <returns>执行成功返回<c>true</c>,否则为<c>false</c>。</returns>
        Task<bool> DeleteAsync(object id);

        /// <summary>
        /// 查询数据库,返回指定ID的对象
        /// </summary>
        /// <param name="id">ID主键的值</param>
        /// <returns>存在则返回指定的对象,否则返回Null</returns>
        T FindByID(object id);

        /// <summary>
        /// 查询数据库,返回指定ID的对象(异步)
        /// </summary>
        /// <param name="id">ID主键的值</param>
        /// <returns>存在则返回指定的对象,否则返回Null</returns>
        Task<T> FindByIDAsync(object id);

地点的外键统一定义为object类型,因为大家为了主键类型通用的考虑。

在实际上表的外键类型或者是很多种的,如可能是广阔的字符类型,也说不定是int类型,也说不定是long类型等等。假诺我们立异、查找、删除整形类型的笔录的时候,那么可能机会见世错误:

The argument types 'Edm.Int32' and 'Edm.String' are incompatible for this operation.

这多少个错误就是主键类型不兼容导致的,我们操作这一个接口的时候,一定要传播对应项目给它们,才能健康的处理。

当然想尝试在里头开展转移处理为不易的品类的,然而没有找到很好的解决方案来鉴别和处理,因而最好的化解方法,就是我们调用这个有object类型主键的接口时,传入正确的品种即可。

                    RoleInfo info = CallerFactory<IRoleService>.Instance.FindByID(currentID.ToInt32());
                    if (info != null)
                    {
                        info = SetRoleInfo(info);
                        CallerFactory<IRoleService>.Instance.Update(info, info.ID);

                        RefreshTreeView();
                    }

又或者是底下的代码:

        /// <summary>
        /// 分页控件删除操作
        /// </summary>
        private void winGridViewPager1_OnDeleteSelected(object sender, EventArgs e)
        {
            if (MessageDxUtil.ShowYesNoAndTips("您确定删除选定的记录么?") == DialogResult.No)
            {
                return;
            }

            int[] rowSelected = this.winGridViewPager1.GridView1.GetSelectedRows();
            foreach (int iRow in rowSelected)
            {
                string ID = this.winGridViewPager1.GridView1.GetRowCellDisplayText(iRow, "ID");
                CallerFactory<IDistrictService>.Instance.Delete(ID.ToInt64());
            }

            BindData();
        }

 

2、递归函数的拍卖

在无数时候,我们都会用到递归函数的处理,这样可以使得我们把整个列表的情节都创立的领到出来,是我们开发常见的知识点之一。

但是貌似在拍卖LINQ的时候,它的递归函数的处理和大家见怪不怪的做法有一对距离。

例如我们假如要赢得一个树形机构列表,假如我们指定了一个起始的单位节点ID,大家需要递归获取下面的具有层次的会聚的时候,常规的做法如下所示。

        /// <summary>
        /// 根据指定机构节点ID,获取其下面所有机构列表
        /// </summary>
        /// <param name="parentId">指定机构节点ID</param>
        /// <returns></returns>
        public List<OUInfo> GetAllOUsByParent(int parentId)
        {
            List<OUInfo> list = new List<OUInfo>();
            string sql = string.Format("Select * From {0} Where Deleted <> 1 Order By PID, Name ", tableName);

            DataTable dt = SqlTable(sql);
            string sort = string.Format("{0} {1}", GetSafeFileName(sortField), isDescending ? "DESC" : "ASC");
            DataRow[] dataRows = dt.Select(string.Format(" PID = {0}", parentId), sort);
            for (int i = 0; i < dataRows.Length; i++)
            {
                string id = dataRows[i]["ID"].ToString();
                list.AddRange(GetOU(id, dt));
            }

            return list;
        }

        private List<OUInfo> GetOU(string id, DataTable dt)
        {
            List<OUInfo> list = new List<OUInfo>();

            OUInfo ouInfo = this.FindByID(id);
            list.Add(ouInfo);

            string sort = string.Format("{0} {1}", GetSafeFileName(sortField), isDescending ? "DESC" : "ASC");
            DataRow[] dChildRows = dt.Select(string.Format(" PID={0} ", id), sort);
            for (int i = 0; i < dChildRows.Length; i++)
            {
                string childId = dChildRows[i]["ID"].ToString();
                List<OUInfo> childList = GetOU(childId, dt);
                list.AddRange(childList);
            }
            return list;
        }

这中间的大体思路就是把符合条件的聚众全体弄到DataTable集合里面,然后再在其间举办搜寻,也就是递归获取里面的内容。

下面是常规的做法,可以观察代码量还是太多了,如若应用LINQ,就不需要如此了,而且也不能够这样处理。

利用实体框架后,紧要就是使用LINQ进行局部汇集的操作,那个LINQ的操作即便有点难度,不过学习精晓了,处理起来也是相比较便宜的。

在数量访问层,处理地点一样的效果,LINQ操作代码如下所示。

        /// <summary>
        /// 根据指定机构节点ID,获取其下面所有机构列表
        /// </summary>
        /// <param name="parentId">指定机构节点ID</param>
        /// <returns></returns>
        public IList<Ou> GetAllOUsByParent(int parentId)
        {
            //递归获取指定PID及下面所有所有的OU
            var query = this.GetQueryable().Where(s => s.PID == parentId).Where(s => !s.Deleted.HasValue || s.Deleted == 0).OrderBy(s => s.PID).OrderBy(s => s.Name);
            return query.ToList().Concat(query.ToList().SelectMany(t => GetAllOUsByParent(t.ID))).ToList();
        }

大多,可以见到就是两行代码了,是不是很神奇,它们实现的法力完全一致。

唯独,也不是所有的LINQ递归函数都能够做的万分简化,有些递归函数,我们如故需要采纳正规的笔触举行拍卖。

        /// <summary>
        /// 获取树形结构的机构列表
        /// </summary>
        public IList<OuNodeInfo> GetTree()
        {
            IList<OuNodeInfo> returnList = new List<OuNodeInfo>();
            IList<Ou> list = this.GetQueryable().Where(p => p.PID == -1).OrderBy(s => s.PID).OrderBy(s => s.Name).ToList();

            if (list != null)
            {
                foreach (Ou info in list.Where(s => s.PID == -1))
                {
                    OuNodeInfo nodeInfo = GetNode(info);
                    returnList.Add(nodeInfo);
                }
            }
            return returnList;
        }

可是相对来说,LINQ已经给我们带来的不胜大的方便了。

 

3、日期字段类型转换的错误处理

大家在做一些表的时候,一般意况下都会有日期类型存在,如我辈的南阳,创制、编辑日期等,一般大家数据库可能用的是datetime类型,即使这么些日子的花色内容在底下这一个区间的话:

"0001-01-01 到 9999-12-31"(公元元年 1 月 1 日到公元 9999 年 12 月 31 日)

我们恐怕就会获取下边的荒谬:

从 datetime2 数据类型到 datetime 数据类型的转换产生一个超出范围的值

相似之所以会报错数据类型转换暴发一个超出范围的值,都是因为数量的深浅和范围超出要转换的目的的缘由。大家先看datetime2和datetime那六个数据类型的实际区别在哪个地方。

官方MSDN对于datetime2的说明:定义结合了
24 时辰制时间的日子。 可将 datetime2 视作现有 datetime
类型的扩充,其数量范围更大,默认的小数精度更高,并具有可选的用户定义的精度。

此处值的小心的是datetime2的日子范围是”0001-01-01 到
9999-12-31″(公元元年 1 月 1 日到公元 9999 年 12 月 31
日)。而datetime的日期范围是:”1753 年 1 月 1 日到 9999 年 12 月 31
日“。这里的日子范围就是造成“从 datetime2 数据类型到 datetime
数据类型的转移发生一个超出范围的值”这一个荒唐的原由!!!

在c#中,如果实体类的性能没有赋值,一般都会取默认值,比如int类型的默认值为0,string类型默认值为null,
那Date提姆(Tim)e的默认值呢?由于Date提姆(Tim)e的默认值为”0001-01-01″,所以entity
framework在开展数据库操作的时候,在扩散数据的时会自动将本来是datetime类型的数额字段转换为datetime2类型(因为0001-01-01这多少个日子超出了数据库中datetime的微小日期范围),然后在进展数据库操作。问题来了,固然EF已经把要保存的数量自动转为了datetime2类型,可是数据库中表的字段依旧datetime类型!所以将datetime2类型的数目增长到数据库中datetime类型的字段里去,就会报错并提示转换超出范围。

化解措施如下所示:

这一个问题的缓解方法:

  1. C#代码中
    Date提姆e类型的字段在作为参数传入到数据库前记得赋值,并且的日期要高于1753年11月1日。
  2. C#代码中将本来是Date提姆(Tim)e类型的字段修改为Date提姆e?类型,由于可空类型的默认值都是为null,所以传入数据库就可以不用赋值,数据库中的datetime类型也是支撑null值的。
  3. 修改数据库中表的字段类型,将datetime类型修改为datetime2类型

例如,我在实业框架之中,对用户表的日子类型字段举行先河化,这样就能担保我存储数据的时候,默认值是不会有问题的。

    /// <summary>
    /// 系统用户信息,数据实体对象
    /// </summary>
    public class User
    { 
        /// <summary>
        /// 默认构造函数(需要初始化属性的在此处理)
        /// </summary>
        public User()
        {
            this.ID= 0;

            //从 datetime2 数据类型到 datetime 数据类型的转换产生一个超出范围的值
            //避免这个问题,可以初始化日期字段
            DateTime defaultDate = Convert.ToDateTime("1900-1-1");
            this.Birthday = defaultDate;
            this.LastLoginTime = defaultDate;
            this.LastPasswordTime = defaultDate;
            this.CurrentLoginTime = defaultDate;

            this.EditTime = DateTime.Now;
            this.CreateTime = DateTime.Now;
         }

偶然,即使这么设置了,不过在界面可能给这一个日期字段设置了不客观的值,也可能暴发问题。那么大家对此这种情况,判断一下,假诺低于某个值,我们给它一个默认值。

图片 1

 

4、实体框架的界面处理

在界面调整这块,我们如故尽量保持着的Enterprise
Library的Winform界面样式,也就是混合型或者普通Winform的界面效果。但是这里我们是以混合式框架举办整合测试,由此实体框架的各样方面的调用处理基本上保持一致。

然则是因为实体框架之中,实体类避免耦合的原故,大家引入了DTO的定义,并应用了AutoMapper组件举办了Entity与DTO的并行映射,具体介绍可以参考《Entity
Framework
实体框架的演进之旅–数据传输模型DTO和实体模型Entity的诀别与一块

》。

图片 2

就此我们在界面操作的都是DTO对象类型了,我们在概念的时候,为了防止更多的更动,依然利用***Info这样的类名称作为DTO对象的名称,***代表表名对象。

在混合式框架的界面表现层,它们的数量对象的拍卖基本上保持和原先的代码差不多。

        /// <summary>
        /// 新增状态下的数据保存
        /// </summary>
        /// <returns></returns>
        public override bool SaveAddNew()
        {
            UserInfo info = tempInfo;//必须使用存在的局部变量,因为部分信息可能被附件使用
            SetInfo(info);
            info.Creator = Portal.gc.UserInfo.FullName;
            info.Creator_ID = Portal.gc.UserInfo.ID.ToString();
            info.CreateTime = DateTime.Now;

            try
            {
                #region 新增数据

                bool succeed = CallerFactory<IUserService>.Instance.Insert(info);
                if (succeed)
                {
                    //可添加其他关联操作

                    return true;
                }
                #endregion
            }
            catch (Exception ex)
            {
                LogTextHelper.Error(ex);
                MessageDxUtil.ShowError(ex.Message);
            }
            return false;
        }

但咱们需要在WCF服务层表明她们之间的映射关系,方便举办内部的更换处理。

图片 3

在实体框架界面层的询问中,我们也不在使用一些SQL的尺度做法了,采纳更加安全的遵照DTO的LINQ表达式举办打包,最终传递给后台的也就是一个LINQ对象(非传统方法的实业LINQ,这样在分布式处理中会出错)。

如查询条件的包装处理如下所示:

        /// <summary>
        /// 根据查询条件构造查询语句
        /// </summary> 
        private ExpressionNode GetConditionSql()
        {
            Expression<Func<UserInfo, bool>> expression = p => true;
            if (!string.IsNullOrEmpty(this.txtHandNo.Text))
            {
                expression = expression.And(x => x.HandNo.Equals(this.txtHandNo.Text));
            }
            if (!string.IsNullOrEmpty(this.txtName.Text))
            {
                expression = expression.And(x => x.Name.Contains(this.txtName.Text));
            }
.........................................

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

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

            //如非选定,只显示正常用户
            if (!this.chkIncludeDelete.Checked)
            {
                expression = expression.And(x => x.Deleted == 0);
            }
            return expression.ToExpressionNode();
        }

而分页查询的拍卖,依旧和原来的品格差不多,只但是这里的Where条件为ExpressionNode
对象了,如代码所示、

            ExpressionNode where = GetConditionSql();
            PagerInfo PagerInfo = this.winGridViewPager1.PagerInfo;
            IList<UserInfo> list = CallerFactory<IUserService>.Instance.FindWithPager(where, ref PagerInfo);
            this.winGridViewPager1.DataSource = new WHC.Pager.WinControl.SortableBindingList<UserInfo>(list);
            this.winGridViewPager1.PrintTitle = "系统用户信息报表";

末尾我们来看望整个实体框架的协会和界面的功能介绍。

界面效果如下所示:

图片 4

代码结构如下所示:

图片 5

架构设计的效用图如下所示:

图片 6

 

相关文章