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 = ; 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 = ; 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 == ).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 == -).OrderBy(s => s.PID).OrderBy(s => s.Name).ToList(); if (list != null)
{
foreach (Ou info in list.Where(s => s.PID == -))
{
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, 那DateTime的默认值呢?由于DateTime的默认值为"0001-01-01",所以entity framework在进行数据库操作的时候,在传入数据的时会自动将原本是datetime类型的数据字段转换为datetime2类型(因为0001-01-01这个时间超出了数据库中datetime的最小日期范围),然后在进行数据库操作。问题来了,虽然EF已经把要保存的数据自动转为了datetime2类型,但是数据库中表的字段还是datetime类型!所以将datetime2类型的数据添加到数据库中datetime类型的字段里去,就会报错并提示转换超出范围。
解决方法如下所示:
这个问题的解决方法:
- C#代码中 DateTime类型的字段在作为参数传入到数据库前记得赋值,并且的日期要大于1753年1月1日。
- C#代码中 将原本是DateTime类型的字段修改为DateTime?类型,由于可空类型的默认值都是为null,所以传入数据库就可以不用赋值,数据库中的datetime类型也是支持null值的。
- 修改数据库中表的字段类型,将datetime类型修改为datetime2类型
例如,我在实体框架里面,对用户表的日期类型字段进行初始化,这样就能保证我存储数据的时候,默认值是不会有问题的。
/// <summary>
/// 系统用户信息,数据实体对象
/// </summary>
public class User
{
/// <summary>
/// 默认构造函数(需要初始化属性的在此处理)
/// </summary>
public User()
{
this.ID= ; //从 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;
}
有时候,虽然这样设置了,但是在界面可能给这个日期字段设置了不合理的值,也可能产生问题。那么我们对于这种情况,判断一下,如果小于某个值,我们给它一个默认值。
4、实体框架的界面处理
在界面调整这块,我们还是尽可能保持着的Enterprise Library的Winform界面样式,也就是混合型或者普通Winform的界面效果。不过这里我们是以混合式框架进行整合测试,因此实体框架的各个方面的调用处理基本上保持一致。
不过由于实体框架里面,实体类避免耦合的原因,我们引入了DTO的概念,并使用了AutoMapper组件进行了Entity与DTO的相互映射,具体介绍可以参考《Entity Framework 实体框架的形成之旅--数据传输模型DTO和实体模型Entity的分离与联合
》。
因此我们在界面操作的都是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服务层说明他们之间的映射关系,方便进行内部的转换处理。
在实体框架界面层的查询中,我们也不在使用部分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 == );
}
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 = "系统用户信息报表";
最后我们来看看整个实体框架的结构和界面的效果介绍。
界面效果如下所示:
代码结构如下所示:
架构设计的效果图如下所示:
Entity Framework 实体框架的形成之旅--实体框架的开发的几个经验总结的更多相关文章
- Entity Framework 实体框架的形成之旅--实体数据模型 (EDM)的处理(4)
在前面几篇关于Entity Framework 实体框架的介绍里面,已经逐步对整个框架进行了一步步的演化,以期达到统一.高效.可重用性等目的,本文继续探讨基于泛型的仓储模式实体框架方面的改进优化,使我 ...
- Entity Framework 6 Code First系列1: 实体类1:1配置
从4.1版本开始,EF开始支持Code First模式,值得注意的是Code First不是和DataBase First或Model First平级的概念,而是和EDM平级的概念.使用Code Fi ...
- 对Entity Framework Core的一次误会:实体状态不跟踪
在 Entity Framework 中,当通过 EF 使用 LINQ 查询获取到一个实体(实际得到的是 EF 动态生成的实体类的代理类的实例)时,这个实体的状态默认是被跟踪的.所以,当你修改实体的某 ...
- Entity Framework 6.0 对枚举的支持/实体添加后会有主键反回
实验 直接上代码,看结果 实体类 [Flags] public enum FlagsEnum { Day = , Night = } public class EntityWithEnum { pub ...
- Entity Framework入门教程(15)---DbContext追踪实体状态改变
这一节介绍DbContext追踪实体的变化.EF支持DbContext在其生命周期中自动追踪加载的实体.我们可以通过DbChangeTracker类获取DbContext追踪的所有实体的变化. 注意每 ...
- Entity Framework 学习总结之一:ADO.NET 实体框架概述
http://www.cnblogs.com/xlovey/archive/2011/01/03/1924800.html ADO.NET 实体框架概述 新版本中的 ADO.NET 以新实体框架为特色 ...
- Entity Framework 实体框架的形成之旅--几种数据库操作的代码介绍(9)
本篇主要对常规数据操作的处理和实体框架的处理代码进行对比,以便更容易学习理解实体框架里面,对各种数据库处理技巧,本篇介绍几种数据库操作的代码,包括写入中间表操作.联合中间表获取对象集合.递归操作.设置 ...
- Entity Framework 实体框架的形成之旅--界面操作的几个典型的处理(8)
在上篇随笔<Entity Framework 实体框架的形成之旅--数据传输模型DTO和实体模型Entity的分离与联合>里面,介绍了在Entity Framework 实体框架里面引入了 ...
- Entity Framework 实体框架的形成之旅--数据传输模型DTO和实体模型Entity的分离与联合
在使用Entity Framework 实体框架的时候,我们大多数时候操作的都是实体模型Entity,这个和数据库操作上下文结合,可以利用LINQ等各种方便手段,实现起来非常方便,一切看起来很美好.但 ...
随机推荐
- 作业三:代码规范、代码复审、PSP
一.代码规范 我认为我们编写的代码都需要进行规范的操作,因为如果为了图省事情或者为了减少时间去完成这个编程.在最后检验的时候就会出现一些警告,导致你这次编程的代码出现问题,当出现问题的时候你在回头去检 ...
- 博客的QQ群群规
本人博客园博客夏天的森林相关的QQ群一共有3个,它们分别是Web前端及Web开发技术群(群号:262413025).JS及Web前端技术群(群号:35079861)和Web服务端技术群(群号:3414 ...
- Model模型和Module模块的区别
资料是从网上找的,具体是谁最先写的,不清楚了. Model通常是指模型.这个模型也许是你需求分析出来的, 也许是你算法做出来的. 不过最大可能是MVC的网站,或者是GUI开发模式中的M里的那个模型. ...
- Bootstrap~表单Form
回到目录 在进行自己的后台改版时,大体布局都使用了bootstrap,剩下的表单部分没理由不去使用它,对于表单的美化和布局,bootstrap做的也是很不错的,有大气的边框,多功能的按钮及宏观的表单布 ...
- Atitit 图片 验证码生成attilax总结
Atitit 图片 验证码生成attilax总结 1.1. 图片验证码总结1 1.2. 镂空文字 打散 干扰线 文字扭曲 粘连2 1.1. 图片验证码总结 因此,CAPTCHA在图片验证码这一应用点 ...
- Atitit DbServiceV4qb9 数据库查询类库v4 新特性
Atitit DbServiceV4qb9 数据库查询类库v4 新特性 V4新特性 安全特性,屏蔽了executeUpdate,使用v2版 Sql异常转换,特别转换了DuplicateEnt ...
- 对于System.Net.Http的学习(三)——使用 HttpClient 检索与获取过程数据
对于System.Net.Http的学习(一)——System.Net.Http 简介 对于System.Net.Http的学习(二)——使用 HttpClient 进行连接 如何使用 HttpCli ...
- 在忘记root密码的情况下如何修改linux系统的root密码
1.系统启动时长按shift键后可以看到如下界面: 2.找到 recovery mode 那一行, 按下[e]键进入命令编辑状态,到 linux /boot/vmlinuz-....... r ...
- require.js笔记
笔记参考来源:阮一峰 http://www.ruanyifeng.com/blog/2012/10/javascript_module.html 1. 浏览器端的模块只能采用“异步加载”方式 = ...
- Sql Server系列:索引维护
1. DBCC SHOWCONTIG 显示指定表的数据和索引的碎片信息.当对表进行大量的修改或添加数据后,执行此语句可以查看有无碎片,显示指定的表或试图的数据和索引的碎片信息. 其语法格式: DBCC ...