使用EF构建企业级应用(一)
2012-04-07 13:29 谢中涞 阅读(5087) 评论(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对象
/// <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);那这种扩展又是如何实现的呢?在下一节中,我们将来探讨这个问题.
本着互相分享的精神,文章欢迎转载,但转载需要标明本文出处.除特殊声明外,本文章均首发于博客园.