利用AOP實現SqlSugar自動事務

先看一下效果,帶接口層的三層架構:git

BL層:github

public class StudentBL : IStudentService
      {
          private ILogger mLogger;
          private readonly IStudentDA mStudentDa;
          private readonly IValueService mValueService;

          public StudentService(IStudentDA studentDa,IValueService valueService)
          {
              mLogger = LogManager.GetCurrentClassLogger();
              mStudentDa = studentDa;
              mValueService = valueService;

          }

          [TransactionCallHandler]
          public IList<Student> GetStudentList(Hashtable paramsHash)
          {
              var list = mStudentDa.GetStudents(paramsHash);
              var value = mValueService.FindAll();
              return list;
          }
      }

假設GetStudentList方法裏的mStudentDa.GetStudents和mValueService.FindAll不是查詢操做,而是更新操做,當一個失敗另外一個須要回滾,就須要在同一個事務裏,當一個出現異常就要回滾事務。sql

特性TransactionCallHandler就代表當前方法須要開啓事務,而且當出現異常的時候回滾事務,方法執行完後提交事務。架構

DA層:ide

public class StudentDA : IStudentDA
     {

         private SqlSugarClient db;
         public StudentDA()
         {
             db = SugarManager.GetInstance().SqlSugarClient;
         }
         public IList<Student> GetStudents(Hashtable paramsHash)
         {
             return db.Queryable<Student>().AS("T_Student").With(SqlWith.NoLock).ToList();
         }
     }

SqlSugar作一下包裝

public class SugarManager
     {
         private static ConcurrentDictionary<string,SqlClient> _cache =
             new ConcurrentDictionary<string, SqlClient>();
         private static ThreadLocal<string> _threadLocal;
         private static readonly string _connStr = @"Data Source=localhost;port=3306;Initial Catalog=thy;user id=root;password=xxxxxx;Charset=utf8";
         static SugarManager()
         {
             _threadLocal = new ThreadLocal<string>();
         }

         private static SqlSugarClient CreatInstance()
         {
             SqlSugarClient client = new SqlSugarClient(new ConnectionConfig()
             {
                 ConnectionString = _connStr, //必填
                 DbType = DbType.MySql, //必填
                 IsAutoCloseConnection = true, //默認false
                 InitKeyType = InitKeyType.SystemTable
             });
             var key=Guid.NewGuid().ToString().Replace("-", "");
             if (!_cache.ContainsKey(key))
             {
                 _cache.TryAdd(key,new SqlClient(client));
                 _threadLocal.Value = key;
                 return client;
             }
             throw new Exception("建立SqlSugarClient失敗");
         }
         public static SqlClient GetInstance()
         {
             var id= _threadLocal.Value;
             if (string.IsNullOrEmpty(id)||!_cache.ContainsKey(id))
                 return new SqlClient(CreatInstance());
             return _cache[id];
         }


         public static void Release()
         {
             try
             {
                 var id = GetId();
                 if (!_cache.ContainsKey(id))
                     return;
                 Remove(id);
             }
             catch (Exception e)
             {
                 throw e;
             }
         }
         private static bool Remove(string id)
         {
             if (!_cache.ContainsKey(id)) return false;

             SqlClient client;

             int index = 0;
             bool result = false;
             while (!(result = _cache.TryRemove(id, out client)))
             {
                 index++;
                 Thread.Sleep(20);
                 if (index > 3) break;
             }
             return result;
         }
         private static string GetId()
         {
             var id = _threadLocal.Value;
             if (string.IsNullOrEmpty(id))
             {
                 throw new Exception("內部錯誤: SqlSugarClient已丟失.");
             }
             return id;
         }

         public static void BeginTran()
         {
             var instance=GetInstance();
             //開啓事務
             if (!instance.IsBeginTran)
             {
                 instance.SqlSugarClient.Ado.BeginTran();
                 instance.IsBeginTran = true;
             }
         }

         public static void CommitTran()
         {
             var id = GetId();
             if (!_cache.ContainsKey(id))
                 throw new Exception("內部錯誤: SqlSugarClient已丟失.");
             if (_cache[id].TranCount == 0)
             {
                 _cache[id].SqlSugarClient.Ado.CommitTran();
                 _cache[id].IsBeginTran = false;
             }
         }

         public static void RollbackTran()
         {
             var id = GetId();
             if (!_cache.ContainsKey(id))
                 throw new Exception("內部錯誤: SqlSugarClient已丟失.");
             _cache[id].SqlSugarClient.Ado.RollbackTran();
             _cache[id].IsBeginTran = false;
             _cache[id].TranCount = 0;
         }

         public static void TranCountAddOne()
         {
             var id = GetId();
             if (!_cache.ContainsKey(id))
                 throw new Exception("內部錯誤: SqlSugarClient已丟失.");
             _cache[id].TranCount++;
         }
         public static void TranCountMunisOne()
         {
             var id = GetId();
             if (!_cache.ContainsKey(id))
                 throw new Exception("內部錯誤: SqlSugarClient已丟失.");
             _cache[id].TranCount--;
         }
     }

_cache保存SqlSugar實例,_threadLocal確保同一線程下取出的是同一個SqlSugar實例。ui

不知道SqlSugar判斷當前實例是否已經開啓事務,因此又將SqlSugar包了一層。this

public class SqlClient
     {
         public SqlSugarClient SqlSugarClient;
         public bool IsBeginTran = false;
         public int TranCount = 0;

         public SqlClient(SqlSugarClient sqlSugarClient)
         {
             this.SqlSugarClient = sqlSugarClient;
         }
     }

IsBeginTran標識當前SqlSugar實例是否已經開啓事務,TranCount是一個避免事務嵌套的計數器。線程

一開始的例子代理

[TransactionCallHandler]
            public IList<Student> GetStudentList(Hashtable paramsHash)
            {
                var list = mStudentDa.GetStudents(paramsHash);
                var value = mValueService.FindAll();
                return list;
            }

TransactionCallHandler代表該方法要開啓事務,可是若是mValueService.FindAll也標識了TransactionCallHandler,又要開啓一次事務?因此用TranCount作一個計數。code

使用Castle.DynamicProxy

要實現標識了TransactionCallHandler的方法實現自動事務,使用Castle.DynamicProxy實現BL類的代理

Castle.DynamicProxy通常操做

public class MyClass : IMyClass
   {
       public void MyMethod()
       {
           Console.WriteLine("My Mehod");
       }
  }
  public class TestIntercept : IInterceptor
      {
          public void Intercept(IInvocation invocation)
          {
              Console.WriteLine("before");
              invocation.Proceed();
              Console.WriteLine("after");
          }
      }

   var proxyGenerate = new ProxyGenerator();
   TestIntercept t=new TestIntercept();
   var pg = proxyGenerate.CreateClassProxy<MyClass>(t);
   pg.MyMethod();
   //輸出是
   //before
   //My Mehod
   //after

before就是要開啓事務的地方,after就是提交事務的地方
最後實現

public class TransactionInterceptor : IInterceptor
      {
          private readonly ILogger logger;
          public TransactionInterceptor()
          {
              logger = LogManager.GetCurrentClassLogger();
          }
          public void Intercept(IInvocation invocation)
          {
              MethodInfo methodInfo = invocation.MethodInvocationTarget;
              if (methodInfo == null)
              {
                  methodInfo = invocation.Method;
              }

              TransactionCallHandlerAttribute transaction =
                  methodInfo.GetCustomAttributes<TransactionCallHandlerAttribute>(true).FirstOrDefault();
              if (transaction != null)
              {
                  SugarManager.BeginTran();
                  try
                  {
                      SugarManager.TranCountAddOne();
                      invocation.Proceed();
                      SugarManager.TranCountMunisOne();
                      SugarManager.CommitTran();
                  }
                  catch (Exception e)
                  {
                      SugarManager.RollbackTran();
                      logger.Error(e);
                      throw e;
                  }

              }
              else
              {
                  invocation.Proceed();
              }
          }
      }
[AttributeUsage(AttributeTargets.Method, Inherited = true)]
     public class TransactionCallHandlerAttribute : Attribute
     {
         public TransactionCallHandlerAttribute()
         {

         }
     }

Autofac與Castle.DynamicProxy結合使用

建立代理的時候一個BL類就要一次操做

proxyGenerate.CreateClassProxy<MyClass>(t);

並且項目裏BL類的實例化是交給IOC容器控制的,我用的是Autofac。固然Autofac和Castle.DynamicProxy是能夠結合使用的

using System.Reflection;
using Autofac;
using Autofac.Extras.DynamicProxy;
using Module = Autofac.Module;
public class BusinessModule : Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            var business = Assembly.Load("FTY.Business");
            builder.RegisterAssemblyTypes(business)
                .AsImplementedInterfaces().InterceptedBy(typeof(TransactionInterceptor)).EnableInterfaceInterceptors();
            builder.RegisterType<TransactionInterceptor>();
        }
    }
相關文章
相關標籤/搜索