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

    这个系列文章索引如下:

    Entity Framework 实体框架的形成之旅--基于泛型的仓储模式的实体框架(1)

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

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

  • 相关阅读:
    jdk .tar.gz 包安装 inAction
    Consistent Hashing原理与实现
    开放GitHub的理由
    dll signing issue
    Regular expression cheat sheet
    DOMElement之Offset
    扫码支付测试点
    SQL注入是什么?如何防止?
    什么是接口测试?为什么要做接口测试?如何开展接口测试?
    软件测试的常识
  • 原文地址:https://www.cnblogs.com/wuhuacong/p/4338564.html
Copyright © 2011-2022 走看看