系列目录

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; } = ; [Autowired]
public IDbFactory DbFactory { set; get; } public void BeginTransaction()
{
if (this.ActiveNumber == )
{
DbFactory.BeginTransaction();
Console.WriteLine("开启事务");
}
this.ActiveNumber++;
} public void Commit()
{
this.ActiveNumber--;
if (this.ActiveNumber == )
{
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 > && 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 == ? DbFactory.ShortDbConnection : DbFactory.LongDbConnection;
dbcon.Insert(t, DbFactory.LongDbTransaction); if (Uow.ActiveNumber == )
{
dbcon.Close();
dbcon.Dispose();
} return t;
} public IList<T> Insert(IList<T> t)
{
var dbcon = Uow.ActiveNumber == ? DbFactory.ShortDbConnection : DbFactory.LongDbConnection;
dbcon.Insert(t, DbFactory.LongDbTransaction); if (Uow.ActiveNumber == )
{
dbcon.Close();
dbcon.Dispose();
} return t;
} public void Delete(T t)
{
var dbcon = Uow.ActiveNumber == ? DbFactory.ShortDbConnection : DbFactory.LongDbConnection;
dbcon.Delete(t, DbFactory.LongDbTransaction); if (Uow.ActiveNumber == )
{
dbcon.Close();
dbcon.Dispose();
} } public void Delete(IList<T> t)
{
var dbcon = Uow.ActiveNumber == ? DbFactory.ShortDbConnection : DbFactory.LongDbConnection;
dbcon.Delete(t, DbFactory.LongDbTransaction); if (Uow.ActiveNumber == )
{
dbcon.Close();
dbcon.Dispose();
} } public void Update(T t)
{
var dbcon = Uow.ActiveNumber == ? DbFactory.ShortDbConnection : DbFactory.LongDbConnection;
dbcon.Update(t, DbFactory.LongDbTransaction); if (Uow.ActiveNumber == )
{
dbcon.Close();
dbcon.Dispose();
} } public void Update(IList<T> t)
{
var dbcon = Uow.ActiveNumber == ? DbFactory.ShortDbConnection : DbFactory.LongDbConnection;
dbcon.Update(t, DbFactory.LongDbTransaction);
if (Uow.ActiveNumber == )
{
dbcon.Close();
dbcon.Dispose();
} }
}

事物拦截器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 = });
//初始化油量
var oilQuantity = OilQuantityRepository.Insert(new OilQuantity() { Quantity = }); cashBalance.Balance -= ;
oilQuantity.Quantity += ; 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>,然后注入自己的特殊仓储就行了。

如果这篇文章对你有所帮助,不妨点个赞咯。

net core天马行空系列: 泛型仓储和声明式事物实现最优雅的crud操作的更多相关文章

  1. net core天马行空系列:移植Feign,结合Polly,实现回退,熔断,重试,超时,做最好用的声明式http服务调用端

    系列目录 1.net core天马行空系列:原生DI+AOP实现spring boot注解式编程 2.net core天马行空系列: 泛型仓储和声明式事物实现最优雅的crud操作 3.net core ...

  2. net core天马行空系列: 一个接口多个实现类,利用mixin技术通过自定义服务名,实现精准属性注入

    系列目录 1.net core天马行空系列:原生DI+AOP实现spring boot注解式编程 2.net core天马行空系列: 泛型仓储和声明式事物实现最优雅的crud操作 哈哈哈哈,大家好,我 ...

  3. net core天马行空系列:SummerBoot,将SpringBoot的先进理念与C#的简洁优雅合二为一

    系列目录 1.net core天马行空系列:原生DI+AOP实现spring boot注解式编程 2.net core天马行空系列: 泛型仓储和声明式事物实现最优雅的crud操作 3.net core ...

  4. net core天马行空系列:原生DI+AOP实现spring boot注解式编程

    写过spring boot之后,那种无处不在的注解让我非常喜欢,比如属性注入@autowire,配置值注入@value,声明式事物@Transactional等,都非常简洁优雅,那么我就在想,这些在n ...

  5. net core天马行空系列-微服务篇:全声明式http客户端feign快速接入微服务中心nacos

    1.前言 hi,大家好,我是三合,距离上一篇博客已经过去了整整两年,这两年里,博主通关了<人生>这个游戏里的两大关卡,买房和结婚.最近闲了下来,那么当然要继续写博客了,今天这篇博客的主要内 ...

  6. net core天马行空系列:降低net core门槛,数据库操作和http访问仅需写接口,实现类由框架动态生成

    引文   hi,大家好,我是三合.不知各位有没有想过,如果能把数据库操作和http访问都统一封装成接口(interface)的形式, 然后接口对应的实现类由框架去自动生成,那么必然能大大降低工作量,因 ...

  7. net core天马行空系列-可用于依赖注入的,数据库表和c#实体类互相转换的接口实现

    1.前言 hi,大家好,我是三合.作为一名程序猿,日常开发中,我们在接到需求以后,一般都会先构思一个模型,然后根据模型写实体类,写完实体类后在数据库里建表,接着进行增删改查, 也有第二种情况,就是有些 ...

  8. net core天马行空系列-各大数据库快速批量插入数据方法汇总

    1.前言 hi,大家好,我是三合.我是怎么想起写一篇关于数据库快速批量插入的博客的呢?事情起源于我们工作中的一个需求,简单来说,就是有一个定时任务,从数据库里获取大量数据,在应用层面经过处理后再把结果 ...

  9. ASP.NET Core 2.2 WebApi 系列【七】泛型仓储模式和工作单元

    在之前的泛型仓储模式实现中,每个增删改都调用了SaveChanges方法,导致每次更新都提交了事务. 在实际开发过程中,我们经常遇到同时操作多张表数据,那么按照之前的写法,对数据库提交了多次操作,开启 ...

随机推荐

  1. Linux/UNIX编程:使用C语言实现简单的 ls 命令

    刚好把 Linux/UNIX 编程中的文件和IO部分学完了,就想编写个 ls 命令练习一下,本以为很简单,调用个 stat 就完事了,没想到前前后后弄了七八个小时,90%的时间都用在格式化(像 ls ...

  2. 史上最强Java开发环境搭建

    在项目产品开发中,开发环境搭建是软件开发的首要阶段,也是必须阶段,只有开发环境搭建好了,方可进行开发,良好的开发环境搭建,为后续的开发工作带来极大便利. 对于大公司来说,软件开发环境搭建工作一般是由运 ...

  3. EF Core的Code First 基础

    一.创建实体类与映射类 通过NuGet引用Microsoft.EntityFrameworkCore 1.创建实体类 Code First可以通过为实体类字段添加相应特性,来创建对应的字段类型等,举例 ...

  4. AI中台——智能聊天机器人平台的架构与应用(分享实录)

    内容来源:宜信技术学院第3期技术沙龙-线上直播|AI中台——智能聊天机器人平台 主讲人:宜信科技中心AI中台团队负责人王东 导读:随着“中台”战略的提出,目前宜信中台建设在思想理念及架构设计上都已经取 ...

  5. IO流与NIO流

    JAVA IO流最详解   (转自CSDN) IO流上:概述.字符流.缓冲区(java基础)   一.IO流概述 概述: IO流简单来说就是Input和Output流,IO流主要是用来处理设备之间的数 ...

  6. Codeforces 868E Policeman and a Tree

    题意简述 给你一颗有n个点的树,每条边有边权,有一个警察一开始在点S,他的速度是1,即通过一条长度为x的边要花x单位时间. 有m个罪犯,一开始第i个在点x[i],他们的速度无限快. 如果罪犯和警察到达 ...

  7. 从零开始学习GDI+ (一)

    前言: GDI+从Windows XP操作系统(大概2002-2003年)开始引入的,现在都9102年了,再学习这么古老的技术肯定是过时了.windows桌面程序没落了,随着移动的兴起,用户被惯坏了, ...

  8. 消息中间件——RabbitMQ(二)各大主流消息中间件综合对比介绍!

    前言 消息队列已经逐渐成为企业IT系统内部通信的核心手段.它具有低耦合.可靠投递.广播.流量控制.最终一致性等一系列功能,成为异步RPC的主要手段之一.当今市面上有很多主流的消息中间件,如老牌的Act ...

  9. redis 有没有ACID事务

    看redis官网的介绍: redis确实是有事务的,但是和传统的ACID是否相同呢? 原子性(Atomicity) 原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生. 一致 ...

  10. R 包 rgl 安装失败, 报错 X11 not found but required, configure aborted 及解决方法

    R 包 rgl 安装失败, X11 not found but required, configure aborted * installing *source* package ‘rgl’ ... ...