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

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

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() > 0;
            }
            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 < 1) ? 1 : info.CurrenetPageIndex;
            int pageSize = (info.PageSize <= 0) ? 20 : info.PageSize;

            int excludedRows = (pageindex - 1) * 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 = 30;
                info.CurrenetPageIndex =1 ;

                ICollection<City> list;
                if (i++ % 2 == 0)
                {
                    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() > 0;
        }

和普通的接口定义不一样的地方,我们看到异步的接口都是以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() > 0;
        }

我们定义的这些异步接口,基本上都是类似的操作,但是我们应该如何调用异步的处理呢?

好像有两个调用代码方式。

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条件等处理方式,还有就是增加了相关的异步操作接口和实现,随着我们对通用功能的进一步要求,可以为基类增加更多的接口函数。

时间: 2024-09-30 09:06:37

Entity Framework 实体框架的形成之旅--基类接口的统一和异步操作的实现(3)的相关文章

Entity Framework 实体框架的形成之旅--为基础类库接口增加单元测试,对基类接口进行正确性校验(10)

本篇介绍Entity Framework 实体框架的文章已经到了第十篇了,对实体框架的各个分层以及基类的封装管理,已经臻于完善,为了方便对基类接口的正确性校验,以及方便对以后完善或扩展接口进行回归测试,那么建立单元测试就有很大的必要,本篇主要介绍如何利用VS创建内置的单元测试项目进行实体框架的基类接口测试. 在采用单元测试这个事情上,很多人可能想到了NUnit单元测试工具和NMock工具进行处理,其实微软VS里面也已经为我们提供了类似的单元测试工具了,可以不需要使用这个第三方的单元测试工具,经试

Entity Framework 实体框架的形成之旅--Code First的框架设计(5)

在前面几篇介绍了Entity Framework 实体框架的形成过程,整体框架主要是基于Database First的方式构建,也就是利用EDMX文件的映射关系,构建表与表之间的关系,这种模式弹性好,也可以利用图形化的设计器来设计表之间的关系,是开发项目较多采用的模式,不过问题还是这个XML太过复杂,因此有时候也想利用Code First模式构建整个框架.本文主要介绍利用Code First 来构建整个框架的过程以及碰到的问题探讨. 1.基于SqlServer的Code First模式 为了快速

Entity Framework 实体框架的形成之旅--数据传输模型DTO和实体模型Entity的分离与联合

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

Entity Framework 实体框架的形成之旅--几种数据库操作的代码介绍(9)

本篇主要对常规数据操作的处理和实体框架的处理代码进行对比,以便更容易学习理解实体框架里面,对各种数据库处理技巧,本篇介绍几种数据库操作的代码,包括写入中间表操作.联合中间表获取对象集合.递归操作.设置单一字段的修改等几种方式. 1.写入中间表操作 一般情况下,我们可以通过执行数据库脚本方式写入. /// <summary> /// 增加用户IP信息 /// </summary> /// <param name="userID"></param&

Entity Framework 实体框架的形成之旅--实体框架的开发的几个经验总结

在前阵子,我对实体框架进行了一定的研究,然后把整个学习的过程开了一个系列,以逐步深入的方式解读实体框架的相关技术,期间每每碰到一些新的问题需要潜入研究.本文继续前面的主题介绍,着重从整体性的来总结一下实体框架的一些方面,希望针对这些实际问题,和大家进行学习交流. 我的整个实体框架的学习和研究,是以我的Winform框架顺利升级到这个实体框架基础上为一个阶段终结,这个阶段事情很多,从开始客运联网售票的WebAPI平台的开发,到微软实体框架的深入研究,以及<基于Metronic的Bootstrap开

Entity Framework 实体框架的形成之旅--利用Unity对象依赖注入优化实体框架(2)

在本系列的第一篇随笔<Entity Framework 实体框架的形成之旅--基于泛型的仓储模式的实体框架(1)>中介绍了Entity Framework 实体框架的一些基础知识,以及构建了一个简单的基于泛型的仓储模式的框架,例子也呈现了一个实体框架应用的雏形,本篇继续介绍这个主题,继续深化介绍Entity Framework 实体框架的知识,以及持续优化这个仓储模式的实体框架,主要介绍业务逻辑层的构建,以及利用Unity和反射进行动态的对象注册. 1.EDMX文件位置的调整 我们从上篇例子,

Entity Framework实体框架使用TrackerEnabledDbContext进行操作日志跟踪

在EF实体框架中进行日志跟踪,一般都是自己写个Log实体类,在数据保存时进行属性原始值验证来进行日志跟踪.当然还可以使用一些第三扩展库例如:entity framework extended进行日志记录,本文介绍如何使用TrackerEnabledDbContext进行操作日志跟踪. 1,首先创建项目工程,TrackerEnabledDbContext只支持4.5及以上框架,详情见:https://www.nuget.org/packages/TrackerEnabledDbContext 2,

基于MVC4+EasyUI的Web开发框架形成之旅--基类控制器CRUD的操作

在上一篇随笔中,我对Web开发框架的总体界面进行了介绍,其中并提到了我的<Web开发框架>的控制器的设计关系,Web开发框架沿用了我的<Winform开发框架>的很多架构设计思路和特点,对Controller进行了封装.使得控制器能够获得很好的继承关系,并能以更少的代码,更高效的开发效率,实现Web项目的开发工作,整个控制器的设计思路如下所示. 从上图的设计里面可以看到,我把主要能通过抽象封装的CRUD方法都放到了BusinessController<B, T>类里面,

Python接口测试实战4(下) - 框架完善:用例基类,用例标签,重新运行上次失败用例

如有任何学习问题,可以添加作者微信:lockingfree 课程目录 Python接口测试实战1(上)- 接口测试理论 Python接口测试实战1(下)- 接口测试工具的使用 Python接口测试实战2 - 使用Python发送请求 Python接口测试实战3(上)- Python操作数据库 Python接口测试实战3(下)- unittest测试框架 Python接口测试实战4(上) - 接口测试框架实战 Python接口测试实战4(下) - 框架完善:用例基类,用例标签,重新运行上次失败用例