利用AOP实现SqlSugar自动事务

1,044 阅读3分钟

先看一下效果,带接口层的三层架构:

BL层:

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不是查询操作,而是更新操作,当一个失败另一个需要回滚,就需要在同一个事务里,当一个出现异常就要回滚事务。

特性TransactionCallHandler就表明当前方法需要开启事务,并且当出现异常的时候回滚事务,方法执行完后提交事务。

DA层:

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实例。

不知道SqlSugar判断当前实例是否已经开启事务,所以又将SqlSugar包了一层。

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做一个计数。

使用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>();
        }
    }