• 七色花基本权限系统(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个事务

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

    截止本章节,项目源码下载:点击下载(存在百度云盘中)

  • 相关阅读:
    vue cli选择lint模式
    收集Java8 Lambda mapreduce代码
    k8s笔记
    Deep Reinforcement Learning with Double Qlearning
    Deep Spiking Neural Networks With Binary Weights for Object Recognition
    Implementing Spiking Neural Networks on Neuromorphic Architectures: A Review
    BPSTDP: Approximating Backpropagation using Spike Timing Dependent Plasticity
    Deep Spiking Neural Network model for timevariant signals classification: a realtime speech recognition approach
    SpikingYOLO: Spiking Neural Network for EnergyEfficient Object Detection
    政策、效果、请求、匹配 PERM (Policy, Effect, Request, Matcher)
  • 原文地址:https://www.cnblogs.com/matrixkey/p/5668030.html
Copyright © 2020-2023  润新知