net core天馬行空系列: 泛型倉儲和宣告式事物實現最優雅的crud操作
系列目錄
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:IDisposable { [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,當次數為1時提交,主要是為了防止事物巢狀 /// </summary> int ActiveNumber { get; set; } /// <summary> /// 開啟事務 /// </summary> void BeginTransaction(); /// <summary> /// 提交 /// </summary> void Commit(); /// <summary> /// 事物回滾 /// </summary> void RollBack(); }
public class UnitOfWork : IUnitOfWork { /// <summary> /// 工作單元引用次數,當次數為1時提交,主要為了防止事物巢狀 /// </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>,然後注入自己的特殊倉儲就行了。
如果這篇文章對你有所幫助,不妨點個贊