七色花基本权限系统(14)- 实现EntityFramework和Dapper的混搭

Dapper是什么

Dapper是一款轻量级的微ORM,其核心是实现了“将查询结果映射到指定数据模型”,因此可以抛开DataSet、DataTable等数据集对象,以强类型的方式使用查询数据结果。Dapper是开源的,它的GitHub地址在这里:https://github.com/StackExchange/dapper-dot-net,本章节中选择1.4.0版本的Dapper下的.NET45下的核心类,点击下载该核心类:SqlMapper

为什么要用Dapper来配合EntityFramework使用

EF作为纯粹的ORM,太重,其核心的linq to entity、lambda并不合适进行复杂的查询。那么复杂的查询就交给“能够将查询结果自动映射到指定数据模型”的工具吧,Dapper恰好符合。

EF虽然也暴露了3个执行sql的接口,但比较不方便,对参数的自动识别也没有做处理。Dapper对sql参数的自动识别处理非常棒。

Dapper非常轻量,其本身只有一个SqlMapper类。

Dapper执行速度快,性能高。

支持绝大部分的主流数据库。

Dapper层

Nuget上有Dapper下载,但为了能看源码,还是自己建一个类库来包装Dapper源码更好。

数据核心层

在数据核心层(S.Framework.DataCore)创建Dapper上下文实现类,使核心层支持Dapper,创建结构如下:

DapperContext类是Dapper上下文类(作用类似EF的entityContext),在这个类里将对“Dapper暴露出来的主要方法(如查询、执行)”进行封装,其完整代码如下:

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Text;
  5 using System.Threading.Tasks;
  6 using System.Data;
  7 using System.Data.Common;
  8
  9 using S.Dapper;
 10 using S.Utilities;
 11
 12 namespace S.Framework.DataCore.Dapper
 13 {
 14     /// <summary>
 15     /// 数据Dapper工具
 16     /// </summary>
 17     public class DapperContext : IDisposable
 18     {
 19         /// <summary>
 20         /// 数据连接
 21         /// </summary>
 22         private IDbConnection dbConnecttion { get; set; }
 23
 24         /// <summary>
 25         /// 数据事务
 26         /// </summary>
 27         private IDbTransaction dbTransaction { get; set; }
 28
 29         /// <summary>
 30         /// 数据管道
 31         /// </summary>
 32         private DbProviderFactory dbProviderFactory { get; set; }
 33
 34         /// <summary>
 35         /// 持久化行数
 36         /// </summary>
 37         public int PersistenceLine = 0;
 38
 39         /// <summary>
 40         /// 构造函数
 41         /// </summary>
 42         /// <param name="connString">连接字符串</param>
 43         /// <param name="providerName">提供商名称</param>
 44         public DapperContext(string connString, string providerName)
 45             : this(DbProviderFactories.GetFactory(providerName),connString)
 46         {
 47
 48         }
 49
 50         /// <summary>
 51         /// 构造函数
 52         /// </summary>
 53         /// <param name="providerFactory">管道工厂对象</param>
 54         /// <param name="connString">连接字符串</param>
 55         public DapperContext(DbProviderFactory providerFactory, string connString)
 56         {
 57             this.dbProviderFactory = providerFactory;
 58             this.dbConnecttion = this.dbProviderFactory.CreateConnection();
 59             this.dbConnecttion.ConnectionString = connString;
 60         }
 61
 62         /// <summary>
 63         /// 构造函数
 64         /// </summary>
 65         /// <param name="conn">数据库连接对象</param>
 66         public DapperContext(DbConnection conn)
 67         {
 68             this.dbProviderFactory = DbProviderFactories.GetFactory(conn);
 69             this.dbConnecttion = conn;
 70         }
 71
 72         /// <summary>
 73         /// 开始事务
 74         /// </summary>
 75         public void BeginTransaction()
 76         {
 77             this.TryOpenConnection();
 78             this.BeginTransaction(this.dbConnecttion.BeginTransaction());
 79         }
 80
 81         /// <summary>
 82         /// 设置事务
 83         /// </summary>
 84         /// <param name="dbTransaction">事务对象</param>
 85         public void BeginTransaction(IDbTransaction dbTransaction)
 86         {
 87             this.TryOpenConnection();
 88             this.dbTransaction = dbTransaction;
 89             this.PersistenceLine = 0;
 90         }
 91
 92         /// <summary>
 93         /// 提交事务
 94         /// </summary>
 95         public int Commit()
 96         {
 97             if (this.dbTransaction != null)
 98             {
 99                 this.dbTransaction.Commit();
100                 this.dbTransaction = null;//Commit之后虽会将事务对象的连接信息清空,但对象本身仍旧存在。为方便外部获取事务对象后判定空,此处清空事务对象。
101             }
102             int result = this.PersistenceLine;
103             this.PersistenceLine = 0;
104             return result;
105         }
106
107         /// <summary>
108         /// 回滚事务
109         /// </summary>
110         public void Rollback()
111         {
112             if (this.dbTransaction != null)
113             {
114                 this.dbTransaction.Rollback();
115                 this.dbTransaction = null;//Rollback之后虽会将事务对象的连接信息清空,但对象本身仍旧存在。为方便外部获取事务对象后判定空,此处清空事务对象。
116                 this.PersistenceLine = 0;
117             }
118         }
119
120         /// <summary>
121         /// 获取事务对象
122         /// </summary>
123         /// <returns></returns>
124         public DbTransaction GetTransaction()
125         {
126             return this.dbTransaction as DbTransaction;
127         }
128
129         #region 原生函数
130
131         /// <summary>
132         /// 根据SQL查询列表
133         /// </summary>
134         /// <typeparam name="T">实体类型</typeparam>
135         /// <param name="sql">SQL</param>
136         /// <param name="param">参数</param>
137         /// <param name="buffered">是否缓冲</param>
138         /// <param name="commandTimeout">超时时间</param>
139         /// <returns>查询结果泛型序列</returns>
140         public IEnumerable<T> Query<T>(string sql, object param = null, bool buffered = true, int? commandTimeout = null)
141         {
142             this.TryOpenConnection();
143             return this.dbConnecttion.Query<T>(sql, param, this.dbTransaction, buffered, commandTimeout, CommandType.Text);
144         }
145
146         /// <summary>
147         /// 执行SQL语句
148         /// </summary>
149         /// <param name="sql">SQL</param>
150         /// <param name="param">参数</param>
151         /// <param name="commandTimeout">超时时间</param>
152         /// <returns>受影响行数</returns>
153         public int Execute(string sql, object param = null, int? commandTimeout = null)
154         {
155             this.TryOpenConnection();
156             int result = this.dbConnecttion.Execute(sql, param, this.dbTransaction, commandTimeout, CommandType.Text);
157             this.PersistenceLine += result;
158             return result;
159         }
160
161         /// <summary>
162         /// 查询取值
163         /// </summary>
164         /// <param name="sql">查询字符串</param>
165         /// <param name="param">参数</param>
166         /// <param name="commandTimeout">超时时间</param>
167         /// <returns></returns>
168         public object ExecuteScalar(string sql, object param = null, int? commandTimeout = null)
169         {
170             this.TryOpenConnection();
171             return this.dbConnecttion.ExecuteScalar(sql, param, this.dbTransaction, commandTimeout, CommandType.Text);
172         }
173
174         /// <summary>
175         /// 查询取值
176         /// </summary>
177         /// <typeparam name="T">返回值类型</typeparam>
178         /// <param name="sql">查询字符串</param>
179         /// <param name="param">参数</param>
180         /// <param name="commandTimeout">超时时间</param>
181         /// <returns></returns>
182         public T ExecuteScalar<T>(string sql, object param = null, int? commandTimeout = null)
183         {
184             this.TryOpenConnection();
185             return this.dbConnecttion.ExecuteScalar<T>(sql, param, this.dbTransaction, commandTimeout, CommandType.Text);
186         }
187
188         /// <summary>
189         /// 执行存储过程返回列表
190         /// </summary>
191         /// <param name="name">存储过程名称</param>
192         /// <param name="param">参数</param>
193         /// <param name="buffered">是否缓冲</param>
194         /// <param name="commandTimeout">超时时间</param>
195         /// <returns>查询结果泛型序列</returns>
196         public IEnumerable<T> StoredQuery<T>(string name, object param = null, bool buffered = true, int? commandTimeout = null)
197         {
198             this.TryOpenConnection();
199             return this.dbConnecttion.Query<T>(name, param, this.dbTransaction, buffered, commandTimeout, CommandType.StoredProcedure);
200         }
201
202         /// <summary>
203         /// 存储过程取值
204         /// </summary>
205         /// <param name="name">存储过程名称</param>
206         /// <param name="param">参数</param>
207         /// <param name="commandTimeout">超时时间</param>
208         /// <returns></returns>
209         public object StoredScalar(string name, object param = null, int? commandTimeout = null)
210         {
211             this.TryOpenConnection();
212             return this.dbConnecttion.ExecuteScalar(name, param, this.dbTransaction, commandTimeout, CommandType.StoredProcedure);
213         }
214
215         /// <summary>
216         /// 存储过程取值
217         /// </summary>
218         /// <typeparam name="T">返回值类型</typeparam>
219         /// <param name="name">存储过程名称</param>
220         /// <param name="param">参数</param>
221         /// <param name="commandTimeout">超时时间</param>
222         /// <returns></returns>
223         public T StoredScalar<T>(string name, object param = null, int? commandTimeout = null)
224         {
225             this.TryOpenConnection();
226             return this.dbConnecttion.ExecuteScalar<T>(name, param, this.dbTransaction, commandTimeout, CommandType.StoredProcedure);
227         }
228
229         /// <summary>
230         /// 执行存储过程
231         /// </summary>
232         /// <param name="name">存储过程名称</param>
233         /// <param name="param">参数</param>
234         /// <param name="commandTimeout">超时时间</param>
235         public void StoredExecute(string name, object param = null, int? commandTimeout = null)
236         {
237             this.TryOpenConnection();
238             this.dbConnecttion.Execute(name, param, this.dbTransaction, commandTimeout, CommandType.StoredProcedure);
239         }
240
241         #endregion
242
243         /// <summary>
244         /// 尝试打开数据库连接
245         /// </summary>
246         private void TryOpenConnection()
247         {
248             if (this.dbConnecttion.State == ConnectionState.Closed)
249             {
250                 try { this.dbConnecttion.Open(); }
251                 catch (Exception e)
252                 {
253                     throw ExceptionHelper.ThrowDataAccessException("Dapper打开数据库连接时发生异常。", e);
254                 }
255             }
256         }
257
258         /// <summary>
259         /// 释放资源
260         /// </summary>
261         public void Dispose()
262         {
263             Dispose(true);
264             GC.SuppressFinalize(this);
265         }
266
267         protected virtual void Dispose(bool disposing)
268         {
269             if (disposing)
270             {
271                 if (dbTransaction != null) { try { dbTransaction.Dispose(); dbTransaction = null; } catch { } }
272                 if (dbConnecttion != null) { try { dbConnecttion.Dispose(); dbConnecttion = null; } catch { } }
273             }
274         }
275
276         ~DapperContext() { Dispose(false); }
277     }
278 }
279 

DapperContext类

除了暴露Dapper的常用方法之外,还封装了事务相关的方法。这个类可以比较简单,也可以复杂到支持泛型Insert、Update、Delete等操作,但不是本文重点,此处不展开。如果需要暴露更多的Dapper方法,可以在这里添加。

数据实现层 - 工作单元

工作单元是定义数据库上下文的地方,EF的上下文对象就定义在这里,同样也要在此处增加“Dapper上下文”的定义。

这样一来,事务处理要同时考虑EF和Dapper的上下文,释放资源时一样。

开启事务时,仅是设置标记,因为此时上下文对象可能还不存在(初次调用仓储时才会初始化EF上下文),等到初始化上下文(无论是EF还是Dapper)时,再根据事务标记去决定是否需要对上下文开启事务,并保证两个上下文(如果两个上下文都存在)处于同一事务中。

不过需要注意的是,这里的事务是以数据库为单位的。工作单元的事务虽然涵盖所有数据库的事务,但各自独立。

工作单元的主要部分是由T4模板自动生成的,因此上述改动最后都在T4模板中调整,调整后的工作单元模板代码如下:

  1 <#+
  2 // <copyright file="UnitOfWork.tt" company="">
  3 //  Copyright ? . All Rights Reserved.
  4 // </copyright>
  5
  6 public class UnitOfWork : CSharpTemplate
  7 {
  8
  9     private IEnumerable<string> _prefixNameList;
 10
 11     public UnitOfWork(IEnumerable<string> prefixNameList)
 12     {
 13         this._prefixNameList = prefixNameList;
 14     }
 15 	public override string TransformText()
 16 	{
 17 		base.TransformText();
 18 #>
 19 using System;
 20 using System.Collections.Generic;
 21 using System.Linq;
 22 using System.Text;
 23 using System.Threading.Tasks;
 24
 25 using S.Framework.DataInterface;
 26 using S.Framework.DataInterface.IRepositoryFactories;
 27 using S.Utilities;
 28
 29 namespace S.Framework.DataAchieve.EntityFramework
 30 {
 31 	public partial class UnitOfWork : IUnitOfWork
 32     {
 33 <#+
 34             foreach(string item in _prefixNameList)
 35             {
 36 #>
 37         #region <#= item #> 的数据库连接字符串、数据库提供程序名称、上下文对象
 38
 39         /// <summary>
 40         /// 当前工作单元中 <#= item #>  数据库连接字符串
 41         /// </summary>
 42         internal string <#= item #>ConnString { get; private set; }
 43
 44         /// <summary>
 45         /// 当前工作单元中 <#= item #> 数据库提供程序名称
 46         /// </summary>
 47         internal string <#= item #>ProviderName { get; private set; }
 48
 49         private System.Data.Entity.DbContext _db<#= item #>;
 50
 51         private S.Framework.DataCore.Dapper.DapperContext _dapper<#= item #>;
 52
 53         /// <summary>
 54         /// 当前工作单元中 <#= item #> 数据库的 EF 上下文
 55         /// </summary>
 56         internal System.Data.Entity.DbContext Db<#= item #>
 57         {
 58             get
 59             {
 60                 if (!this.<#= item #>DbIsExist)
 61                 {
 62                     this._db<#= item #> = new S.Framework.DataCore.EntityFramework.EntityContexts.<#= item #>EntityContext(this.<#= item #>ConnString);
 63                     if (this.HasTransaction)
 64                     {
 65                         if (this.<#= item #>DapperIsExist)
 66                         {
 67                             //如果 <#= item #>Dapper 存在
 68                             var trans = this._dapper<#= item #>.GetTransaction();
 69                             if (trans != null)
 70                             {
 71                                 //并且 <#= item #>Dapper 的事务存在,就用 <#= item #>Dapper 的事务作为 <#= item #>Db 的事务
 72                                 this._db<#= item #>.Database.UseTransaction(trans);
 73                             }
 74                             else
 75                             {
 76                                 //否则由 <#= item #>Db 启动事务,并将该事务设置给 <#= item #>Dapper
 77                                 this._db<#= item #>.Database.BeginTransaction();
 78                                 this._dapper<#= item #>.BeginTransaction(this._db<#= item #>.Database.CurrentTransaction.UnderlyingTransaction);
 79                             }
 80                         }
 81                         else
 82                         {
 83                             //如果 <#= item #>Dapper 不存在,则由 <#= item #>Db 启动事务
 84                             if (this._db<#= item #>.Database.CurrentTransaction == null)
 85                             {
 86                                 this._db<#= item #>.Database.BeginTransaction();
 87                             }
 88                         }
 89                     }
 90                 }
 91                 return this._db<#= item #>;
 92             }
 93         }
 94
 95         /// <summary>
 96         /// 当前工作单元中 <#= item #> 数据库的 Dapper 上下文
 97         /// </summary>
 98         internal S.Framework.DataCore.Dapper.DapperContext Dapper<#= item #>
 99         {
100             get
101             {
102                 if (!this.<#= item #>DapperIsExist)
103                 {
104                     if (this.<#= item #>DbIsExist)
105                     {
106                         this._dapper<#= item #> = new S.Framework.DataCore.Dapper.DapperContext(this._db<#= item #>.Database.Connection);
107                     }
108                     else
109                     {
110                         this._dapper<#= item #> = new S.Framework.DataCore.Dapper.DapperContext(this.<#= item #>ConnString, this.<#= item #>ProviderName);
111                     }
112                     if (this.HasTransaction)
113                     {
114                         if (this.<#= item #>DbIsExist)
115                         {
116                             //如果 <#= item #>Db 存在
117                             var trans = this._db<#= item #>.Database.CurrentTransaction;
118                             if (trans != null)
119                             {
120                                 //并且 <#= item #>Db 的事务存在,就用 <#= item #>Db 的事务作为 <#= item #>Dapper 的事务
121                                 this._dapper<#= item #>.BeginTransaction(trans.UnderlyingTransaction);
122                             }
123                             else
124                             {
125                                 //否则由 <#= item #>Dapper 启动事务,并将该事务设置给 <#= item #>Db
126                                 this._dapper<#= item #>.BeginTransaction();
127                                 System.Data.Common.DbTransaction tr = this._dapper<#= item #>.GetTransaction();
128                                 this._db<#= item #>.Database.UseTransaction(tr);
129                             }
130                         }
131                         else
132                         {
133                             //如果 <#= item #>Db 不存在,则由 <#= item #>Dapper 启动事务
134                             if (this._dapper<#= item #>.GetTransaction() == null)
135                             {
136                                 this._dapper<#= item #>.BeginTransaction();
137                             }
138                         }
139                     }
140                 }
141                 return this._dapper<#= item #>;
142             }
143         }
144
145         /// <summary>
146         /// <#= item #> 数据库是否存在 EF 上下文
147         /// </summary>
148         private bool <#= item #>DbIsExist { get { return this._db<#= item #> != null; } }
149
150         /// <summary>
151         /// <#= item #> 数据库是否存在 Dapper 上下文
152         /// </summary>
153         private bool <#= item #>DapperIsExist { get { return this._dapper<#= item #> != null; } }
154
155         /// <summary>
156         /// 是否存在事务
157         /// </summary>
158         private bool HasTransaction { get; set; }
159
160         #endregion
161
162 <#+
163             }
164 #>
165         #region 仓储工厂对象
166
167 <#+
168         foreach(string item in _prefixNameList)
169         {
170 #>
171         /// <summary>
172         /// <#= item #> 仓储工厂
173         /// </summary>
174         public I<#= item #>IRepositoryFactory <#= item #>
175         {
176             get { return GetRepositoryFactoryByInstance<RepositoryFactories.<#= item #>IRepositoryFactory>(); }
177         }
178 <#+
179         }
180 #>
181
182         #endregion
183
184         #region 构造函数
185
186         /// <summary>
187         /// 构造函数
188         /// <param name="connectionStringNames">数据库连接字符串名称集合,Key表示数据库标识名称,Value表示数据库连接字符串名称</param>
189         /// </summary>
190         public UnitOfWork(Dictionary<string, string> connectionStringNames)
191         {
192             if (connectionStringNames.IsNullOrEmpty())
193             {
194                 throw ExceptionHelper.ThrowDataAccessException("初始化工作单元对象时发生异常。", new ArgumentException("数据库连接信息集合参数不可为空。"));
195             }
196 <#+
197         foreach(string item in _prefixNameList)
198         {
199 #>
200             if (connectionStringNames.ContainsKey("<#= item #>"))
201             {
202                 var name = connectionStringNames["<#= item #>"];
203                 string connectionString = ConfigHelper.ConnectionString(name);
204                 string providerName = ConfigHelper.ProviderName(name);
205
206                 if (string.IsNullOrWhiteSpace(connectionString) || string.IsNullOrWhiteSpace(providerName))
207                 { throw ExceptionHelper.ThrowDataAccessException("初始化工作单元对象时发生异常。", new ArgumentException(name + "数据库连接信息有误。")); }
208
209                 this.<#= item #>ConnString = connectionString;
210                 this.<#= item #>ProviderName = providerName;
211             }
212 <#+
213         }
214 #>
215         }
216
217         #endregion
218
219         /// <summary>
220         /// 以数据库为单位开启事务
221         /// </summary>
222         public void BeginTransaction()
223         {
224             this.HasTransaction = true;
225         }
226
227         /// <summary>
228         /// 提交工作单元
229         /// </summary>
230         /// <returns>受影响行数</returns>
231         public int Commit()
232         {
233             int result = 0;
234 <#+
235         foreach(string item in _prefixNameList)
236         {
237 #>
238             if (this.<#= item #>DbIsExist && this._db<#= item #>.ChangeTracker.HasChanges())
239             {
240                 try
241                 { result += this._db<#= item #>.SaveChanges(); }
242                 catch (Exception e)
243                 {
244                     throw ExceptionHelper.ThrowDataAccessException("db<#= item #>执行SaveChange时发生异常。", e);
245                 }
246             }
247             if (this.<#= item #>DapperIsExist && this.HasTransaction)
248             {
249                 try
250                 {
251                     result += this._dapper<#= item #>.Commit();
252                 }
253                 catch(Exception e){
254                     this._dapper<#= item #>.Rollback();
255                     result = 0;
256                 }
257             }
258             this.HasTransaction = false;
259 <#+
260         }
261 #>
262             return result;
263         }
264
265         /// <summary>
266         /// 执行回滚事务
267         /// </summary>
268         public void Rollback()
269         {
270 <#+
271         foreach(string item in _prefixNameList)
272         {
273 #>
274             if (this.<#= item #>DbIsExist && this._db<#= item #>.ChangeTracker.HasChanges())
275             {
276                 var entities = this._db<#= item #>.ChangeTracker.Entries();
277                 foreach (var entity in entities.Where(e => e.State == System.Data.Entity.EntityState.Added || e.State == System.Data.Entity.EntityState.Modified || e.State == System.Data.Entity.EntityState.Deleted))
278                 {
279                     entity.State = System.Data.Entity.EntityState.Detached;
280                 }
281             }
282             if (this.<#= item #>DapperIsExist && this.HasTransaction)
283             {
284                 this._dapper<#= item #>.Rollback();
285             }
286             this.HasTransaction = false;
287 <#+
288         }
289 #>
290         }
291
292         #region 释放资源
293
294         /// <summary>
295         /// 释放资源
296         /// </summary>
297         public void Dispose()
298         {
299             Dispose(true); GC.SuppressFinalize(this);
300         }
301
302         /// <summary>
303         /// 释放资源
304         /// </summary>
305         /// <param name="disposing">是否释放</param>
306         protected virtual void Dispose(bool disposing)
307         {
308             if (disposing)
309             {
310 <#+
311         foreach(string item in _prefixNameList)
312         {
313 #>
314                 if (this.<#= item #>DbIsExist)
315                 {
316                     try
317                     {
318                         this.Db<#= item #>.Dispose();
319                         this._db<#= item #> = null;
320                     }
321                     catch { }
322                 }
323                 if (this.<#= item #>DapperIsExist)
324                 {
325                     try
326                     {
327                         this.Dapper<#= item #>.Dispose();
328                         this._dapper<#= item #> = null;
329                     }
330                     catch { }
331                 }
332 <#+
333         }
334 #>
335             }
336         }
337
338         #endregion
339     }
340 }
341 <#+
342         return this.GenerationEnvironment.ToString();
343 	}
344 }
345 #>
346 

调整后的工作单元模板

除了“由T4生成的工作单元类”之外,还需在“手动创建的工作单元类”中增加一个方法:

  1 private System.Reflection.PropertyInfo[] _propertiesCache;
  2
  3 /// <summary>
  4 /// 获取指定数据库的 Dapper 上下文
  5 /// </summary>
  6 /// <param name="databaseKey">数据库标记</param>
  7 /// <returns></returns>
  8 internal S.Framework.DataCore.Dapper.DapperContext GetDapperDbContext(string databaseKey)
  9 {
 10     if (_propertiesCache == null)
 11     {
 12         _propertiesCache = this.GetType().GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
 13     }
 14     var the = _propertiesCache.FirstOrDefault(f => f.Name.Contains("Dapper") && f.Name.Contains(databaseKey) && f.PropertyType == typeof(S.Framework.DataCore.Dapper.DapperContext));
 15     if (the != null)
 16     {
 17         S.Framework.DataCore.Dapper.DapperContext db = the.GetMethod.Invoke(this, null) as S.Framework.DataCore.Dapper.DapperContext;
 18         return db;
 19     }
 20     return null;
 21 }

该方法用于实现“让Dapper上下文能够按需初始化”,也就是说调用仓储后自动初始化的仅仅是EF上下文,只有在调用Dapper时才会初始化Dapper上下文。这样就避免了“仅使用EF的情况下也要初始化Dapper”的情况。

数据实现层 - 基本仓储

在前面的实现仓储的章节中,通过“数据库仓储工厂对象”把所属数据库的EF上下文传入基本仓储的方式来确定“基本仓储中的EF上下文是工作单元中的哪个EF上下文(因为工作单元中可能存在多个数据库的EF上下文)”。同样,Dapper上下文也需要“类似但稍有差异”的方式来确定。

先调整基本仓储接口(IBaseRepository),增加方法定义:

  1 /// <summary>
  2 /// 设置数据库标记。设置了编辑器不可见,但只有跨解决方案时才有用。当前解决方案下,还是会被智能提示捕捉到。
  3 /// <param name="key">数据库标记</param>
  4 /// </summary>
  5 [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
  6 void SetDatabaseKey(string key);

然后在基本仓储实现(BaseRepository)中,定义数据库标记:

  1 private string DatabaseKey { get; set; }

并实现接口中的SetDatabaseKey方法:

  1 /// <summary>
  2 /// 设置数据库标记
  3 /// </summary>
  4 /// <param name="key">设置数据库标记</param>
  5 public void SetDatabaseKey(string key)
  6 {
  7     this.DatabaseKey = key;
  8 }

最后实现Dapper上下文:

  1 private S.Framework.DataCore.Dapper.DapperContext _dapperDb;
  2
  3 /// <summary>
  4 /// Dapper数据库上下文,该上下文实现了按需初始化
  5 /// </summary>
  6 private S.Framework.DataCore.Dapper.DapperContext DapperDb
  7 {
  8     get
  9     {
 10         if (this._dapperDb == null)
 11         {
 12             this._dapperDb = this.UnitOfWork.GetDapperDbContext(this.DatabaseKey);
 13         }
 14         return this._dapperDb;
 15     }
 16 }

数据实现层 - 仓储工厂

基本仓储中用于设置数据库标记的SetDatabaseKey方法已经准备好,那么在仓储工厂中初始化仓储时需要调用该方法并传递正确的参数。

修改基本仓储工厂(BaseRepositoryFactory)中的获取仓储方法(GetRepositoryByInstance),增加一个string类型的参数,并在调用SetDatabaseKey时传入:

  1 /// <summary>
  2 /// 获取仓储
  3 /// </summary>
  4 /// <typeparam name="TEntity">实体类型</typeparam>
  5 /// <typeparam name="R">仓储接口</typeparam>
  6 /// <param name="db">EF数据库上下文</param>
  7 /// <param name="databaseKey">数据库标记</param>
  8 /// <returns>仓储实例</returns>
  9 protected R GetRepositoryByInstance<TEntity, R>(System.Data.Entity.DbContext db, string databaseKey)
 10     where TEntity : class
 11     where R : class, IBaseRepository<TEntity>, new()
 12 {
 13     if (!repositoryCache.ContainsKey(typeof(TEntity)))
 14     {
 15         var repository = new R();
 16         repository.SetUnitOfWork(this.UnitOfWork);
 17         repository.SetDataContext(db);
 18         repository.SetDatabaseKey(databaseKey);
 19
 20         repositoryCache.Add(typeof(TEntity), repository);
 21
 22         return repository;
 23     }
 24     else { return (R)repositoryCache[typeof(TEntity)]; }
 25 }

再修改仓储工厂模板,调用上述方法时增加传入的参数即可,调整后的模板代码如下:

  1 <#+
  2 // <copyright file="RepositoryFactories.tt" company="">
  3 //  Copyright ? . All Rights Reserved.
  4 // </copyright>
  5
  6 public class RepositoryFactories : CSharpTemplate
  7 {
  8
  9     private string _prefixName;
 10     private IEnumerable<Type> _typeList;
 11
 12     public RepositoryFactories(string prefixName, IEnumerable<Type> typeList)
 13     {
 14         this._prefixName = prefixName;
 15         this._typeList = typeList;
 16     }
 17
 18 	public override string TransformText()
 19 	{
 20 		base.TransformText();
 21 #>
 22 using System;
 23 using System.Collections.Generic;
 24 using System.Linq;
 25 using System.Text;
 26 using System.Threading.Tasks;
 27
 28 using S.Framework.Entity.<#= _prefixName #>;
 29 using S.Framework.DataInterface.IRepositories.<#= _prefixName #>;
 30 using S.Framework.DataInterface.IRepositoryFactories;
 31 using S.Framework.DataAchieve.EntityFramework.Repositories.<#= _prefixName #>;
 32
 33 namespace S.Framework.DataAchieve.EntityFramework.RepositoryFactories
 34 {
 35     public class <#= _prefixName #>IRepositoryFactory : BaseRepositoryFactory, I<#= _prefixName #>IRepositoryFactory
 36     {
 37         private string _databaseKey = "<#= _prefixName #>";
 38
 39         #region 仓储对象
 40
 41 <#+
 42         foreach(Type item in _typeList)
 43         {
 44 #>
 45         /// <summary>
 46         /// <#= item.Name #> 仓储接口
 47         /// </summary>
 48         public I<#= item.Name #>Repository <#= item.Name #>
 49         {
 50             get
 51             {
 52                 return this.GetRepositoryByInstance<<#= item.Name #>, <#= item.Name #>Repository>(this.UnitOfWork.Db<#= _prefixName #>, this._databaseKey);
 53             }
 54         }
 55 <#+
 56         }
 57 #>
 58
 59         #endregion
 60     }
 61 }
 62 <#+
 63         return this.GenerationEnvironment.ToString();
 64 	}
 65 }
 66 #>
 67 

调整后的仓储工厂模板

到这一步,Dapper上下文已经准备完毕,接下来就要考虑“如何将Dapper上下文暴露给实体仓储”。

EF上下文是通过定义在基本仓储中的Query方法把IQueryable接口来实现暴露的,而没有直接暴露EF上下文对象本身。同样的,如果直接把Dapper上下文暴露出去,那么在实体仓储中将可以使用Dapper上下文内的所有公开成员,但其实Dapper上下文中的部分公开方法是为了在工作单元中更好地结合EF而已,不该全部暴露给实体仓储。并且可能还需要对Dapper上下文的方法进行扩充,所以应该在基本仓储中暴露原始方法并扩充新方法。

在基本仓储实现类(BaseRepository)中,增加以下代码(直接在类中加,不是并列):

  1 #region Dapper对外公开的方法,为方便区分,用子类隔离
  2
  3         private DapperIsolate _dapper;
  4
  5         /// <summary>
  6         /// Dapper成员封装对象
  7         /// </summary>
  8         internal DapperIsolate Dapper
  9         {
 10             get
 11             {
 12                 if (this._dapper == null)
 13                 {
 14                     this._dapper = new DapperIsolate(this.DapperDb);
 15                 }
 16                 return this._dapper;
 17             }
 18         }
 19
 20         /// <summary>
 21         /// Dapper隔离类
 22         /// </summary>
 23         internal class DapperIsolate
 24         {
 25             /// <summary>
 26             /// Dapper 数据库上下文
 27             /// </summary>
 28             private S.Framework.DataCore.Dapper.DapperContext DapperDb { get; set; }
 29
 30             public DapperIsolate(S.Framework.DataCore.Dapper.DapperContext db)
 31             {
 32                 this.DapperDb = db;
 33             }
 34
 35             /// <summary>
 36             /// 根据SQL查询列表
 37             /// </summary>
 38             /// <typeparam name="T">实体类型</typeparam>
 39             /// <param name="sql">SQL</param>
 40             /// <param name="param">参数</param>
 41             /// <param name="buffered">是否缓冲</param>
 42             /// <param name="commandTimeout">超时时间</param>
 43             /// <returns>查询结果泛型序列</returns>
 44             public IEnumerable<T> Query<T>(string sql, object param = null, bool buffered = true, int? commandTimeout = null)
 45             {
 46                 return this.DapperDb.Query<T>(sql, param, buffered, commandTimeout);
 47             }
 48
 49             /// <summary>
 50             /// 执行SQL语句
 51             /// </summary>
 52             /// <param name="sql">SQL</param>
 53             /// <param name="param">参数</param>
 54             /// <param name="commandTimeout">超时时间</param>
 55             /// <returns>受影响行数</returns>
 56             public int Execute(string sql, object param = null, int? commandTimeout = null)
 57             {
 58                 return this.DapperDb.Execute(sql, param, commandTimeout);
 59             }
 60
 61             /// <summary>
 62             /// 查询取值
 63             /// </summary>
 64             /// <param name="sql">查询字符串</param>
 65             /// <param name="param">参数</param>
 66             /// <param name="commandTimeout">超时时间</param>
 67             /// <returns></returns>
 68             public object ExecuteScalar(string sql, object param = null, int? commandTimeout = null)
 69             {
 70                 return this.DapperDb.ExecuteScalar(sql, param, commandTimeout);
 71             }
 72
 73             /// <summary>
 74             /// 查询取值
 75             /// </summary>
 76             /// <typeparam name="T">返回值类型</typeparam>
 77             /// <param name="sql">查询字符串</param>
 78             /// <param name="param">参数</param>
 79             /// <param name="commandTimeout">超时时间</param>
 80             /// <returns></returns>
 81             public T ExecuteScalar<T>(string sql, object param = null, int? commandTimeout = null)
 82             {
 83                 return this.DapperDb.ExecuteScalar<T>(sql, param, commandTimeout);
 84             }
 85
 86             /// <summary>
 87             /// 执行存储过程返回列表
 88             /// </summary>
 89             /// <param name="name">存储过程名称</param>
 90             /// <param name="param">参数</param>
 91             /// <param name="buffered">是否缓冲</param>
 92             /// <param name="commandTimeout">超时时间</param>
 93             /// <returns>查询结果泛型序列</returns>
 94             public IEnumerable<T> StoredQuery<T>(string name, object param = null, bool buffered = true, int? commandTimeout = null)
 95             {
 96                 return this.DapperDb.StoredQuery<T>(name, param, buffered, commandTimeout);
 97             }
 98
 99             /// <summary>
100             /// 存储过程取值
101             /// </summary>
102             /// <param name="name">存储过程名称</param>
103             /// <param name="param">参数</param>
104             /// <param name="commandTimeout">超时时间</param>
105             /// <returns></returns>
106             public object StoredScalar(string name, object param = null, int? commandTimeout = null)
107             {
108                 return this.DapperDb.StoredScalar(name, param, commandTimeout);
109             }
110
111             /// <summary>
112             /// 存储过程取值
113             /// </summary>
114             /// <typeparam name="T">返回值类型</typeparam>
115             /// <param name="name">存储过程名称</param>
116             /// <param name="param">参数</param>
117             /// <param name="commandTimeout">超时时间</param>
118             /// <returns></returns>
119             public T StoredScalar<T>(string name, object param = null, int? commandTimeout = null)
120             {
121                 return this.DapperDb.StoredScalar<T>(name, param, commandTimeout);
122             }
123
124             /// <summary>
125             /// 执行存储过程
126             /// </summary>
127             /// <param name="name">存储过程名称</param>
128             /// <param name="param">参数</param>
129             /// <param name="commandTimeout">超时时间</param>
130             public void StoredExecute(string name, object param = null, int? commandTimeout = null)
131             {
132                 this.DapperDb.StoredExecute(name, param, commandTimeout);
133             }
134         }
135
136 #endregion

在BaseRepository中的Dapper封装

其中为了隔离EF、Dapper的方法,特地嵌套了一个中间隔离类,使得能够在实体仓储中这样写:

  1 this.Dapper.Query<int>("select ID from table");

此致,混搭完成。

测试效果

把用户仓储(SysUserRepository)中用于登录校验的GetByUserName方法从linq to entity改成通过dapper查询:

  1 /// <summary>
  2 /// 根据用户名获取用户实体
  3 /// </summary>
  4 /// <param name="userName">用户名</param>
  5 /// <returns>用户实体</returns>
  6 public SysUser GetByUserName(string userName)
  7 {
  8     return this.Dapper.Query<SysUser>("select * from SysUser where UserName = @UserName", new { UserName = userName }).FirstOrDefault();
  9     //return this.Query(w => w.UserName == userName).FirstOrDefault();
 10 }

编译运行,登录正常,说明Dapper功能有效。

再来检验一下Dapper与EF混搭之后事务的效果。

在用户仓储中写一个以Dapper方式插入用户的方法:

  1 public void TestDapperAdd(SysUser entity)
  2 {
  3     StringBuilder sb = new StringBuilder();
  4     sb.Append(" insert SysUser (ID,UserName,Password,IsDisabled,IsDeleted,CreateUser,CreateDate)");
  5     sb.Append(" Values ");
  6     sb.Append(" (@ID,@UserName,@Password,@IsDisabled,@IsDeleted,@CreateUser,@CreateDate) ");
  7     sb.Append(";");
  8
  9     //传递user对象,dapper会自动解析对象属性名,并取值与sql中的同名参数相对应
 10     this.Dapper.Execute(sb.ToString(), entity);
 11 }

别忘了在用户仓储接口中定义同方法。

在用户业务类(SysUserBll)中写个测试方法:

  1 public void TestEFDapperTransaction()
  2 {
  3     using (var unit = IUnitOfWorkFactory.UnitOfWork)
  4     {
  5         //开启事务
  6         //其实 unit 内部只是设置一个标记而已,此时并未初始化任何数据库上下文
  7         unit.BeginTransaction();
  8
  9         var u1 = new S.Framework.Entity.Master.SysUser { ID = Guid.NewGuid().ToString(), UserName = "ef", Password = "123456", CreateUser = "admin", CreateDate = DateTime.Now };
 10         //调用到 SysUser 的仓储,自动初始化 ef 上下文,并开启事务,但不会初始化 dapper 上下文
 11         unit.Master.SysUser.Add(u1);
 12
 13         var u2 = new S.Framework.Entity.Master.SysUser { ID = Guid.NewGuid().ToString(), UserName = "dapper", Password = "123456", CreateUser = "admin", CreateDate = DateTime.Now };
 14         //通过 TestDapperAdd 方法调用到 Dapper 时,自动初始化 dapper 上下文,并获取 ef 上下文的事务对象,设置为 dapper 上下文的事务,这样就保证了共用1个事务
 15         unit.Master.SysUser.TestDapperAdd(u2);
 16
 17         //如果不进行 commit,ef 不会插入数据(因为 commit 中才会 SaveChanges ),dapper 也不会插入数据(执行了 sql 但回滚了,说明 dapper 是开启了事务的)
 18         //如果进行 commit,则 ef 和 dapper 都会插入数据
 19         //如果需要测试 ef 和 dapper 是否共用1个事务,需要将 unit 中的 Commit 方法中对 dapper.Commit 的代码注释掉,才能测试“EF SaveChanges 之后不对事务 Commit也无法插入数据”。
 20         unit.Commit();
 21 }

在Home/Index中调用该业务方法,运行一下首页进行测试。

可以发现结果与代码中注释的描述一致。

这个章节比较长,最后回顾一下本章节实现的内容:

1、引入 dapper,封装 dapper 上下文类

2、在工作单元和仓储中实现 dapper 上下文的使用,并实现:

(1)使 dapper 上下文能够按需初始化

(2)使 ef 和 dapper 仅在使用时才根据“事务标记”决定是否开启事务

(3)使 ef 和 dapper 可以共用1个事务

下一章节将演示,我还没想好。

时间: 2024-10-11 16:14:26

七色花基本权限系统(14)- 实现EntityFramework和Dapper的混搭的相关文章

七色花基本权限系统(3)- 利用EasyUI进行首页布局

EasyUI EasyUI是基于JQuery库的偏js轻型前端UI框架,不了解的读者可以参考官网地址. 在项目中增加JQuery和EasyUI,并在布局页中引用.为了结构清晰,方便日后维护和升级,可以在Scripts下创建jquery文件夹和jquery-easyui文件夹.这里选择1.11.0的JQuery和1.4.3的EasyUI. 特别说明一下,我已经修复了该版本的几个(只能通过修改源码来修正的)bug,并在updateLog.txt文件中做了修复记录. 图标库 再引入2套通用的图标库,以

七色花基本权限系统(4) - 初步使用EntityFramework实现用户登录

这篇日志将演示: 1.利用EF的Code First模式,自动创建数据库 2.实现简单的用户登录(不考虑安全仅仅是密码验证) 为什么选择EntityFramework 第一,开发常规中小型系统,能够提高开发效率. 针对小型系统,ORM提高开发效率那是立竿见影.而且linq+lambda的用户体验非常棒,让代码可读性大大增强.即使把linq写得很烂,小系统也无所谓. 针对中型系统,在对ORM有一定了解并且对linq to entity也掌握一定技巧的基础上,使用ORM还是可以提高开发效率. 第二,

七色花基本权限系统(6)- 让EntityFramework Code First自动合并/迁移/数据初始化

在前一章节里,我们已经能够对映射字段进行配置了.但在演示中,我们通过删除原数据库让EF重新创建的方式,才把新的字段信息更新(其实是破而后立)到了数据库.这显然无法让人接受.在这篇日志里,将演示"在实体类发生改变时如何自动更新数据库中的表结构"和"在EF创建数据库的时候如何初始化一批数据". 合并/迁移 合并是指"新的实体模型映射到数据库中,更新其结构",例如: 新增了实体类,那在数据库中就是新增数据表. 删除了实体类,那在数据库中就是删除数据表.

七色花基本权限系统(8)- 实现实体层和核心层解耦

经过前面的工作,系统正变得越来越清晰. 现在有一个问题需要解决.当需要额外增加一个数据表时,我们需要做的操作是: 在实体层创建实体并编译实体层 在核心层运行T4 配置实体 将实体对象关联给EF数据库上下文(定义DbSet) 将实体配置注册给EF配置对象 这过于繁琐,最后2个步骤,强行地把实体关联在EF数据库上下文里,导致了两者的耦合.这篇日志将演示如何将最后2个步骤省略,解放EF数据库上下文,不用手动关联实体对象,不用手动注册实体配置. 回顾和解释 最后两步还记得吗? 通过定义DbSet对象将实

七色花基本权限系统(10)- 数据接口的实现

为什么要设计数据接口 首先来看一下3层的主要逻辑:数据层 => 业务层 => 应用层.作为通用的项目模板,其中最可能按需而大变的就是数据层,因为不同的项目,使用的数据库.数据驱动技术,是很有可能不同的.项目A,MsSql+EF(就像我正在演示的),项目B,也用这套模板,但变成了MySql+ADO.NET,那么就要尽可能地维持项目的整洁,减少需要修改的代码的量和范围.最佳的做法自然就是"数据层暴露出接口,业务层不关心数据实现". 要设计哪些接口 凡是数据实现层要暴露给业务逻辑

七色花基本权限系统(5)- 实体配置的使用和利用T4自动生成实体配置

在前面的章节里,用户表的结构非常简单,没有控制如何映射到数据库.通常,需要对字段的长度.是否可为空甚至特定数据类型进行设置,因为EntityFramework的默认映射规则相对而言比较简单和通用.在这篇日志里,将演示如何对数据实体进行映射配置,并利用T4模板自动创建映射配置类文件. 配置方式 EntityFramework的实体映射配置有2种. 第一种是直接在实体类中以特性的方式进行控制,这些特性有部分是EF实现的,也有部分是非EF实现的.也就是说,在数据实体层不引用EF的情况下,只能使用不全的

七色花基本权限系统(13)- 业务层的设计和实现

解耦WebUI层与EntityFramework 在还未实现实体仓储时,登录功能是在控制器中直接初始化EF数据库上下文来实现的,这样也导致WebUI层必须引用EntityFramework.在完成数据层的设计和实现之后,控制器中不再直接使用EF数据库上下文对象,而是通过工作单元去调用实体仓储,其实到了这一步就可以让WebUI层不再依赖EntityFramework.从WebUI层中通过nuget管理的方式移除EF,但要注意的是,EF包含2个dll,其中的EntityFramework.SqlSe

七色花基本权限系统(2)- MVC项目搭建及初步调整

新建ASP.NET MVC项目,解决方案名称Seven,MVC项目名称S.Framework.WebClient.如下图: 创建MVC项时会让你选择身份验证方式,选择无吧,咱要空白干净的MVC项目.是否勾选单元测试随自己喜欢就好. 现在把MVC项目中我们不需要用到的部分移除掉,比如自带的bootstrap.jquery.jquery Validation等,可通过nuget工具来移除.如下图: (如果你没装nuget,请google) 请依次移除bootstrap.Microsoft jQuer

权限系统——初识

最近正在做高效平台中权限系统的项目,原来想的权限吗?简单,不就是判断一下这个用户存不存在,能看那些页面吗? 如果你是这么想的,那么我也只能说你应该好好的看看这篇文章,如果您对权限系统认识很深,那么就不需要再看了,因为这篇文章是帮助我自己理解的,当然也能帮助一下那些对权限系统一点认识也没有的朋友! 首先应该说说,什么是权限系统?它有什么作用? 权限管理系统,一般指根据系统设置的安全规则或者安全策略,用户可以访问而且只能访问自己被授权的资源,不多不少.权限管理系统几乎出现在任何系统里面,只要有用户和