Entity Framework 实体框架的形成之旅--基类接口的统一和异步操作的实现(3)
在本系列的第一篇随笔《Entity Framework 实体框架的形成之旅--基于泛型的仓储模式的实体框架(1)》中介绍了Entity Framework 实体框架的一些基础知识,以及构建了一个简单的基于泛型的仓储模式的框架;在随笔《Entity Framework 实体框架的形成之旅--利用Unity对象依赖注入优化实体框架(2)》则持续优化这个仓储模式的实体框架,主要介绍业务逻辑层的构建,以及利用Unity和反射进行动态的对象注册。本篇主要介绍基类接口的统一和异步操作的实现等方面,逐步把我框架接口命名的方式进行统一,并增加所有必要用到的增删改查、分页、lambda表达式条件处理,以及异步操作等特性,这样能够尽可能的符合基类这个特殊类的定义,实现功能接口的最大化重用和统一。
1、基类接口的统一命名和扩展
在我以前的基于Enterprise Library的框架里面,定义了一个超级的数据访问基类,是特定数据访问类基类的基类,AbstractBaseDAL的数据访问层基类定义了很多通用的接口,具有非常强大的操作功能,如下所示。

这里面的很多接口命名我都经过了一些推敲,或者我基于我或者我客户群体的使用习惯和理解考虑,也是想沿承这些命名规则,扩充我这个基于泛型的仓储模式的实体框架基类接口。

下面是各类不同接口的定义内容。
1)增加操作
/// <summary>
/// 插入指定对象到数据库中
/// </summary>
/// <param name="t">指定的对象</param>
/// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns>
bool Insert(T t);
2)删除操作
/// <summary>
/// 根据指定对象的ID,从数据库中删除指定对象
/// </summary>
/// <param name="id">对象的ID</param>
/// <returns>执行成功返回<c>true</c>,否则为<c>false</c>。</returns>
bool Delete(object id); /// <summary>
/// 从数据库中删除指定对象
/// </summary>
/// <param name="id">指定对象</param>
/// <returns>执行成功返回<c>true</c>,否则为<c>false</c>。</returns>
bool Delete(T t);
3)修改操作
/// <summary>
/// 更新对象属性到数据库中
/// </summary>
/// <param name="t">指定的对象</param>
/// <param name="key">主键的值</param>
/// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns>
bool Update(T t, object key);
4)主键查询以及条件查询操作
/// <summary>
/// 查询数据库,返回指定ID的对象
/// </summary>
/// <param name="id">ID主键的值</param>
/// <returns>存在则返回指定的对象,否则返回Null</returns>
T FindByID(object id); /// <summary>
/// 根据条件查询数据库,如果存在返回第一个对象
/// </summary>
/// <param name="match">条件表达式</param>
/// <returns>存在则返回指定的第一个对象,否则返回默认值</returns>
T FindSingle(Expression<Func<T, bool>> match);
5)集合查询(分返回IQueryable和ICollection<T>两种方式)
/// <summary>
/// 返回可查询的记录源
/// </summary>
/// <returns></returns>
IQueryable<T> GetQueryable(); /// <summary>
/// 根据条件表达式返回可查询的记录源
/// </summary>
/// <param name="match">查询条件</param>
/// <param name="orderByProperty">排序表达式</param>
/// <param name="isDescending">如果为true则为降序,否则为升序</param>
/// <returns></returns>
IQueryable<T> GetQueryable(Expression<Func<T, bool>> match, string sortPropertyName, bool isDescending = true); /// <summary>
/// 根据条件查询数据库,并返回对象集合
/// </summary>
/// <param name="match">条件表达式</param>
/// <returns></returns>
ICollection<T> Find(Expression<Func<T, bool>> match); /// <summary>
/// 根据条件查询数据库,并返回对象集合
/// </summary>
/// <param name="match">条件表达式</param>
/// <param name="orderByProperty">排序表达式</param>
/// <param name="isDescending">如果为true则为降序,否则为升序</param>
/// <returns></returns>
ICollection<T> Find<TKey>(Expression<Func<T, bool>> match, Expression<Func<T, TKey>> orderByProperty, bool isDescending = true);
6)分页查询操作
/// <summary>
/// 根据条件查询数据库,并返回对象集合(用于分页数据显示)
/// </summary>
/// <param name="match">条件表达式</param>
/// <param name="info">分页实体</param>
/// <param name="orderByProperty">排序表达式</param>
/// <param name="isDescending">如果为true则为降序,否则为升序</param>
/// <returns>指定对象的集合</returns>
ICollection<T> FindWithPager(Expression<Func<T, bool>> match, PagerInfo info); /// <summary>
/// 根据条件查询数据库,并返回对象集合(用于分页数据显示)
/// </summary>
/// <param name="match">条件表达式</param>
/// <param name="info">分页实体</param>
/// <param name="orderByProperty">排序表达式</param>
/// <param name="isDescending">如果为true则为降序,否则为升序</param>
/// <returns>指定对象的集合</returns>
ICollection<T> FindWithPager<TKey>(Expression<Func<T, bool>> match, PagerInfo info, Expression<Func<T, TKey>> orderByProperty, bool isDescending = true);
这样我们在BaseDAL里面,把这些接口全部实现了,那么所有继承这个基类对象的数据访问对象,就具有这些标准的接口了,也给我们开发实现了整体性的统一。
首先我们来看看这个基类BaseDAL的初始化定义代码。
/// <summary>
/// 数据访问层基类
/// </summary>
/// <typeparam name="T">实体对象类型</typeparam>
public abstract class BaseDAL<T> : IBaseDAL<T> where T : class
{
#region 变量及构造函数 /// <summary>
/// DbContext对象
/// </summary>
protected DbContext baseContext; /// <summary>
/// 指定类型的实体对象集合
/// </summary>
protected DbSet<T> objectSet; /// <summary>
/// 是否为降序
/// </summary>
public bool IsDescending { get; set; } /// <summary>
/// 排序属性
/// </summary>
public string SortPropertyName { get; set; } /// <summary>
/// 参数化构造函数
/// </summary>
/// <param name="context">DbContext对象</param>
public BaseDAL(DbContext context)
{
this.baseContext = context;
this.objectSet = this.baseContext.Set<T>(); this.IsDescending = true;
this.SortPropertyName = "ID";
} #endregion
有了这些DbContext对象以及DbSet<T>对象,具体的接口实现就很容易了,下面我抽几个代表性的函数来介绍实现。
1)增加对象
/// <summary>
/// 插入指定对象到数据库中
/// </summary>
/// <param name="t">指定的对象</param>
/// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns>
public virtual bool Insert(T t)
{
ArgumentValidation.CheckForNullReference(t, "传入的对象t为空"); objectSet.Add(t);
return baseContext.SaveChanges() > ;
}
2)删除对象
/// <summary>
/// 根据指定对象的ID,从数据库中删除指定对象
/// </summary>
/// <param name="id">对象的ID</param>
/// <returns>执行成功返回<c>true</c>,否则为<c>false</c>。</returns>
public virtual bool Delete(object id)
{
T obj = objectSet.Find(id);
objectSet.Remove(obj);
return baseContext.SaveChanges() > ;
}
3)修改对象
/// <summary>
/// 更新对象属性到数据库中
/// </summary>
/// <param name="t">指定的对象</param>
/// <param name="key">主键的值</param>
/// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns>
public virtual bool Update(T t, object key)
{
ArgumentValidation.CheckForNullReference(t, "传入的对象t为空"); bool result = false;
T existing = objectSet.Find(key);
if (existing != null)
{
baseContext.Entry(existing).CurrentValues.SetValues(t);
result = baseContext.SaveChanges() > ;
}
return result;
}
4)根据条件查询
/// <summary>
/// 根据条件查询数据库,如果存在返回第一个对象
/// </summary>
/// <param name="match">条件表达式</param>
/// <returns>存在则返回指定的第一个对象,否则返回默认值</returns>
public virtual T FindSingle(Expression<Func<T, bool>> match)
{
return objectSet.FirstOrDefault(match);
} /// <summary>
/// 根据条件表达式返回可查询的记录源
/// </summary>
/// <param name="match">查询条件</param>
/// <param name="orderByProperty">排序表达式</param>
/// <param name="isDescending">如果为true则为降序,否则为升序</param>
/// <returns></returns>
public virtual IQueryable<T> GetQueryable(Expression<Func<T, bool>> match, string sortPropertyName, bool isDescending = true)
{
IQueryable<T> query = this.objectSet;
if (match != null)
{
query = query.Where(match);
}
return query.OrderBy(sortPropertyName, isDescending);
}
5)分页查询
/// <summary>
/// 根据条件查询数据库,并返回对象集合(用于分页数据显示)
/// </summary>
/// <param name="match">条件表达式</param>
/// <param name="info">分页实体</param>
/// <param name="orderByProperty">排序表达式</param>
/// <param name="isDescending">如果为true则为降序,否则为升序</param>
/// <returns>指定对象的集合</returns>
public virtual ICollection<T> FindWithPager(Expression<Func<T, bool>> match, PagerInfo info)
{
int pageindex = (info.CurrenetPageIndex < ) ? : info.CurrenetPageIndex;
int pageSize = (info.PageSize <= ) ? : info.PageSize; int excludedRows = (pageindex - ) * pageSize; IQueryable<T> query = GetQueryable().Where(match);
info.RecordCount = query.Count(); return query.Skip(excludedRows).Take(pageSize).ToList();
}
更多的代码就不一一贴出,反正我们全部实现自己所需的各种操作就可以了,这里要提的是,我们尽可能利用Lambda表达式进行条件处理,包括查询、删除等条件处理。
对上面的这些常规接口,我们调用代码处理的例子如下所示。
private void btnProvince_Click(object sender, EventArgs e)
{
DateTime dt = DateTime.Now; var list = BLLFactory<ProvinceBLL>.Instance.GetAll(s=>s.ProvinceName);
this.dataGridView1.DataSource = list; Console.WriteLine("花费时间:{0}", DateTime.Now.Subtract(dt).TotalMilliseconds);
} private void btnCity_Click(object sender, EventArgs e)
{
DateTime dt = DateTime.Now; CityBLL bll = new CityBLL();
var result = bll.GetAll(); this.dataGridView1.DataSource = result; Console.WriteLine("花费时间:{0}", DateTime.Now.Subtract(dt).TotalMilliseconds);
}
如果需要考虑分页,以上接口已经定义了分页处理的接口和实现了,我们在业务对象里面直接调用接口就可以了,具体代码如下所示。
CityBLL bll = new CityBLL();
PagerInfo info = new PagerInfo();
info.PageSize = ;
info.CurrenetPageIndex = ; ICollection<City> list;
if (i++ % == )
{
sortType = "自定义排序";
//使用自定义排序
list = bll.FindWithPager(s => s.CityName.Contains("南"), info, o => o.ID, true);
}
else
{
sortType = "默认字段排序";
//使用默认字段排序
list = bll.FindWithPager(s => s.CityName.Contains("南"), info);
} this.dataGridView1.DataSource = list;
2、异步操作的定义和调用
在EF里面实现异步(并行)非常容易,在.NET 4.5里由于async/await关键字的出现,使得实现异步变得更加容易。
使用await关键字后,.NET会自动把返回结果包装在一个Task类型的对象中。使用await表达式时,控制会返回到调用此方法的线程中;在await等待的方法执行完毕后,控制会自动返回到下面的语句中。发生异常时,异常会在await表达式中抛出。
我们基本上所有的增删改查、分页等接口,都可以使用异步操作来定义这些新接口,代码如下所示。
1)增加对象异步实现
异步定义的接口如下所示
/// <summary>
/// 插入指定对象到数据库中(异步)
/// </summary>
/// <param name="t">指定的对象</param>
/// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns>
Task<bool> InsertAsync(T t);
接口的实现如下所示
/// <summary>
/// 插入指定对象到数据库中(异步)
/// </summary>
/// <param name="t">指定的对象</param>
/// <returns>执行成功返回<c>true</c>,否则为<c>false</c></returns>
public virtual async Task<bool> InsertAsync(T t)
{
ArgumentValidation.CheckForNullReference(t, "传入的对象t为空"); objectSet.Add(t);
return await baseContext.SaveChangesAsync() > ;
}
和普通的接口定义不一样的地方,我们看到异步的接口都是以Async结尾,并且返回值使用Task<T>进行包装,另外实现里面,增加了async的定义,方法体里面增加 await 的关键字,这些就构成了异步操作的接口定义和接口实现了。
2)条件删除异步实现
我们再来看一个复杂一点的条件删除操作,代码如下所示。
定义接口
/// <summary>
/// 根据指定条件,从数据库中删除指定对象(异步)
/// </summary>
/// <param name="match">条件表达式</param>
/// <returns>执行成功返回<c>true</c>,否则为<c>false</c>。</returns>
Task<bool> DeleteByConditionAsync(Expression<Func<T, bool>> match);
接口实现
/// <summary>
/// 根据指定条件,从数据库中删除指定对象(异步)
/// </summary>
/// <param name="match">条件表达式</param>
/// <returns>执行成功返回<c>true</c>,否则为<c>false</c>。</returns>
public virtual async Task<bool> DeleteByConditionAsync(Expression<Func<T, bool>> match)
{
objectSet.Where<T>(match).ToList<T>().ForEach(d => baseContext.Entry<T>(d).State = EntityState.Deleted);
return await baseContext.SaveChangesAsync() > ;
}
我们定义的这些异步接口,基本上都是类似的操作,但是我们应该如何调用异步的处理呢?
好像有两个调用代码方式。
1)使用async和await关键字处理
private async void btnCity_Click(object sender, EventArgs e)
{
DateTime dt = DateTime.Now; CityBLL bll = new CityBLL();
var result = await bll.GetAllAsync(); this.dataGridView1.DataSource = result; Console.WriteLine("花费时间:{0}", DateTime.Now.Subtract(dt).TotalMilliseconds);
}
2)使用 await Task.Run的处理方式
private async void btnCity_Click(object sender, EventArgs e)
{
DateTime dt = DateTime.Now; CityBLL bll = new CityBLL();
var result = await Task.Run(() =>
{
var list = bll.GetAllAsync();
return list;
}); this.dataGridView1.DataSource = result; Console.WriteLine("花费时间:{0}", DateTime.Now.Subtract(dt).TotalMilliseconds);
}
两种方式都能正常运行,并得到所要的效果。
本篇主要介绍了基类接口的统一封装、并增加所有必要的增删改查、分页查询、Lambda条件等处理方式,还有就是增加了相关的异步操作接口和实现,随着我们对通用功能的进一步要求,可以为基类增加更多的接口函数。
这个系列文章索引如下:
Entity Framework 实体框架的形成之旅--基于泛型的仓储模式的实体框架(1)
Entity Framework 实体框架的形成之旅--利用Unity对象依赖注入优化实体框架(2)
Entity Framework 实体框架的形成之旅--基类接口的统一和异步操作的实现(3)
Entity Framework 实体框架的形成之旅--基类接口的统一和异步操作的实现(3)的更多相关文章
- Entity Framework 实体框架的形成之旅--为基础类库接口增加单元测试,对基类接口进行正确性校验(10)
本篇介绍Entity Framework 实体框架的文章已经到了第十篇了,对实体框架的各个分层以及基类的封装管理,已经臻于完善,为了方便对基类接口的正确性校验,以及方便对以后完善或扩展接口进行回归测试 ...
- Entity Framework 实体框架的形成之旅--实体数据模型 (EDM)的处理(4)
在前面几篇关于Entity Framework 实体框架的介绍里面,已经逐步对整个框架进行了一步步的演化,以期达到统一.高效.可重用性等目的,本文继续探讨基于泛型的仓储模式实体框架方面的改进优化,使我 ...
- Entity Framework 实体框架的形成之旅--几种数据库操作的代码介绍(9)
本篇主要对常规数据操作的处理和实体框架的处理代码进行对比,以便更容易学习理解实体框架里面,对各种数据库处理技巧,本篇介绍几种数据库操作的代码,包括写入中间表操作.联合中间表获取对象集合.递归操作.设置 ...
- Entity Framework 实体框架的形成之旅--界面操作的几个典型的处理(8)
在上篇随笔<Entity Framework 实体框架的形成之旅--数据传输模型DTO和实体模型Entity的分离与联合>里面,介绍了在Entity Framework 实体框架里面引入了 ...
- Entity Framework 实体框架的形成之旅--数据传输模型DTO和实体模型Entity的分离与联合
在使用Entity Framework 实体框架的时候,我们大多数时候操作的都是实体模型Entity,这个和数据库操作上下文结合,可以利用LINQ等各种方便手段,实现起来非常方便,一切看起来很美好.但 ...
- Entity Framework 实体框架的形成之旅--Code First的框架设计(5)
在前面几篇介绍了Entity Framework 实体框架的形成过程,整体框架主要是基于Database First的方式构建,也就是利用EDMX文件的映射关系,构建表与表之间的关系,这种模式弹性好, ...
- Entity Framework 实体框架的形成之旅--利用Unity对象依赖注入优化实体框架(2)
在本系列的第一篇随笔<Entity Framework 实体框架的形成之旅--基于泛型的仓储模式的实体框架(1)>中介绍了Entity Framework 实体框架的一些基础知识,以及构建 ...
- Entity Framework 实体框架的形成之旅--基于泛型的仓储模式的实体框架(1)
很久没有写博客了,一些读者也经常问问一些问题,不过最近我确实也很忙,除了处理日常工作外,平常主要的时间也花在了继续研究微软的实体框架(EntityFramework)方面了.这个实体框架加入了很多特性 ...
- Entity Framework 实体框架的形成之旅--实体框架的开发的几个经验总结
在前阵子,我对实体框架进行了一定的研究,然后把整个学习的过程开了一个系列,以逐步深入的方式解读实体框架的相关技术,期间每每碰到一些新的问题需要潜入研究.本文继续前面的主题介绍,着重从整体性的来总结一下 ...
随机推荐
- 团队项目——站立会议 DAY11
团队项目--站立会议 DAY11 团队成员介绍(5人):张靖颜.何玥.钟灵毓秀.赵莹.王梓萱 今日(2016/5/20),站立会议已进行了两周时间,将这一周所遇到的问题和心 ...
- SQL 行列倒置
SQL的的行列倒置已经不是新知识了,但在博主的技术咨询期间,仍发现其实有很多人并不了解这块,所以在此专门写一篇博客记录.本文将以Mysql为例,并以数据采集指标信息获取为例子.在下面的例子,你可以在s ...
- Linux shell之打印输出
介绍 经常需要和shell命令打交道,但是一直没有系统的学习,接下来会花1到2个月的时间系统的学习一下shell命令,接下来就开启shell命令的奇妙旅行吧.本章主要介绍shell的打印输出. 知识要 ...
- [ZigBee] 11、ZigBee之睡眠定时器二
1.前言 上一节讲了Zigbee的睡眠定时器利用外部按键使系统从休眠态唤醒到工作态,其核心在于: 61 void SysPowerMode(uchar mode) 62 { 63 if(mode &g ...
- iOS 向模拟器里添加照片
iOS 向模拟器里添加照片 模拟器里Photos最开始时是没有照片的,有时我们做Demo时需要Photos里面的照片做测试,这时就需要把Mac上的照片导入到模拟器里.步骤如下: 1,打开模拟器 2,选 ...
- Java 9终于要包含Jigsaw项目了
当Jigsaw在Java 9中最终发布时,这个项目的历史已经超过八年了. 转载于:http://www.itxuexiwang.com/a/liunxjishu/2016/0228/180.html? ...
- draggable属性设置元素是否可拖动。
设置标签属性draggable="true"将一个标签内的元素拖动到另外一个标签进行显示: <!DOCTYPE HTML> <html> <head& ...
- js之滚动置顶效果
0.js获取高度 ? 1 2 3 4 5 6 document.all // 只有ie认识 document.body.clientHeight // 文档的高,屏幕 ...
- CSS伪类与CSS伪元素的区别及由来
关于两者的区别,其实是很古老的问题.但是时至今日,由于各种网络误传以及一些不负责任的书籍误笔,仍然有相当多的人将伪类与伪元素混为一谈,甚至不乏很多CSS老手.早些年刚入行的时候,我自己也被深深误导,因 ...
- javascript类型系统——包装对象
× 目录 [1]定义 [2]生存期 [3]显式创建[4]转型函数[5]比较运算 前面的话 javascript对象是一种复合值,它是属性或已命名值的集合.通过'.'符号来引用属性值.当属性值是一个函数 ...