net core系列:泛型倉儲和聲明式事物實現最優雅crud

哈哈哈哈,大家好,我就是那個高產似母豬的三合,長久以來,我一直在思考,如何才能實現高效而簡潔的倉儲模式(不是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>

/// 油量

///

public decimal Quantity { set; get; }

}

[Table("CashBalance")]

public class CashBalance

{

[Key]

public int Id { set; get; }

/// <summary>

/// 餘額

///

public decimal Balance { set; get; }

}

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

public interface IDbFactory:IDisposable

{

/// <summary>

/// 長鏈接

///

IDbConnection LongDbConnection { get; }

/// <summary>

/// 長鏈接的事物

///

IDbTransaction LongDbTransaction { get; }

/// <summary>

/// 短鏈接

///

IDbConnection ShortDbConnection { get; }

/// <summary>

/// 開啟事務

///

void BeginTransaction();

}

/// <summary>

/// 負責生成和銷燬數據庫鏈接

///

public class DbFactory:IDbFactory

{

[Value("MysqlConnectionStr")]

public string MysqlConnectionStr { set; get; }

/// <summary>

/// 長連接

///

public IDbConnection LongDbConnection { private set; get; }

/// <summary>

/// 長連接的事物

///

public IDbTransaction LongDbTransaction { private set; get; }

/// <summary>

/// 短鏈接

///

public IDbConnection ShortDbConnection

{

get

{

var dbConnection = new MySqlConnection(MysqlConnectionStr);

dbConnection.Open();

return dbConnection;

}

}

/// <summary>

/// 開啟事務

///

/// <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時提交,主要是為了防止事物嵌套

///

int ActiveNumber { get; set; }

/// <summary>

/// 開啟事務

///

void BeginTransaction();

/// <summary>

/// 提交

///

void Commit();

/// <summary>

/// 事物回滾

///

void RollBack();

}

public class UnitOfWork : IUnitOfWork

{

/// <summary>

/// 工作單元引用次數,當次數為0時提交,主要為了防止事物嵌套

///

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和他的實現類BaseRepository,為了偷懶,只寫了同步部分,異步同理,若使用異步,攔截器也要使用異步攔截器。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 where T : class

{

IList GetAll();

T Get(object id);

T Insert(T t);

IList Insert(IList t);

void Update(T t);

void Update(IList t);

void Delete(IList t);

void Delete(T t);

}

public class BaseRepository : IRepository where T : class

{

[Autowired]

public IUnitOfWork Uow { set; get; }

[Autowired]

public IDbFactory DbFactory { set; get; }

public IList GetAll()

{

var dbcon = DbFactory.ShortDbConnection;

var result = dbcon.GetAll().ToList();

dbcon.Close();

dbcon.Dispose();

return result;

}

public T Get(object id)

{

var dbcon = DbFactory.ShortDbConnection;

var result = dbcon.Get(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 Insert(IList 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)

{

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)

{

var dbcon = Uow.ActiveNumber == 0 ? DbFactory.ShortDbConnection : DbFactory.LongDbConnection;

dbcon.Update(t, DbFactory.LongDbTransaction);

if (Uow.ActiveNumber == 0)

{

dbcon.Close();

dbcon.Dispose();

}

}

}

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

/// <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)/<transactionalattribute>

{

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)/<transactionalattribute>

{

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>

/// 瞬時

///

/// <typeparam>

/// <typeparam>

/// <param>

/// <param>

/// <returns>

public static IServiceCollection AddSbTransient<tservice>(this IServiceCollection services, params Type[] interceptorTypes)/<tservice>

{

return services.AddSbService(typeof(TService), typeof(TImplementation), ServiceLifetime.Transient, interceptorTypes);

}

/// <summary>

/// 瞬時

///

/// <param>

/// <param>

/// <param>

/// <param>

/// <returns>

public static IServiceCollection AddSbTransient(this IServiceCollection services, Type serviceType,

Type implementationType, params Type[] interceptorTypes)

{

return services.AddSbService(serviceType, implementationType, ServiceLifetime.Transient, interceptorTypes);

}

/// <summary>

/// 請求級別

///

/// <typeparam>

/// <typeparam>

/// <param>

/// <param>

/// <returns>

public static IServiceCollection AddSbScoped<tservice>(this IServiceCollection services, params Type[] interceptorTypes)/<tservice>

{

return services.AddSbService(typeof(TService), typeof(TImplementation), ServiceLifetime.Scoped, interceptorTypes);

}

/// <summary>

/// 請求級別

///

/// <param>

/// <param>

/// <param>

/// <param>

/// <returns>

public static IServiceCollection AddSbScoped(this IServiceCollection services, Type serviceType,

Type implementationType, params Type[] interceptorTypes)

{

return services.AddSbService(serviceType, implementationType, ServiceLifetime.Scoped, interceptorTypes);

}

/// <summary>

/// 單例

///

/// <typeparam>

/// <typeparam>

/// <param>

/// <param>

/// <returns>

public static IServiceCollection AddSbSingleton<tservice>(this IServiceCollection services, params Type[] interceptorTypes)/<tservice>

{

return services.AddSbService(typeof(TService), typeof(TImplementation), ServiceLifetime.Singleton, interceptorTypes);

}

/// <summary>

/// 單例

///

/// <param>

/// <param>

/// <param>

/// <param>

/// <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)/<autowiredattribute>

{

var propertyType = info.PropertyType;

var impl = provider.GetService(propertyType);

if (impl != null)

{

info.SetValue(target, impl);

}

}

//配置值注入

if (info.GetCustomAttribute<valueattribute>() is ValueAttribute 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()/<iinterceptor>

.ConvertAll<iinterceptor>(interceptorType => provider.GetService(interceptorType) as IInterceptor);/<iinterceptor>

var proxyGenerator = provider.GetService<proxygenerator>();/<proxygenerator>

var proxy = proxyGenerator.CreateInterfaceProxyWithTarget(serviceType, target, interceptors.ToArray());

return proxy;

};

var serviceDescriptor = new ServiceDescriptor(serviceType, Factory, lifetime);

services.Add(serviceDescriptor);

return services;

}

/// <summary>

/// 瞬時

///

/// <typeparam>

/// <param>

/// <param>

/// <returns>

public static IServiceCollection AddSbTransient<tservice>(this IServiceCollection services, params Type[] interceptorTypes)/<tservice>

{

return services.AddSbService(typeof(TService), ServiceLifetime.Transient, interceptorTypes);

}

/// <summary>

/// 瞬時

///

/// <param>

/// <param>

/// <param>

/// <returns>

public static IServiceCollection AddSbTransient(this IServiceCollection services, Type serviceType, params Type[] interceptorTypes)

{

return services.AddSbService(serviceType, ServiceLifetime.Transient, interceptorTypes);

}

/// <summary>

/// 請求

///

/// <typeparam>

/// <param>

/// <param>

/// <returns>

public static IServiceCollection AddSbScoped<tservice>(this IServiceCollection services, params Type[] interceptorTypes)/<tservice>

{

return services.AddSbService(typeof(TService), ServiceLifetime.Scoped, interceptorTypes);

}

/// <summary>

/// 請求

///

/// <param>

/// <param>

/// <param>

/// <returns>

public static IServiceCollection AddSbScoped(this IServiceCollection services, Type serviceType,

params Type[] interceptorTypes)

{

return services.AddSbService(serviceType, ServiceLifetime.Scoped, interceptorTypes);

}

/// <summary>

/// 單例

///

/// <typeparam>

/// <param>

/// <param>

/// <returns>

public static IServiceCollection AddSbSingleton<tservice>(this IServiceCollection services, params Type[] interceptorTypes)/<tservice>

{

return services.AddSbService(typeof(TService), ServiceLifetime.Singleton, interceptorTypes);

}

/// <summary>

/// 單例

///

/// <param>

/// <param>

/// <param>

/// <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()/<iinterceptor>

.ConvertAll<iinterceptor>(interceptorType => provider.GetService(interceptorType) as IInterceptor);/<iinterceptor>

var proxyGenerator = provider.GetService<proxygenerator>();/<proxygenerator>

var proxy = proxyGenerator.CreateClassProxy(serviceType, interceptors.ToArray());

var properties = serviceType.GetTypeInfo().DeclaredProperties;

foreach (PropertyInfo info in properties)

{

//屬性注入

if (info.GetCustomAttribute<autowiredattribute>() != null)/<autowiredattribute>

{

var propertyType = info.PropertyType;

var impl = provider.GetService(propertyType);

if (impl != null)

{

info.SetValue(proxy, impl);

}

}

//配置值注入

if (info.GetCustomAttribute<valueattribute>() is ValueAttribute 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擴展

///

/// <param>

/// <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);/<controllerfeature>

foreach (Type type in feature.Controllers.Select<typeinfo>((Func<typeinfo>)(c => c.AsType())))/<typeinfo>/<typeinfo>

builder.Services.TryAddTransient(type, type);

builder.Services.Replace(ServiceDescriptor.Transient<icontrolleractivator>());/<icontrolleractivator>

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);/<tableattribute>

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; }/<cashbalance>

[Autowired]

public IRepository<oilquantity> OilQuantityRepository { set; get; }/<oilquantity>

[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 =>/<cookiepolicyoptions>

{

// 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>();/<proxygenerator>

services.AddSbScoped<engine>(typeof(TransactionalInterceptor));/<engine>

services.AddSbScoped<iunitofwork>();/<iunitofwork>

services.AddScoped(typeof(TransactionalInterceptor));

services.AddSbScoped<icar>(typeof(TransactionalInterceptor));/<icar>

services.AddSbScoped<idbfactory>();/<idbfactory>

services.AddSbRepositoryService(typeof(TransactionalInterceptor));

services.AddSbScoped<iaddoilservice>(typeof(TransactionalInterceptor));/<iaddoilservice>

}

控制器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末尾打斷點。

net core系列:泛型倉儲和聲明式事物實現最優雅crud


net core系列:泛型倉儲和聲明式事物實現最優雅crud


net core系列:泛型倉儲和聲明式事物實現最優雅crud

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

net core系列:泛型倉儲和聲明式事物實現最優雅crud


net core系列:泛型倉儲和聲明式事物實現最優雅crud

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

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

net core系列:泛型倉儲和聲明式事物實現最優雅crud


net core系列:泛型倉儲和聲明式事物實現最優雅crud


net core系列:泛型倉儲和聲明式事物實現最優雅crud

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

net core系列:泛型倉儲和聲明式事物實現最優雅crud

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

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

net core系列:泛型倉儲和聲明式事物實現最優雅crud


net core系列:泛型倉儲和聲明式事物實現最優雅crud


net core系列:泛型倉儲和聲明式事物實現最優雅crud

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

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

3. 寫在最後

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

原文地址:博客園

原文來源:https://www.cnblogs.com/hezp/p/11434046.html


分享到:


相關文章: