当前位置:Gxlcms > 数据库问题 > MVC实用架构设计(三)——EF-Code First(1):Repository,UnitOfWork,DbContext

MVC实用架构设计(三)——EF-Code First(1):Repository,UnitOfWork,DbContext

时间:2021-07-01 10:21:17 帮助过:2人阅读

 1 namespace GMF.Component.Data
 2 {
 3     /// <summary>
 4     ///     定义仓储模型中的数据标准操作
 5     /// </summary>
 6     /// <typeparam name="TEntity">动态实体类型</typeparam>
 7     public interface IRepository<TEntity> where TEntity : Entity
 8     {
 9         #region 属性
10 
11         /// <summary>
12         ///     获取 当前实体的查询数据集
13         /// </summary>
14         IQueryable<TEntity> Entities { get; }
15 
16         #endregion
17 
18         #region 公共方法
19 
20         /// <summary>
21         ///     插入实体记录
22         /// </summary>
23         /// <param name="entity"> 实体对象 </param>
24         /// <param name="isSave"> 是否执行保存 </param>
25         /// <returns> 操作影响的行数 </returns>
26         int Insert(TEntity entity, bool isSave = true);
27 
28         /// <summary>
29         ///     批量插入实体记录集合
30         /// </summary>
31         /// <param name="entities"> 实体记录集合 </param>
32         /// <param name="isSave"> 是否执行保存 </param>
33         /// <returns> 操作影响的行数 </returns>
34         int Insert(IEnumerable<TEntity> entities, bool isSave = true);
35 
36         /// <summary>
37         ///     删除指定编号的记录
38         /// </summary>
39         /// <param name="id"> 实体记录编号 </param>
40         /// <param name="isSave"> 是否执行保存 </param>
41         /// <returns> 操作影响的行数 </returns>
42         int Delete(object id, bool isSave = true);
43 
44         /// <summary>
45         ///     删除实体记录
46         /// </summary>
47         /// <param name="entity"> 实体对象 </param>
48         /// <param name="isSave"> 是否执行保存 </param>
49         /// <returns> 操作影响的行数 </returns>
50         int Delete(TEntity entity, bool isSave = true);
51 
52         /// <summary>
53         ///     删除实体记录集合
54         /// </summary>
55         /// <param name="entities"> 实体记录集合 </param>
56         /// <param name="isSave"> 是否执行保存 </param>
57         /// <returns> 操作影响的行数 </returns>
58         int Delete(IEnumerable<TEntity> entities, bool isSave = true);
59 
60         /// <summary>
61         ///     删除所有符合特定表达式的数据
62         /// </summary>
63         /// <param name="predicate"> 查询条件谓语表达式 </param>
64         /// <param name="isSave"> 是否执行保存 </param>
65         /// <returns> 操作影响的行数 </returns>
66         int Delete(Expression<Func<TEntity, bool>> predicate, bool isSave = true);
67 
68         /// <summary>
69         ///     更新实体记录
70         /// </summary>
71         /// <param name="entity"> 实体对象 </param>
72         /// <param name="isSave"> 是否执行保存 </param>
73         /// <returns> 操作影响的行数 </returns>
74         int Update(TEntity entity, bool isSave = true);
75 
76         /// <summary>
77         ///     查找指定主键的实体记录
78         /// </summary>
79         /// <param name="key"> 指定主键 </param>
80         /// <returns> 符合编号的记录,不存在返回null </returns>
81         TEntity GetByKey(object key);
82 
83         #endregion
84     }
85 }
技术分享

  还要说明一下,每个操作方法都带有一个 isSave 可选参数,是为了单个实体操作的需要,免去了每次都要调用 context.SaveChanged()的麻烦。如果是进行多个实体的单元事务操作,就需要把这个参数设置为 false 。

  Repository的通用实现如下:

技术分享
  1 namespace GMF.Component.Data
  2 {
  3     /// <summary>
  4     ///     EntityFramework仓储操作基类
  5     /// </summary>
  6     /// <typeparam name="TEntity">动态实体类型</typeparam>
  7     public abstract class EFRepositoryBase<TEntity> : IRepository<TEntity> where TEntity : Entity
  8     {
  9         #region 属性
 10 
 11         /// <summary>
 12         ///     获取 仓储上下文的实例
 13         /// </summary>
 14         [Import]
 15         public IUnitOfWork UnitOfWork { get; set; }
 16 
 17         /// <summary>
 18         ///     获取或设置 EntityFramework的数据仓储上下文
 19         /// </summary>
 20         protected IUnitOfWorkContext EFContext
 21         {
 22             get
 23             {
 24                 if (UnitOfWork is IUnitOfWorkContext)
 25                 {
 26                     return UnitOfWork as IUnitOfWorkContext;
 27                 }
 28                 throw new DataAccessException(string.Format("数据仓储上下文对象类型不正确,应为IUnitOfWorkContext,实际为 {0}", UnitOfWork.GetType().Name));
 29             }
 30         }
 31 
 32         /// <summary>
 33         ///     获取 当前实体的查询数据集
 34         /// </summary>
 35         public virtual IQueryable<TEntity> Entities
 36         {
 37             get { return EFContext.Set<TEntity>(); }
 38         }
 39 
 40         #endregion
 41 
 42         #region 公共方法
 43 
 44         /// <summary>
 45         ///     插入实体记录
 46         /// </summary>
 47         /// <param name="entity"> 实体对象 </param>
 48         /// <param name="isSave"> 是否执行保存 </param>
 49         /// <returns> 操作影响的行数 </returns>
 50         public virtual int Insert(TEntity entity, bool isSave = true)
 51         {
 52             PublicHelper.CheckArgument(entity, "entity");
 53             EFContext.RegisterNew(entity);
 54             return isSave ? EFContext.Commit() : 0;
 55         }
 56 
 57         /// <summary>
 58         ///     批量插入实体记录集合
 59         /// </summary>
 60         /// <param name="entities"> 实体记录集合 </param>
 61         /// <param name="isSave"> 是否执行保存 </param>
 62         /// <returns> 操作影响的行数 </returns>
 63         public virtual int Insert(IEnumerable<TEntity> entities, bool isSave = true)
 64         {
 65             PublicHelper.CheckArgument(entities, "entities");
 66             EFContext.RegisterNew(entities);
 67             return isSave ? EFContext.Commit() : 0;
 68         }
 69 
 70         /// <summary>
 71         ///     删除指定编号的记录
 72         /// </summary>
 73         /// <param name="id"> 实体记录编号 </param>
 74         /// <param name="isSave"> 是否执行保存 </param>
 75         /// <returns> 操作影响的行数 </returns>
 76         public virtual int Delete(object id, bool isSave = true)
 77         {
 78             PublicHelper.CheckArgument(id, "id");
 79             TEntity entity = EFContext.Set<TEntity>().Find(id);
 80             return entity != null ? Delete(entity, isSave) : 0;
 81         }
 82 
 83         /// <summary>
 84         ///     删除实体记录
 85         /// </summary>
 86         /// <param name="entity"> 实体对象 </param>
 87         /// <param name="isSave"> 是否执行保存 </param>
 88         /// <returns> 操作影响的行数 </returns>
 89         public virtual int Delete(TEntity entity, bool isSave = true)
 90         {
 91             PublicHelper.CheckArgument(entity, "entity");
 92             EFContext.RegisterDeleted(entity);
 93             return isSave ? EFContext.Commit() : 0;
 94         }
 95 
 96         /// <summary>
 97         ///     删除实体记录集合
 98         /// </summary>
 99         /// <param name="entities"> 实体记录集合 </param>
100         /// <param name="isSave"> 是否执行保存 </param>
101         /// <returns> 操作影响的行数 </returns>
102         public virtual int Delete(IEnumerable<TEntity> entities, bool isSave = true)
103         {
104             PublicHelper.CheckArgument(entities, "entities");
105             EFContext.RegisterDeleted(entities);
106             return isSave ? EFContext.Commit() : 0;
107         }
108 
109         /// <summary>
110         ///     删除所有符合特定表达式的数据
111         /// </summary>
112         /// <param name="predicate"> 查询条件谓语表达式 </param>
113         /// <param name="isSave"> 是否执行保存 </param>
114         /// <returns> 操作影响的行数 </returns>
115         public virtual int Delete(Expression<Func<TEntity, bool>> predicate, bool isSave = true)
116         {
117             PublicHelper.CheckArgument(predicate, "predicate");
118             List<TEntity> entities = EFContext.Set<TEntity>().Where(predicate).ToList;
119             return entities.Count > 0 ? Delete(entities, isSave) : 0;
120         }
121 
122         /// <summary>
123         ///     更新实体记录
124         /// </summary>
125         /// <param name="entity"> 实体对象 </param>
126         /// <param name="isSave"> 是否执行保存 </param>
127         /// <returns> 操作影响的行数 </returns>
128         public virtual int Update(TEntity entity, bool isSave = true)
129         {
130             PublicHelper.CheckArgument(entity, "entity");
131             EFContext.RegisterModified(entity);
132             return isSave ? EFContext.Commit() : 0;
133         }
134 
135         /// <summary>
136         ///     查找指定主键的实体记录
137         /// </summary>
138         /// <param name="key"> 指定主键 </param>
139         /// <returns> 符合编号的记录,不存在返回null </returns>
140         public virtual TEntity GetByKey(object key)
141         {
142             PublicHelper.CheckArgument(key, "key");
143             return EFContext.Set<TEntity>().Find(key);
144         }
145 
146         #endregion
147     }
148 }
技术分享

   实现类中所有操作最终都是通过单元操作来提交的,关于单元操作,马上就会讲到。

UnitOfWork

  引入单元操作,主要是为了给各个实体维护一个共同的DbContext上下文对象,保证所有的操作都是在共同的上下文中进行的。EF的操作提交 context.SaveChanged() 默认就是事务性的,只要保证了当前的所有实体的操作都是在一个共同的上下文中进行的,就实现了事务操作了。

  在业务层中,各个实体的增删改操作都是通过各个实体的Repository进行的,只需要提供一个提交保存的功能作为最后调用,即可保证当前的提交是事务性的。因此定义给业务层引用的单元操作接口如下:

技术分享
 1 namespace GMF.Component.Data
 2 {
 3     /// <summary>
 4     ///     业务单元操作接口
 5     /// </summary>
 6     public interface IUnitOfWork
 7     {
 8         #region 属性
 9 
10         /// <summary>
11         ///     获取 当前单元操作是否已被提交
12         /// </summary>
13         bool IsCommitted { get; }
14 
15         #endregion
16 
17         #region 方法
18 
19         /// <summary>
20         ///     提交当前单元操作的结果
21         /// </summary>
22         /// <returns></returns>
23         int Commit();
24 
25         /// <summary>
26         ///     把当前单元操作回滚成未提交状态
27         /// </summary>
28         void Rollback();
29 
30         #endregion
31     }
32 }
技术分享

   在数据组件内部,数据操作最终都提交到一个与IUnitOfWork接口的实现类中进行操作,以保证各个实体的Repository与IUnitOfWork使用的是同一个DbContext上下文。定义数据单元操作接口如下:

技术分享
 1 namespace GMF.Component.Data
 2 {
 3     /// <summary>
 4     ///     数据单元操作接口
 5     /// </summary>
 6     public interface IUnitOfWorkContext : IUnitOfWork, IDisposable
 7     {
 8         /// <summary>
 9         ///   为指定的类型返回 System.Data.Entity.DbSet,这将允许对上下文中的给定实体执行 CRUD 操作。
10         /// </summary>
11         /// <typeparam name="TEntity"> 应为其返回一个集的实体类型。 </typeparam>
12         /// <returns> 给定实体类型的 System.Data.Entity.DbSet 实例。 </returns>
13         DbSet<TEntity> Set<TEntity>() where TEntity : Entity;
14 
15         /// <summary>
16         ///   注册一个新的对象到仓储上下文中
17         /// </summary>
18         /// <typeparam name="TEntity"> 要注册的类型 </typeparam>
19         /// <param name="entity"> 要注册的对象 </param>
20         void RegisterNew<TEntity>(TEntity entity) where TEntity : Entity;
21 
22         /// <summary>
23         ///   批量注册多个新的对象到仓储上下文中
24         /// </summary>
25         /// <typeparam name="TEntity"> 要注册的类型 </typeparam>
26         /// <param name="entities"> 要注册的对象集合 </param>
27         void RegisterNew<TEntity>(IEnumerable<TEntity> entities) where TEntity : Entity;
28 
29         /// <summary>
30         ///   注册一个更改的对象到仓储上下文中
31         /// </summary>
32         /// <typeparam name="TEntity"> 要注册的类型 </typeparam>
33         /// <param name="entity"> 要注册的对象 </param>
34         void RegisterModified<TEntity>(TEntity entity) where TEntity : Entity;
35 
36         /// <summary>
37         ///   注册一个删除的对象到仓储上下文中
38         /// </summary>
39         /// <typeparam name="TEntity"> 要注册的类型 </typeparam>
40         /// <param name="entity"> 要注册的对象 </param>
41         void RegisterDeleted<TEntity>(TEntity entity) where TEntity : Entity;
42 
43         /// <summary>
44         ///   批量注册多个删除的对象到仓储上下文中
45         /// </summary>
46         /// <typeparam name="TEntity"> 要注册的类型 </typeparam>
47         /// <param name="entities"> 要注册的对象集合 </param>
48         void RegisterDeleted<TEntity>(IEnumerable<TEntity> entities) where TEntity : Entity;
49     }
50 }
技术分享

  在单元操作的实现基类中,定义一个只读的DbContext抽象属性,实际的DbContext上下文需要在实现类中进行重写赋值。

技术分享
  1 namespace GMF.Component.Data
  2 {
  3     /// <summary>
  4     ///     单元操作实现
  5     /// </summary>
  6     public abstract class UnitOfWorkContextBase : IUnitOfWorkContext
  7     {
  8         /// <summary>
  9         /// 获取 当前使用的数据访问上下文对象
 10         /// </summary>
 11         protected abstract DbContext Context { get; }
 12 
 13         /// <summary>
 14         ///     获取 当前单元操作是否已被提交
 15         /// </summary>
 16         public bool IsCommitted { get; private set; }
 17 
 18         /// <summary>
 19         ///     提交当前单元操作的结果
 20         /// </summary>
 21         /// <returns></returns>
 22         public int Commit()
 23         {
 24             if (IsCommitted)
 25             {
 26                 return 0;
 27             }
 28             try
 29             {
 30                 int result = Context.SaveChanges();
 31                 IsCommitted = true;
 32                 return result;
 33             }
 34             catch (DbUpdateException e)
 35             {
 36                 if (e.InnerException != null && e.InnerException.InnerException is SqlException)
 37                 {
 38                     SqlException sqlEx = e.InnerException.InnerException as SqlException;
 39                     string msg = DataHelper.GetSqlExceptionMessage(sqlEx.Number);
 40                     throw PublicHelper.ThrowDataAccessException("提交数据更新时发生异常:" + msg, sqlEx);
 41                 }
 42                 throw;
 43             }
 44         }
 45 
 46         /// <summary>
 47         ///     把当前单元操作回滚成未提交状态
 48         /// </summary>
 49         public void Rollback()
 50         {
 51             IsCommitted = false;
 52         }
 53 
 54         public void Dispose()
 55         {
 56             if (!IsCommitted)
 57             {
 58                 Commit();
 59             }
 60             Context.Dispose();
 61         }
 62 
 63         /// <summary>
 64         ///   为指定的类型返回 System.Data.Entity.DbSet,这将允许对上下文中的给定实体执行 CRUD 操作。
 65         /// </summary>
 66         /// <typeparam name="TEntity"> 应为其返回一个集的实体类型。 </typeparam>
 67         /// <returns> 给定实体类型的 System.Data.Entity.DbSet 实例。 </returns>
 68         public DbSet<TEntity> Set<TEntity>() where TEntity : Entity
 69         {
 70             return Context.Set<TEntity>();
 71         }
 72 
 73         /// <summary>
 74         ///     注册一个新的对象到仓储上下文中
 75         /// </summary>
 76         /// <typeparam name="TEntity"> 要注册的类型 </typeparam>
 77         /// <param name="entity"> 要注册的对象 </param>
 78         public void RegisterNew<TEntity>(TEntity entity) where TEntity : Entity
 79         {
 80             EntityState state = Context.Entry(entity).State;
 81             if (state == EntityState.Detached)
 82             {
 83                 Context.Entry(entity).State = EntityState.Added;
 84             }
 85             IsCommitted = false;
 86         }
 87 
 88         /// <summary>
 89         ///     批量注册多个新的对象到仓储上下文中
 90         /// </summary>
 91         /// <typeparam name="TEntity"> 要注册的类型 </typeparam>
 92         /// <param name="entities"> 要注册的对象集合 </param>
 93         public void RegisterNew<TEntity>(IEnumerable<TEntity> entities) where TEntity : Entity
 94         {
 95             try
 96             {
 97                 Context.Configuration.AutoDetectChangesEnabled = false;
 98                 foreach (TEntity entity in entities)
 99                 {
100                     RegisterNew(entity);
101                 }
102             }
103             finally
104             {
105                 Context.Configuration.AutoDetectChangesEnabled = true;
106             }
107         }
108 
109         /// <summary>
110         ///     注册一个更改的对象到仓储上下文中
111         /// </summary>
112         /// <typeparam name="TEntity"> 要注册的类型 </typeparam>
113         /// <param name="entity"> 要注册的对象 </param>
114         public void RegisterModified<TEntity>(TEntity entity) where TEntity : Entity
115         {
116             if (Context.Entry(entity).State == EntityState.Detached)
117             {
118                 Context.Set<TEntity>().Attach(entity);
119             }
120             Context.Entry(entity).State = EntityState.Modified;
121             IsCommitted = false;
122         }
123 
124         /// <summary>
125         ///   注册一个删除的对象到仓储上下文中
126         /// </summary>
127         /// <typeparam name="TEntity"> 要注册的类型 </typeparam>
128         /// <param name="entity"> 要注册的对象 </param>
129         public void RegisterDeleted<TEntity>(TEntity entity) where TEntity : Entity
130         {
131             Context.Entry(entity).State = EntityState.Deleted;
132             IsCommitted = false;
133         }
134 
135         /// <summary>
136         ///   批量注册多个删除的对象到仓储上下文中
137         /// </summary>
138         /// <typeparam name="TEntity"> 要注册的类型 </typeparam>
139         /// <param name="entities"> 要注册的对象集合 </param>
140         public void RegisterDeleted<TEntity>(IEnumerable<TEntity> entities) where TEntity : Entity
141         {
142             try
143             {
144                 Context.Configuration.AutoDetectChangesEnabled = false;
145                 foreach (TEntity entity in entities)
146                 {
147                     RegisterDeleted(entity);
148                 }
149             }
150             finally
151             {
152                 Context.Configuration.AutoDetectChangesEnabled = true;
153             }
154         }
155     }
156 }
技术分享

  实体数据操作中,需要特别说明一下的是批量操作。EF不支持批量操作(直接执行SQL语句的方式除外),但我们可以使用多次变更一次提交的方式 来进行批量的插入,删除等操作。在进行数据的变更时,EF默认会自动的跟踪数据的变化(AutoDetectChangesEnabled = true),当变更的数据量较大的时候,EF的跟踪工作量就会骤增,使指定操作变得非常缓慢(这也是部分同学怀疑EF的性能问题的一个怀疑点),其实,只 要在批量操作的时候把自动跟踪关闭(AutoDetectChangesEnabled = false),即可解决缓慢的问题。如以上代码 144 行与 152 行所示。

  特别说明:本文的UnitOfWork实现方案参考了 dax.net 的 《深度剖析Byteart Retail案例:仓储(Repository)及其上下文(Repository Context)》,在此表示感谢。

  至此,我们回顾一下解决方案的结构:

  技术分享

  与业务实体无关的数据组件 GMF.Component.Data 已经搭建完成,下面的工作将与业务实体密切相关。

业务整合应用

  与业务实体相关的数据功能代码定义在 GMF.Demo.Core.Data 项目中。

业务实体的仓储实现

  首先,实现各个实体的 Repository 仓储操作,这里只以 用户信息(Member)实体为例:

技术分享
1 namespace GMF.Demo.Core.Data.Repositories
2 {
3     /// <summary>
4     ///     仓储操作接口——用户信息
5     /// </summary>
6     public interface IMemberRepository : IRepository<Member> { }
7 }
技术分享 技术分享
1 namespace GMF.Demo.Core.Data.Repositories.Impl
2 {
3     /// <summary>
4     ///     仓储操作实现——用户信息
5     /// </summary>
6     [Export(typeof(IMemberRepository))]
7     public class MemberRepository : EFRepositoryBase<Member>, IMemberRepository { }
8 }
技术分享

   可以发现,通用仓储操作在数据组件中封装好后,在实际业务中只需要编写非常少量的代码即可实现各个实体的仓储操作,这就是封装的好处。

数据上下文实现

  DbContext上下文的实现,这里先使用微软官方示例中的传统方案:

技术分享
 1 namespace GMF.Demo.Core.Data.Context
 2 {
 3     /// <summary>
 4     ///     Demo项目数据访问上下文
 5     /// </summary>
 6     [Export(typeof (DbContext))]
 7     public class DemoDbContext : DbContext
 8     {
 9         #region 构造函数
10 
11         /// <summary>
12         ///     初始化一个 使用连接名称为“default”的数据访问上下文类 的新实例
13         /// </summary>
14         public DemoDbContext()
15             : base("default") { }
16 
17         /// <summary>
18         /// 初始化一个 使用指定数据连接名称或连接串 的数据访问上下文类 的新实例
19         /// </summary>
20         public DemoDbContext(string nameOrConnectionString)
21             : base(nameOrConnectionString) {  }
22 
23         #endregion
24 
25         #region 属性
26 
27         public DbSet<Role> Roles { get; set; }
28 
29         public DbSet<Member> Members { get; set; }
30 
31         public DbSet<MemberExtend> MemberExtends { get; set; }
32 
33         public DbSet<LoginLog> LoginLogs { get; set; }
34 
35         #endregion
36 
37         protected override void OnModelCreating(DbModelBuilder modelBuilder)
38         {
39             //移除一对多的级联删除约定,想要级联删除可以在 EntityTypeConfiguration<TEntity>的实现类中进行控制
40             modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
41             //多对多启用级联删除约定,不想级联删除可以在删除前判断关联的数据进行拦截
42             //modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
43         }
44     }
45 }
技术分享

  值得注意的是,在EF中提供了很多的规则来定义 EF生成数据库 的行为,如下图所示:

技术分享

  我们可以在 DbContext 的派生类中重写 OnModelCreating 方法来移除一些规则来达到某些需求,比如在此,我们移除 OneToManyCascadeDeleteConvention 来达到禁用数据库的 一对多的级联删除 ,需要时再在做实体映射时启用,就能防止由于误操作而导致实体相关的数据都被删除的情况。

  当然,这里

人气教程排行