هدف ارائه راه حلی برای مدیریت Transactionها به عنوان یک Cross Cutting Concern، توسط ApplicationServiceها میباشد.
- دوره Aspect oriented programming
- بررسی مفاهیم معکوس سازی وابستگیها و ابزارهای مرتبط با آن
- طراحی و پیاده سازی ServiceLayer به همراه خودکارسازی Business Validationها
پیش فرض ما این است که شما از EF به عنوان OR-Mapper استفاده میکنید و الگوی Context Per Request را پیاده سازی کرده اید یا از طریق پیاده سازی الگوی Container Per Request به داشتن Context یکتا برای هر درخواست رسیده اید.
کتابخانه StructureMap.Mvc5 پیاده سازی از الگوی Container Per Request را با استفاده از امکانات Nested Container مربوط به StructureMap ارائه میدهد. اشیاء موجود در Nested Container طول عمر Singleton دارند.
واسط ITransaction
public interface ITransaction : IDisposable { void Commit(); void Rollback(); }
واسط بالا 3 متد را که برای مدیریت تراکنش لازم میباشد، در اختیار استفاده کننده قرار میدهد.
واسط IUnitOfWork
public interface IUnitOfWork : IDisposable { ... ITransaction BeginTransaction(IsolationLevel isolationLevel = IsolationLevel.Snapshot); ITransaction Transaction { get; } IDbConnection Connection { get; } bool HasTransaction { get; } }
اعضای جدید واسط IUnitOfWork کاملا مشخص هستند.
پیاده سازی واسط ITransaction، توسط یک Nested Type در دل کلاس DbContextBase انجام میگیرد.
public abstract class DbContextBase : DbContext { ... #region Fields private ITransaction _currenTransaction; #endregion #region NestedTypes private class DbContextTransactionAdapter : ITransaction { private DbContextTransaction _transaction; public DbContextTransactionAdapter(DbContextTransaction transaction) { Guard.NotNull(transaction, nameof(transaction)); _transaction = transaction; } public void Commit() { _transaction?.Commit(); } public void Rollback() { if (_transaction?.UnderlyingTransaction.Connection != null) _transaction.Rollback(); } public void Dispose() { _transaction?.Dispose(); _transaction = null; } } #endregion #region Public Methods ... public ITransaction BeginTransaction(IsolationLevel isolationLevel = IsolationLevel.ReadCommitted) { if (_currenTransaction != null) return _currenTransaction; return _currenTransaction = new DbContextTransactionAdapter(Database.BeginTransaction(isolationLevel)); } #endregion #region Properties ... public ITransaction Transaction => _currenTransaction; public IDbConnection Connection => Database.Connection; public bool HasTransaction => _currenTransaction != null; #endregion } public class ApplicationDbContext : DbContextBase, IUnitOfWork, ITransientDependency { }
کلاس DbContextTransactionAdapter همانطور که از نام آن مشخص میباشد، پیاده سازی از الگوی Adapter برای وفق دادن DbContextTransaction با واسط ITransaction، میباشد. متد BeginTransaction در صورتی که تراکنشی برای وهله جاری DbContext ایجاد نشده باشد، تراکنشی را ایجاد کرده و فیلد currentTransaction_ را نیز مقدار دهی میکند.
TransactionalAttribute
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] public sealed class TransactionalAttribute : Attribute { public IsolationLevel IsolationLevel { get; set; } = IsolationLevel.ReadCommitted; public TimeSpan? Timeout { get; set; } }
TransactionInterceptor
public class TransactionInterceptor : ISyncInterceptionBehavior { private readonly IUnitOfWork _unitOfWork; public TransactionInterceptor(IUnitOfWork unitOfWork) { _unitOfWork = unitOfWork; } public IMethodInvocationResult Intercept(ISyncMethodInvocation methodInvocation) { var transactionAttribute = GetTransactionaAttributeOrNull(methodInvocation.InstanceMethodInfo); if (transactionAttribute == null || _unitOfWork.HasTransaction) return methodInvocation.InvokeNext(); using (var transaction = _unitOfWork.BeginTransaction(transactionAttribute.IsolationLevel)) { var result = methodInvocation.InvokeNext(); if (result.Successful) transaction.Commit(); else { transaction.Rollback(); } return result; } } private static TransactionalAttribute GetTransactionaAttributeOrNull(MemberInfo methodInfo) { var transactionalAttribute = ReflectionHelper.GetAttributesOfMemberAndDeclaringType<TransactionalAttribute>( methodInfo ).FirstOrDefault(); return transactionalAttribute; } }
واسط ISyncInterceptionBehavior، مربوط میشود به کتابخانه جانبی دیگری که برای AOP توسط تیم StructureMap به نام StructureMap.DynamicInterception ارائه شدهاست. در متد Intercept، ابتدا چک میشود که که آیا این متد با TransactionAttribute تزئین شده و طی درخواست جاری برای Context جاری تراکنشی ایجاد نشده باشد؛ سپس تراکنش جدیدی ایجاد شده و بدنه اصلی متد اجرا میشود و نهایتا در صورت موفقیت آمیز بودن عملیات، تراکنش مورد نظر Commit میشود.
در آخر لازم است این Interceptor در تنظیمات اولیه StructureMap به شکل زیر معرفی شود:
Policies.Interceptors(new DynamicProxyInterceptorPolicy( type => typeof(IApplicationService).IsAssignableFrom(type), typeof(AuthorizationInterceptor), typeof(TransactionInterceptor), typeof(ValidationInterceptor)));
نکته: فرض کنید در بدنه اکشن متد یک کنترلر ASP.NET MVC یا ASP.NET Core، دو متد تراکنشی فراخوانی شود؛ در این صورت شاید لازم باشد که این دو متد طی یک تراکنش واحد به جای تراکنشهای مجزا، اجرا شوند؛ بنابراین نیاز است از الگوی Transaction Per Request استفاده شود. برای این کار میتوان یک ActionFilterAttribute سفارشی ایجاد کرد که ایجاد کننده تراکنش باشد و متدهای داخلی که هر کدام جدا تراکنشی بودند، نیز از تراکنش ایجاد شده استفاده کنند.