系列目錄
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;
}
}
定義一個加油的服務類接口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>,然後注入自己的特殊倉儲就行了。
如果這篇文章對你有所幫助,不妨點個贊咯。