1. Preface
Recent new projects in the company need to be developed with architectures, which need to ensure transaction consistency. After searching, we found that many of the posts are AOP implemented through Spring.Net, Unity, PostSharp, Castle Windsor.But that's not what I want, so after a search, use Autofac, DynamicProxy to implement AOP.
2. Advantages of using AOP
Bloggers find its advantages mainly in:
- By pulling common functionality out of business logic, you can omit a lot of duplicate code, which is beneficial to the operation and maintenance of code.
- In software design, extract common functions (facets) to facilitate the modularization of software design and reduce the complexity of software architecture.That is to say, a common function is a separate module, the design code of which can not be seen in the main business of the project.
3. Reference Library
- Autofac: 4.6
- Autofac.Extras.DynamicProxy: 4.1.0
- Castle.Core: 3.2.2
4. Ideas for implementation
4.1 Defining attributes
Defines an attribute that is included in the current method to determine whether an open transaction is open, and if it exists, the transaction is opened, otherwise the transaction is ignored.
Transaction properties can set timeout, transaction scope, and transaction isolation level.
The code is as follows:
/// <summary> ///Open Transaction Properties /// </summary> [AttributeUsage(AttributeTargets.Method,Inherited = true)] public class TransactionCallHandlerAttribute:Attribute { /// <summary> ///Timeout /// </summary> public int Timeout { get; set; } /// <summary> ///Transaction Scope /// </summary> public TransactionScopeOption ScopeOption { get; set; } /// <summary> ///Transaction isolation level /// </summary> public IsolationLevel IsolationLevel { get; set; } public TransactionCallHandlerAttribute() { Timeout = 60; ScopeOption=TransactionScopeOption.Required; IsolationLevel=IsolationLevel.ReadCommitted; } }
4.2 Face Realization
Gets whether the current method contains the TransactionCallHandlerAttribute property, and if it does, opens the transaction.
I add the development mode judgment here to solve the problem that MSDTC is not set up to cause exceptions if it is not needed.
In addition, the logging function can be implemented by itself.
The code is as follows:
/// <summary> ///Transaction Interceptor /// </summary> public class TransactionInterceptor:IInterceptor { //Self-implemented logger, ignored here /// <summary> ///Logger /// </summary> private static readonly ILog Logger = Log.GetLog(typeof(TransactionInterceptor)); // Is development mode or not private bool isDev = false; public void Intercept(IInvocation invocation) { if (!isDev) { MethodInfo methodInfo = invocation.MethodInvocationTarget; if (methodInfo == null) { methodInfo = invocation.Method; } TransactionCallHandlerAttribute transaction = methodInfo.GetCustomAttributes<TransactionCallHandlerAttribute>(true).FirstOrDefault(); if (transaction != null) { TransactionOptions transactionOptions = new TransactionOptions(); //Set Transaction Isolation Level transactionOptions.IsolationLevel = transaction.IsolationLevel; //Set transaction timeout to 60 seconds transactionOptions.Timeout = new TimeSpan(0, 0, transaction.Timeout); using (TransactionScope scope = new TransactionScope(transaction.ScopeOption, transactionOptions)) { try { //Implement transactional work invocation.Proceed(); scope.Complete(); } catch (Exception ex) { // Record exceptions throw ex; } finally { //Release Resources scope.Dispose(); } } } else { // Execute method directly without transaction invocation.Proceed(); } } else { // Development mode skips interception directly invocation.Proceed(); } } }
4.3 Section Injection
The blogger encapsulated Autofac, which may not be the same as your configuration, but the content of the Load(ContainerBuilder builder) method is consistent, so the injection method is consistent.
By defining an IDependency empty interface, classes that need to be injected can inherit that interface.
The code is as follows:
/// <summary> ///Application IOC Configuration /// </summary> public class IocConfig : ConfigBase { // Override Load Configuration protected override void Load(ContainerBuilder builder) { var assembly = this.GetType().GetTypeInfo().Assembly; builder.RegisterType<TransactionInterceptor>(); builder.RegisterAssemblyTypes(assembly) .Where(type => typeof(IDependency).IsAssignableFrom(type) && !type.GetTypeInfo().IsAbstract) .AsImplementedInterfaces() .InstancePerLifetimeScope() .EnableInterfaceInterceptors() .InterceptedBy(typeof(TransactionInterceptor)); } }
5. Examples
/// <summary> ///Add Article /// </summary> /// <param name="name"></param> [TransactionCallHandler] public void AddArticle(string name) { BasArticle model=new BasArticle(); model.ArticleID = Guid.Empty;//Deliberately repeat to determine if rollback will occur. model.Code = TimestampId.GetInstance().GetId(); model.Name = name; model.Status = 1; model.Creater = "test"; model.Editor = "test"; this._basArticleRepository.Insert(model); }