七色花基本权限系统(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
除了暴露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
其中为了隔离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个事务
下一章节将演示,我还没想好。
截止本章节,项目源码下载:点击下载(存在百度云盘中)