zoukankan      html  css  js  c++  java
  • 七色花基本权限系统(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个事务

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

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

  • 相关阅读:
    linux查看CPU和内存信息
    linux yum命令详解
    查看文件中关键字前后几行的内容
    vue.js+web storm安装及第一个vue.js
    android GPS: code should explicitly check to see if permission is available
    ASP.NET MVC Identity 使用自己的SQL Server数据库
    阿里云服务器,tomcat启动,一直卡在At least one JAR was scanned for TLDs yet contained no TLDs就不动了
    ASP.NET MVC4 MVC 当前上下文中不存在名称“Scripts”
    python 将windows字体中的汉字生成图片的方法
    Java android DES+Base64加密解密
  • 原文地址:https://www.cnblogs.com/matrixkey/p/5668030.html
Copyright © 2011-2022 走看看