代码改变世界

使用EF构建企业级应用(一)

2012-04-07 13:29  谢中涞  阅读(5076)  评论(18编辑  收藏  举报

本系列目录:

使用EF构建企业级应用(一):主要讲数据库访问基类IRepository及Repository 的实现

使用EF构建企业级应用(二):主要讲动态排序扩展的实现

使用EF构建企业级应用(三):主要讲灵活的构建查询条件表达式Expression<Func<TEntity,bool>>.

使用EF构建企业级应用(四):主要讲下在MVC环境中前端开发中如何邮箱的使用,及一个实例源码包

 数据持久化
最初接触EF是在2009年,那时因为EF还只支持DataBase-first模式,在项目中使用需要创建.edmx文件,因为觉得比较累赘,且生成的代码不容易控制,总觉得看着不爽,于是曾一度被抛弃,也总结了自己的一些数据持久化的东东,最近不经意间听说到了一个新词,code-first,觉得挺新潮,最开始还以为是微软出了个什么新技术,于是不停的Google,后得知原来所谓的code-first只是EF中的一部分,在潜心学习一段时间后,因为和Linq结合得非常好,感觉还挺喜欢这种模式的,于是,我们将这种模式应用在了我们新的项目上.在学习EF的时候,走过了很多弯路,也寻找了很多资料,现做一个小的总结,以帮助遇到类似困难的同学们.
因考虑到这个文章篇幅可能较长,故把此文章拆分成一个系列,本小结主要介绍数据持久化(即常说的CURD)相关类容

数据持久化的简单封装,根据以前的经验,以及EF的一些特殊性,我们很自然的画出来下面的Repository类图

    • 在上面的UML中,我们定义了两个接口,IRepository<TEntity>及IRepository<TEntity,TPkType>
    • 上面UML中的泛型类型分别为:

      • TEntity : 数据库操作的实体对象,

      • TPkType:TEntity中单一主键的数据类型

      • TContext:一个派生于DbContext对象

      下面我们来看一下IRepository 这两个接口的具体定义
 
     /// <summary>
    ///  仓储模型基本接口,提供基本的数据库增删改查接口
    /// </summary>
    /// <typeparam name="TEntity">实体类型</typeparam>
    public interface IRepository<TEntity> : IDisposable where TEntity : class
    {
        /// <summary>
        /// 获取所有数据
        /// </summary>
        /// <returns></returns>
        IQueryable<TEntity> Get();

        /// <summary>
        /// 根据指定的查询条件
        /// </summary>
        /// <param name="expression">查询条件</param>
        /// <returns></returns>
        IQueryable<TEntity> Get(Expression<Func<TEntity, bool>> expression);

        /// <summary>
        /// 根据指定条件查询分页数据
        /// </summary>
        /// <typeparam name="TOrderType">排序类型</typeparam>
        /// <param name="expression">查询条件</param>
        /// <param name="orderPropertyName">排序字段</param>
        /// <param name="isAscOrder">是否是升序查询</param>
        /// <param name="pgIndex"></param>
        /// <param name="pgSize"></param>
        /// <param name="total">总记录数</param>
        /// <returns></returns>
        IQueryable<TEntity> Get(Expression<Func<TEntity, bool>> expression,
            string orderPropertyName, bool isAscending,
            int pgIndex, int pgSize, out int total);

         /// <summary>
        /// 新增
         /// </summary>
        /// <param name="entity"></param>
        void Add(TEntity entity);
        /// <summary>
        /// 根据具体实体删除
         /// </summary>
        /// <param name="entity"></param>
        void Delete(TEntity entity);

        /// <summary>
        /// 修改
         /// </summary>
        /// <param name="entity"></param>
        void Update(TEntity entity);

        /// <summary>
        /// 根据主键获取
         /// </summary>
        /// <param name="id">主键Id值</param>
        /// <returns></returns>
        TEntity Get(params object[] ids);

        /// <summary>
        /// 根据主键删除
         /// </summary>
        /// <param name="entity">主键Id值</param>
        void Delete(params object[] ids);

        /// <summary>
        /// 保存数据修改
         /// </summary>
        void SaveDbChange();

    }

     /// <summary>
     ///  仓储模型基本接口,提供基本的数据库增删改查接口
     /// </summary>
    /// <typeparam name="TEntity">实体类型</typeparam>
    /// <typeparam name="TPkType">实体主键类型</typeparam>
    public interface IRepository<TEntity, TPkType> : IRepository<TEntity>
         where TEntity : class,IEntity<TPkType>
    {

        /// <summary>
        /// 根据主键获取
         /// </summary>
        /// <param name="id">主键Id值</param>
        /// <returns></returns>
        TEntity Get(TPkType id);

        /// <summary>
        /// 根据主键删除
         /// </summary>
        /// <param name="id">主键Id值</param>
        void Delete(TPkType id);
    }

  

 

在上面的定义中,细心的同学可能会发现我们定义了一个数据提交修改的方法申明"void SaveDbChange(); “,可能也许你会觉得累赘,为何不把这个方法放在每次的CURD后自动执行呢,最初我们也有同样的想法,但当我们在实际业务中应用发现,通常每个业务都会在多个数据表上进行CURD操作,为了做到尽量减少一个业务逻辑提交数据库的次数,我们采用了将提交数据库这一步拆分出来,放在了单独的方法中进行,这种一次性提交可以做到类似于事务的功能,且操作更加简便.

下面我们来看看这个EFRepository的实现

 
     /// <summary>
    /// EF实现 数据库增删改查
    /// </summary>
    /// <typeparam name="TEntity">查询实体</typeparam>
    /// <typeparam name="TContext">DbContext 类型</typeparam>
    public class EFRepository<TEntity, TContext> : IRepository<TEntity>
        where TEntity : class
        where TContext : DbContext, new()
    {
        private TContext dbContext;

        /// <summary>
        /// 获取DbContext
        /// </summary>
        public TContext DbContext
        {
            get
            {
                if (dbContext == null)
                    dbContext = new TContext();
                return dbContext;
            }
        }

        /// <summary>
        /// 获取DbSet 
        /// </summary>
        protected DbSet<TEntity> DbSet
        {
            get
            {
                return DbContext.Set<TEntity>();
            }
        }
        /// <summary>
        /// 关联查询
        /// </summary>
        /// <typeparam name="TProperty">关联查询的属性类型</typeparam>
        /// <param name="path">关联查询属性</param>
        /// <returns></returns>
        public IQueryable<TEntity> Include<TProperty>(Expression<Func<TEntity, TProperty>> path)
        {
            return DbSet.Include(path);
        }

        /// <summary>
        /// 获取所有数据
        /// </summary>
        /// <returns></returns>
        public IQueryable<TEntity> Get()
        {
            return DbSet;
        }

        /// <summary>
        /// 根据指定的查询条件
        /// </summary>
        /// <param name="expression">查询条件</param>
        /// <returns></returns>
        public IQueryable<TEntity> Get(Expression<Func<TEntity, bool>> expression)
        {
            IQueryable<TEntity> query = DbSet;
            if (expression != null)
                query = DbSet.Where(expression);
            return query;
        }

        /// <summary>
        /// 根据指定条件查询分页数据
         /// </summary>
        /// <param name="expression">查询条件</param>
        /// <param name="orderPropertyName">排序属性</param>
        /// <param name="isAscending">是否是升序查询,false为降序</param>
        /// <param name="pgIndex">分页查询起始页</param>
        /// <param name="pgSize">每页显示记录数</param>
        /// <param name="total">总记录数</param>
        /// <returns></returns>
        public IQueryable<TEntity> Get(Expression<Func<TEntity, bool>> expression,
             string orderPropertyName, bool isAscending, int pgIndex, int pgSize, out int total)
        {
            total = 0;
            IQueryable<TEntity> query = Get(expression).OrderBy(orderPropertyName, isAscending);
            //IQueryable<TEntity> query = Get(expression).OrderBy(orderPropertyName);

            //分页查询
            if (pgSize > 0)
            {
                total = query.Count();      //记录总数
                query = query.Skip(pgIndex * pgSize).Take(pgSize);
            }
            return query;
        }
       
        /// <summary>
        /// 新增
         /// </summary>
        /// <param name="entity"></param>
        public void Add(TEntity entity)
        {
            DbSet.Add(entity);
        }
        /// <summary>
        /// 删除实体
        /// </summary>
        /// <param name="entity"></param>
        public void Delete(TEntity entity)
        {
            DbSet.Remove(entity);
        }

        /// <summary>
        /// 修改实体
         /// </summary>
        /// <param name="entity"></param>
        public virtual void Update(TEntity entity)
        {
            var entry = DbContext.Entry(entity);
            if (entry != null)
            {
                if (entity != null)
                {
                    if (entry.State == EntityState.Detached)
                    {
                        //以下方法在先查询后再修改会报错,提示dbContext已经加载了一个相同主键的对象
                        DbSet.Attach(entity);
                        entry.State = EntityState.Modified;
                    }
                }
            }
        }
        /// <summary>
        /// 修改数据
         /// </summary>
        /// <param name="oldEntity">原实体</param>
        /// <param name="newEntity">修改后的实体</param>
        public void Update(TEntity dbEntity, TEntity newEntity)
        {
            var entry = DbContext.Entry(dbEntity);
            if (entry != null)
            {
                //以下方法在先查询后再修改会报错,提示dbContext已经加载了一个相同主键的对象
                //DbSet.Attach(entity);
                // entry.State = EntityState.Modified;

                //改为如下方式实现 以下代码类似于 entity=new TEntity{p1=entityToUpdate.p1,...},
                EmitMapper.ObjectMapperManager.DefaultInstance.GetMapper<TEntity, TEntity>().Map(newEntity, dbEntity);
            }
        }

        /// <summary>
        /// 根据主键获取数据
         /// </summary>
        /// <param name="ids">主键集合</param>
        /// <returns></returns>
        public virtual TEntity Get(params object[] ids)
        {
            return DbSet.Find(ids);
        }

        /// <summary>
        /// 根据主键删除数据
         /// </summary>
        /// <param name="ids">主键集合</param>
        public virtual void Delete(params object[] ids)
        {
            var item = Get(ids);
            if (item != null)
            {
                DbContext.Entry(item).State = EntityState.Deleted;
            }
        }

        /// <summary>
        /// 提交数据修改
        /// </summary>
        public void SaveDbChange()
        {
            dbContext.SaveChanges();
        }

        /// <summary>
        /// 释放资源
         /// </summary>
        public void Dispose()
        {
            if (dbContext != null)
            {
                dbContext.Dispose();
                dbContext = null;
            }
        }



    }

    /// <summary>
    /// EF实现 数据库增删改查基本实现
     /// </summary>
    /// <typeparam name="TEntity">查询实体</typeparam>
    /// <typeparam name="TPkType">实体主键类型</typeparam>
    /// <typeparam name="TContext">DbContext 类型</typeparam>
    public class EFRepository<TEntity, TPkType, TContext> : EFRepository<TEntity, TContext>, IRepository<TEntity, TPkType>
        where TEntity : class, IEntity<TPkType>
        where TContext : DbContext, new()
    {
        /// <summary>
        /// 根据主键获取
        /// </summary>
        /// <param name="id">主键Id值</param>
        /// <returns></returns>
        public TEntity Get(TPkType id)
        {
            return DbSet.Find(id);
        }

        /// <summary>
        /// 根据主键删除
        /// </summary>
        /// <param name="id"></param>
        public void Delete(TPkType id)
        {
            var info = DbSet.Find(id);
            if (info != null)
            {
                DbContext.Entry(info).State = EntityState.Deleted;
            }
        }

        /// <summary>
        /// 修改,
         /// </summary>
        /// <param name="entity"></param>
        public override void Update(TEntity entity)
        {
            var entry = DbContext.Entry(entity);
            if (entry != null)
            {
                if (entry.State == EntityState.Detached)
                {
                    //以下方法在先查询后再修改会报错,提示dbContext已经加载了一个相同主键的对象
                    //DbSet.Attach(entity);
                    // entry.State = EntityState.Modified;

                    //改为如下方式实现 以下代码类似于 entity=new TEntity{p1=entityToUpdate.p1,...},以下语句在当实体中有虚拟方法时候会报错
                    var entityToUpdate = DbSet.Find(entity.Id);
                    EmitMapper.ObjectMapperManager.DefaultInstance.GetMapper<TEntity, TEntity>().Map(entity, entityToUpdate);
                }
            }
        }

细心的同学可能会发现我们在实现查询结果集的时候,返回的是IQueryable<TEntity>,而不是IList<TEntity> 或者IEnumerable<T>,这是因为,也许在实际业务逻辑中,我们并不需要返回整个集合,或者我们只需要这其中的一个最大值或者一个求和数据,为了不给宝贵的数据库查询带来额外的性能牺牲,我们便决定在此并不立即执行查询,真正的查询交给了前段业务逻辑使用者自己去ToList().

本小结主要讲到这里,在这一节中,我们实现了数据持久化Repository,细心的同学可能发现了我们在写查询的时候,定义的方法中支持动态排序,即类似于IQueryable<TEntity> query = Get(expression).OrderBy(orderPropertyName, isAscending);那这种扩展又是如何实现的呢?在下一节中,我们将来探讨这个问题.