时间: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 }
实现类中所有操作最终都是通过单元操作来提交的,关于单元操作,马上就会讲到。
引入单元操作,主要是为了给各个实体维护一个共同的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 来达到禁用数据库的 一对多的级联删除 ,需要时再在做实体映射时启用,就能防止由于误操作而导致实体相关的数据都被删除的情况。
当然,这里