net core天馬行空系列: 泛型倉儲和聲明式事物實現最優雅的crud操作

  • 2019 年 10 月 3 日
  • 筆記

系列目錄

1.net core天馬行空系列:原生DI+AOP實現spring boot註解式編程

        哈哈哈哈,大家好,我就是那個高產似母豬的三合,長久以來,我一直在思考,如何才能實現高效而簡潔的倉儲模式(不是DDD里的倉儲,更準確的說就是資料庫表的mapper),實現spring boot里那樣利用註解實現事物操作,日有所思,終有所得,本篇文章濃縮了我對於倉儲模式和工作單元模式理解的精華,希望能對大家有所幫助,如果哪裡說錯了,也希望大家不吝賜教。由於ef core本身就實現了這2種模式,再在ef core的基礎上進行封裝就失去了學習的意義,所以本文用到的是ORM方案是dapper+dapper.contrib, 這2個庫皆出自名門stackexchange,也就是大名鼎鼎的爆棧啦,他們出品的庫還有StackExchange.Redis,所以品質自不用說,開始正文前,先在nuget上安裝這2個庫。BTW,動態代理,註解式編程,AOP貫穿本系列始終,no bb,正文開始。

1.定義用到的類

上次講飆車,這次我們講,去加油站加油,加油這個過程呢,存在一個事物操作,那就是,加油站必須給我加足夠的油,我才給他付錢,有點像銀行轉賬,那麼引申出2張表,汽車油量表(oilQuantity)和現金餘額表(cashBalance),對應的表結構和實體類如下,都比較簡單,除了主鍵id,oilQuantity表只有一個油量quantity欄位,cashBalance表只有一個餘額balance欄位,資料庫使用的是mysql,實體類的註解TableAttribute使用的命名空間是System.ComponentModel.DataAnnotations.Schema。

CREATE TABLE test.oilQuantity (      id INT NOT NULL AUTO_INCREMENT,      quantity DECIMAL NULL,      CONSTRAINT caroil_pk PRIMARY KEY (id)  )  ENGINE=InnoDB  DEFAULT CHARSET=utf8  COLLATE=utf8_general_ci;

CREATE TABLE test.cashBalance (      id INT NOT NULL AUTO_INCREMENT,      balance DECIMAL NOT NULL,      CONSTRAINT cashbalance_pk PRIMARY KEY (id)  )  ENGINE=InnoDB  DEFAULT CHARSET=utf8  COLLATE=utf8_general_ci;

 

 [Table("OilQuantity")]      public class OilQuantity      {          [Key]          public int Id { set; get; }          /// <summary>          /// 油量          /// </summary>          public decimal Quantity { set; get; }      }

 [Table("CashBalance")]      public class CashBalance      {          [Key]          public int Id { set; get; }          /// <summary>          /// 餘額          /// </summary>          public decimal Balance { set; get; }      }

定義資料庫鏈接工廠類介面IDbFactory和他的實現類DbFactory,這個類主要負責資料庫鏈接的創建,鏈接分為2種,一種是短鏈接,不開啟事物的時候使用,用完即毀,每次獲得都是全新的鏈接,另一種是長鏈接,用在事物操作中,DbFactory本身註冊為scope級別,長鏈接創建後會保存在DbFactory的屬性中,所以變相的實現了scope級別,同理,長鏈接的事務開啟後也會被保存,用來在倉儲中實現事物操作。

 public interface IDbFactory:IDisposable      {          /// <summary>          /// 長鏈接          /// </summary>          IDbConnection LongDbConnection { get; }            /// <summary>          /// 長鏈接的事物          /// </summary>          IDbTransaction LongDbTransaction { get; }            /// <summary>          /// 短鏈接          /// </summary>          IDbConnection ShortDbConnection { get; }            /// <summary>          /// 開啟事務          /// </summary>          void BeginTransaction();      }

  /// <summary>      /// 負責生成和銷毀資料庫鏈接      /// </summary>      public class DbFactory:IDbFactory      {          [Value("MysqlConnectionStr")]          public string MysqlConnectionStr { set; get; }            /// <summary>          /// 長連接          /// </summary>          public IDbConnection LongDbConnection { private set; get; }            /// <summary>          /// 長連接的事物          /// </summary>          public IDbTransaction LongDbTransaction { private set; get; }            /// <summary>          /// 短鏈接          /// </summary>          public IDbConnection ShortDbConnection          {              get              {                  var dbConnection = new MySqlConnection(MysqlConnectionStr);                  dbConnection.Open();                  return dbConnection;              }          }            /// <summary>          /// 開啟事務          /// </summary>          /// <returns></returns>          public void BeginTransaction()          {              if (LongDbConnection == null)              {                  LongDbConnection = new MySqlConnection(MysqlConnectionStr);                  LongDbConnection.Open();                  LongDbTransaction = LongDbConnection.BeginTransaction();              }          }            public void Dispose()          {              LongDbTransaction?.Dispose();              if (LongDbConnection?.State != ConnectionState.Closed)              {                  LongDbConnection?.Close();              }              LongDbConnection?.Dispose();              LongDbTransaction = null;              LongDbConnection = null;          }      }

 定義工作單元介面IUnitOfWork和他的實現類UnitOfWork,可以看到,IUnitOfWork中有一個引用次數ActiveNumber的屬性,這個屬性的作用主要是,如果一個標註了[Transactional]的方法里嵌套了另一個標註了[Transactional]的方法,我們就可以通過計數來確認,具體誰才是最外層的方法,從而達到不在內層方法里開啟另一個事物,並且在內層方法結束時不會提前提交事務的效果。同時呢,UnitOfWork只負責與事務有關的操作,其他創建鏈接,創建事物等操作,都是由注入的IDbFactory完成的。

 public interface IUnitOfWork : IDisposable      {          /// <summary>          /// 引用次數,開啟一次事物加+1,當次數為0時提交,主要是為了防止事物嵌套          /// </summary>          int ActiveNumber { get; set; }            /// <summary>          /// 開啟事務          /// </summary>          void BeginTransaction();            /// <summary>          /// 提交          /// </summary>          void Commit();            /// <summary>          /// 事物回滾          /// </summary>          void RollBack();      }

 public class UnitOfWork : IUnitOfWork      {          /// <summary>          /// 工作單元引用次數,當次數為0時提交,主要為了防止事物嵌套          /// </summary>          public int ActiveNumber { get; set; } = 0;            [Autowired]          public IDbFactory DbFactory { set; get; }            public void BeginTransaction()          {              if (this.ActiveNumber == 0)              {                  DbFactory.BeginTransaction();                  Console.WriteLine("開啟事務");              }              this.ActiveNumber++;          }            public void Commit()          {              this.ActiveNumber--;              if (this.ActiveNumber == 0)              {                  if (DbFactory.LongDbConnection != null)                  {                      try                      {                          DbFactory.LongDbTransaction.Commit();                      }                      catch (Exception e)                      {                          DbFactory.LongDbTransaction.Rollback();                          Console.WriteLine(e);                          throw;                      }                      finally                      {                          this.Dispose();                      }                  }                    Console.WriteLine("提交事務");              }            }            public void Dispose()          {              DbFactory.Dispose();          }            public void RollBack()          {              if (this.ActiveNumber > 0 && DbFactory.LongDbTransaction != null)              {                  try                  {                      DbFactory.LongDbTransaction.Rollback();                  }                  catch (Exception e)                  {                      Console.WriteLine(e);                      throw;                  }              }                Console.WriteLine("回滾事務");          }      }

泛型倉儲介面IRepository<T>和他的實現類BaseRepository<T>,為了偷懶,只寫了同步部分,非同步同理,若使用非同步,攔截器也要使用非同步攔截器。BaseRepository中通過屬性注入了IUnitOfWork和IDbFactory,IUnitOfWork主要負責告訴倉儲,該使用長連接還是短鏈接,IDbFactory負責提供具體的鏈接和事物,而更細節的crud操作,則都是由dapper和dapper.contrib完成的。看程式碼var dbcon = Uow.ActiveNumber == 0 ? DbFactory.ShortDbConnection : DbFactory.LongDbConnection;可以看到通過判斷uow的引用計數ActiveNumber 來判斷使用的是長鏈接還是短鏈接,並且如果ActiveNumber==0的話,在資料庫操作結束後就會釋放掉鏈接。

 public interface IRepository<T> where T : class      {          IList<T> GetAll();          T Get(object id);            T Insert(T t);          IList<T> Insert(IList<T> t);            void Update(T t);          void Update(IList<T> t);            void Delete(IList<T> t);          void Delete(T t);      }

 public class BaseRepository<T> : IRepository<T> where T : class      {          [Autowired]          public IUnitOfWork Uow { set; get; }            [Autowired]          public IDbFactory DbFactory { set; get; }            public IList<T> GetAll()          {              var dbcon = DbFactory.ShortDbConnection;                var result = dbcon.GetAll<T>().ToList();                dbcon.Close();              dbcon.Dispose();                return result;          }            public T Get(object id)          {              var dbcon = DbFactory.ShortDbConnection;                var result = dbcon.Get<T>(id);                dbcon.Close();              dbcon.Dispose();                return result;          }            public T Insert(T t)          {              var dbcon = Uow.ActiveNumber == 0 ? DbFactory.ShortDbConnection : DbFactory.LongDbConnection;              dbcon.Insert(t, DbFactory.LongDbTransaction);                if (Uow.ActiveNumber == 0)              {                  dbcon.Close();                  dbcon.Dispose();              }                return t;          }            public IList<T> Insert(IList<T> t)          {              var dbcon = Uow.ActiveNumber == 0 ? DbFactory.ShortDbConnection : DbFactory.LongDbConnection;              dbcon.Insert(t, DbFactory.LongDbTransaction);                if (Uow.ActiveNumber == 0)              {                  dbcon.Close();                  dbcon.Dispose();              }                return t;          }            public void Delete(T t)          {              var dbcon = Uow.ActiveNumber == 0 ? DbFactory.ShortDbConnection : DbFactory.LongDbConnection;              dbcon.Delete(t, DbFactory.LongDbTransaction);                if (Uow.ActiveNumber == 0)              {                  dbcon.Close();                  dbcon.Dispose();              }            }            public void Delete(IList<T> t)          {              var dbcon = Uow.ActiveNumber == 0 ? DbFactory.ShortDbConnection : DbFactory.LongDbConnection;              dbcon.Delete(t, DbFactory.LongDbTransaction);                if (Uow.ActiveNumber == 0)              {                  dbcon.Close();                  dbcon.Dispose();              }            }            public void Update(T t)          {              var dbcon = Uow.ActiveNumber == 0 ? DbFactory.ShortDbConnection : DbFactory.LongDbConnection;              dbcon.Update(t, DbFactory.LongDbTransaction);                if (Uow.ActiveNumber == 0)              {                  dbcon.Close();                  dbcon.Dispose();              }            }            public void Update(IList<T> t)          {              var dbcon = Uow.ActiveNumber == 0 ? DbFactory.ShortDbConnection : DbFactory.LongDbConnection;              dbcon.Update(t, DbFactory.LongDbTransaction);              if (Uow.ActiveNumber == 0)              {                  dbcon.Close();                  dbcon.Dispose();              }            }      }

View Code

事物攔截器TransactionalInterceptor,在方法開始前,如果攔截到的方法具有[TransactionalAttribute]註解,則通過uow開啟事務,在方法結束後,如果攔截到的方法具有[TransactionalAttribute]註解,則通過uow結束事務。

/// <summary>      /// 事物攔截器      /// </summary>      public class TransactionalInterceptor : StandardInterceptor      {          private IUnitOfWork Uow { set; get; }            public TransactionalInterceptor(IUnitOfWork uow)          {              Uow = uow;          }            protected override void PreProceed(IInvocation invocation)          {              Console.WriteLine("{0}攔截前", invocation.Method.Name);                var method = invocation.MethodInvocationTarget;              if (method != null && method.GetCustomAttribute<TransactionalAttribute>() != null)              {                  Uow.BeginTransaction();              }          }            protected override void PerformProceed(IInvocation invocation)          {              invocation.Proceed();          }            protected override void PostProceed(IInvocation invocation)          {              Console.WriteLine("{0}攔截後, 返回值是{1}", invocation.Method.Name, invocation.ReturnValue);                var method = invocation.MethodInvocationTarget;              if (method != null && method.GetCustomAttribute<TransactionalAttribute>() != null)              {                  Uow.Commit();              }          }      }

IServiceCollection靜態擴展類SummerBootExtentions.cs,和上一篇比較,主要就是添加了AddSbRepositoryService方法,這個方法主要通過反射獲得由[TableAttribute]標註的實體類,並向IServiceCollection中添加相應的的倉儲介面和相應的倉儲實現類,為什麼不用services.AddScoped(typeof(IRepository<>),typeof(BaseRepository<>));這種方法注入泛型倉儲呢?因為net core原生DI並不支援泛型注入的工廠委託創建,那麼就無法實現動態代理了,所以採用變通的方法,將通用泛型介面,轉成具體的泛型介面,SummerBootExtentions.cs的另一個變動就是將ProxyGenerator註冊成單例了,這樣就可以利用快取,提高創建動態代理的性能,SummerBootExtentions.cs程式碼如下:

  public static class SummerBootExtentions      {          /// <summary>          /// 瞬時          /// </summary>          /// <typeparam name="TService"></typeparam>          /// <typeparam name="TImplementation"></typeparam>          /// <param name="services"></param>          /// <param name="interceptorTypes"></param>          /// <returns></returns>          public static IServiceCollection AddSbTransient<TService, TImplementation>(this IServiceCollection services, params Type[] interceptorTypes)          {              return services.AddSbService(typeof(TService), typeof(TImplementation), ServiceLifetime.Transient, interceptorTypes);          }            /// <summary>          /// 瞬時          /// </summary>          /// <param name="services"></param>          /// <param name="serviceType"></param>          /// <param name="implementationType"></param>          /// <param name="interceptorTypes"></param>          /// <returns></returns>          public static IServiceCollection AddSbTransient(this IServiceCollection services, Type serviceType,              Type implementationType, params Type[] interceptorTypes)          {              return services.AddSbService(serviceType, implementationType, ServiceLifetime.Transient, interceptorTypes);          }            /// <summary>          /// 請求級別          /// </summary>          /// <typeparam name="TService"></typeparam>          /// <typeparam name="TImplementation"></typeparam>          /// <param name="services"></param>          /// <param name="interceptorTypes"></param>          /// <returns></returns>          public static IServiceCollection AddSbScoped<TService, TImplementation>(this IServiceCollection services, params Type[] interceptorTypes)          {              return services.AddSbService(typeof(TService), typeof(TImplementation), ServiceLifetime.Scoped, interceptorTypes);          }            /// <summary>          /// 請求級別          /// </summary>          /// <param name="services"></param>          /// <param name="serviceType"></param>          /// <param name="implementationType"></param>          /// <param name="interceptorTypes"></param>          /// <returns></returns>          public static IServiceCollection AddSbScoped(this IServiceCollection services, Type serviceType,              Type implementationType, params Type[] interceptorTypes)          {              return services.AddSbService(serviceType, implementationType, ServiceLifetime.Scoped, interceptorTypes);          }            /// <summary>          /// 單例          /// </summary>          /// <typeparam name="TService"></typeparam>          /// <typeparam name="TImplementation"></typeparam>          /// <param name="services"></param>          /// <param name="interceptorTypes"></param>          /// <returns></returns>          public static IServiceCollection AddSbSingleton<TService, TImplementation>(this IServiceCollection services, params Type[] interceptorTypes)          {              return services.AddSbService(typeof(TService), typeof(TImplementation), ServiceLifetime.Singleton, interceptorTypes);          }            /// <summary>          /// 單例          /// </summary>          /// <param name="services"></param>          /// <param name="serviceType"></param>          /// <param name="implementationType"></param>          /// <param name="interceptorTypes"></param>          /// <returns></returns>          public static IServiceCollection AddSbSingleton(this IServiceCollection services, Type serviceType,              Type implementationType, params Type[] interceptorTypes)          {              return services.AddSbService(serviceType, implementationType, ServiceLifetime.Singleton, interceptorTypes);          }            public static IServiceCollection AddSbService(this IServiceCollection services, Type serviceType, Type implementationType,              ServiceLifetime lifetime, params Type[] interceptorTypes)          {              services.Add(new ServiceDescriptor(implementationType, implementationType, lifetime));                object Factory(IServiceProvider provider)              {                  var target = provider.GetService(implementationType);                  var properties = implementationType.GetTypeInfo().DeclaredProperties;                    foreach (PropertyInfo info in properties)                  {                      //屬性注入                      if (info.GetCustomAttribute<AutowiredAttribute>() != null)                      {                          var propertyType = info.PropertyType;                          var impl = provider.GetService(propertyType);                          if (impl != null)                          {                              info.SetValue(target, impl);                          }                      }                        //配置值注入                      if (info.GetCustomAttribute<ValueAttribute>() is ValueAttribute valueAttribute)                      {                          var value = valueAttribute.Value;                          if (provider.GetService(typeof(IConfiguration)) is IConfiguration configService)                          {                              var pathValue = configService.GetSection(value).Value;                              if (pathValue != null)                              {                                  var pathV = Convert.ChangeType(pathValue, info.PropertyType);                                  info.SetValue(target, pathV);                              }                          }                        }                  }                    List<IInterceptor> interceptors = interceptorTypes.ToList()                      .ConvertAll<IInterceptor>(interceptorType => provider.GetService(interceptorType) as IInterceptor);                    var proxyGenerator = provider.GetService<ProxyGenerator>();                  var proxy = proxyGenerator.CreateInterfaceProxyWithTarget(serviceType, target, interceptors.ToArray());                    return proxy;              };                var serviceDescriptor = new ServiceDescriptor(serviceType, Factory, lifetime);              services.Add(serviceDescriptor);                return services;          }            /// <summary>          /// 瞬時          /// </summary>          /// <typeparam name="TService"></typeparam>          /// <param name="services"></param>          /// <param name="interceptorTypes"></param>          /// <returns></returns>          public static IServiceCollection AddSbTransient<TService>(this IServiceCollection services, params Type[] interceptorTypes)          {              return services.AddSbService(typeof(TService), ServiceLifetime.Transient, interceptorTypes);          }            /// <summary>          /// 瞬時          /// </summary>          /// <param name="services"></param>          /// <param name="serviceType"></param>          /// <param name="interceptorTypes"></param>          /// <returns></returns>          public static IServiceCollection AddSbTransient(this IServiceCollection services, Type serviceType, params Type[] interceptorTypes)          {              return services.AddSbService(serviceType, ServiceLifetime.Transient, interceptorTypes);          }            /// <summary>          /// 請求          /// </summary>          /// <typeparam name="TService"></typeparam>          /// <param name="services"></param>          /// <param name="interceptorTypes"></param>          /// <returns></returns>          public static IServiceCollection AddSbScoped<TService>(this IServiceCollection services, params Type[] interceptorTypes)          {              return services.AddSbService(typeof(TService), ServiceLifetime.Scoped, interceptorTypes);          }            /// <summary>          /// 請求          /// </summary>          /// <param name="services"></param>          /// <param name="serviceType"></param>          /// <param name="interceptorTypes"></param>          /// <returns></returns>          public static IServiceCollection AddSbScoped(this IServiceCollection services, Type serviceType,               params Type[] interceptorTypes)          {              return services.AddSbService(serviceType, ServiceLifetime.Scoped, interceptorTypes);          }            /// <summary>          /// 單例          /// </summary>          /// <typeparam name="TService"></typeparam>          /// <param name="services"></param>          /// <param name="interceptorTypes"></param>          /// <returns></returns>          public static IServiceCollection AddSbSingleton<TService>(this IServiceCollection services, params Type[] interceptorTypes)          {              return services.AddSbService(typeof(TService), ServiceLifetime.Singleton, interceptorTypes);          }            /// <summary>          /// 單例          /// </summary>          /// <param name="services"></param>          /// <param name="serviceType"></param>          /// <param name="interceptorTypes"></param>          /// <returns></returns>          public static IServiceCollection AddSbSingleton(this IServiceCollection services, Type serviceType, params Type[] interceptorTypes)          {              return services.AddSbService(serviceType, ServiceLifetime.Singleton, interceptorTypes);          }            public static IServiceCollection AddSbService(this IServiceCollection services, Type serviceType,              ServiceLifetime lifetime, params Type[] interceptorTypes)          {              if (services == null)                  throw new ArgumentNullException(nameof(services));              if (serviceType == (Type)null)                  throw new ArgumentNullException(nameof(serviceType));                object Factory(IServiceProvider provider)              {                  List<IInterceptor> interceptors = interceptorTypes.ToList()                      .ConvertAll<IInterceptor>(interceptorType => provider.GetService(interceptorType) as IInterceptor);                    var proxyGenerator = provider.GetService<ProxyGenerator>();                    var proxy = proxyGenerator.CreateClassProxy(serviceType, interceptors.ToArray());                    var properties = serviceType.GetTypeInfo().DeclaredProperties;                    foreach (PropertyInfo info in properties)                  {                      //屬性注入                      if (info.GetCustomAttribute<AutowiredAttribute>() != null)                      {                          var propertyType = info.PropertyType;                          var impl = provider.GetService(propertyType);                          if (impl != null)                          {                              info.SetValue(proxy, impl);                          }                      }                        //配置值注入                      if (info.GetCustomAttribute<ValueAttribute>() is ValueAttribute valueAttribute)                      {                          var value = valueAttribute.Value;                          if (provider.GetService(typeof(IConfiguration)) is IConfiguration configService)                          {                              var pathValue = configService.GetSection(value).Value;                              if (pathValue != null)                              {                                  var pathV = Convert.ChangeType(pathValue, info.PropertyType);                                  info.SetValue(proxy, pathV);                              }                          }                      }                  }                    return proxy;              };                var serviceDescriptor = new ServiceDescriptor(serviceType, Factory, lifetime);              services.Add(serviceDescriptor);                return services;          }            /// <summary>          /// 添加summer boot擴展          /// </summary>          /// <param name="builder"></param>          /// <returns></returns>          public static IMvcBuilder AddSB(this IMvcBuilder builder)          {              if (builder == null)                  throw new ArgumentNullException(nameof(builder));              ControllerFeature feature = new ControllerFeature();              builder.PartManager.PopulateFeature<ControllerFeature>(feature);              foreach (Type type in feature.Controllers.Select<TypeInfo, Type>((Func<TypeInfo, Type>)(c => c.AsType())))                  builder.Services.TryAddTransient(type, type);              builder.Services.Replace(ServiceDescriptor.Transient<IControllerActivator, SbControllerActivator>());                return builder;          }            public static IServiceCollection AddSbRepositoryService(this IServiceCollection services, params Type[] interceptorTypes)          {              var types = AppDomain.CurrentDomain.GetAssemblies().SelectMany(it => it.GetTypes());                var tableType = types.Where(it => it.GetCustomAttribute<TableAttribute>() != null);                foreach (var type in tableType)              {                  var injectServiceType = typeof(IRepository<>).MakeGenericType(type);                  var injectImplType = typeof(BaseRepository<>).MakeGenericType(type);                  services.AddSbScoped(injectServiceType, injectImplType, interceptorTypes);              }                return services;          }      }

View Code

定義一個加油的服務類介面IAddOilService和介面的實現類AddOilService,可以從程式碼中看到,我們通過屬性注入添加了CashBalanceRepository和OilQuantityRepository,通過[Transactional]標註AddOil方法,使其成為事物性操作,AddOil主要就是初始化金額和油量,然後進行加減操作,最後更新。

   public interface IAddOilService      {          void AddOil();      }

public class AddOilService : IAddOilService      {          [Autowired]          public IRepository<CashBalance> CashBalanceRepository { set; get; }            [Autowired]          public IRepository<OilQuantity> OilQuantityRepository { set; get; }            [Transactional]          public void AddOil()          {              //初始化金額              var cashBalance = CashBalanceRepository.Insert(new CashBalance() { Balance = 100 });              //初始化油量              var oilQuantity = OilQuantityRepository.Insert(new OilQuantity() { Quantity = 5 });                cashBalance.Balance -= 95;              oilQuantity.Quantity += 50;                CashBalanceRepository.Update(cashBalance);              //throw new Exception("主動報錯");              OilQuantityRepository.Update(oilQuantity);          }      }

修改Startup.cs中的ConfigureServices方法,程式碼如下:

public void ConfigureServices(IServiceCollection services)          {              services.Configure<CookiePolicyOptions>(options =>              {                  // This lambda determines whether user consent for non-essential cookies is needed for a given request.                  options.CheckConsentNeeded = context => true;                  options.MinimumSameSitePolicy = SameSiteMode.None;              });                  services.AddMvc()                  .SetCompatibilityVersion(CompatibilityVersion.Version_2_1)                  .AddSB();                services.AddSingleton<ProxyGenerator>();                services.AddSbScoped<Engine>(typeof(TransactionalInterceptor));                services.AddSbScoped<IUnitOfWork, UnitOfWork>();              services.AddScoped(typeof(TransactionalInterceptor));                services.AddSbScoped<ICar, Car>(typeof(TransactionalInterceptor));                services.AddSbScoped<IDbFactory, DbFactory>();              services.AddSbRepositoryService(typeof(TransactionalInterceptor));              services.AddSbScoped<IAddOilService, AddOilService>(typeof(TransactionalInterceptor));          }

 控制器HomeController

    public class HomeController : Controller      {          [Autowired]          public ICar Car { set; get; }            [Autowired]          public IDistributedCache Cache { set; get; }            [Value("description")]          public string Description { set; get; }            [Autowired]          public IAddOilService AddOilService { set; get; }            public IActionResult Index()          {                var car = Car;                AddOilService.AddOil();                Car.Fire();                Console.WriteLine(Description);                return View();          }      }

  2.效果圖

2.1 清空2張表裡的數據,在AddOil末尾打斷點。

雖然前面執行了insert操作,但是我們查詢2張表,發現裡面並沒有新增數據,因為事物還未提交,符合預期。從斷點處繼續執行,然後查詢資料庫。

 

執行完事物後,數據正確,符合預期。

2.2 清空2張表裡的數據,注釋掉AddOil方法的[Transactional]註解,在AddOil末尾打斷點。

查看資料庫,因為沒開啟事務,所以數據已經正確插入到表中,並且由於oilQuantity倉儲未更新,所以數值正確,從斷點處繼續執行

 

oilQuantity倉儲更新,數值正確,符合預期。

2.3 清空2張表裡的數據,開啟AddOil方法的[Transactional]註解,並在方法中主動拋出一個錯誤。

表中並未添加數據,因為事物未提交,回滾了,符合預期。

BTW,事物的開啟,除了使用[Transactional]註解外,也可以通過注入uow,手動開啟和提交。

3. 寫在最後

        只需要在資料庫實體類上註解[Table(“表名”)]就可以直接使用倉儲了,是不是很簡潔優雅呢?這裡實現的倉儲都是通用的,如果有特殊需求的倉儲,則需要自定義介面和實現類,介面繼承IRepository<T>,實現類繼承BaseRepository<T>,然後注入自己的特殊倉儲就行了。

        如果這篇文章對你有所幫助,不妨點個贊咯。