七色花基本權限系統(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源碼更好。

image

 

數據核心層

在數據核心層(S.Framework.DataCore)創建Dapper上下文實現類,使核心層支持Dapper,創建結構如下:

image

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個事務

 

下一章節將演示,我還沒想好。

 

截止本章節,項目源碼下載:點擊下載(存在百度雲盤中)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM