Castle.DynamicProxy拦截异步方法

参考:https://www.cnblogs.com/zhaopei/archive/2019/05/20/10875775.html中事务处理部分

 1、Autofac注入

builder.RegisterType();

//增加事务处理
builder.RegisterAssemblyTypes(fmocmAssemblies)
.Where(type => typeof(IAppService).IsAssignableFrom(type) && !type.IsAbstract)
//.AsSelf()
.AsImplementedInterfaces()
.InstancePerLifetimeScope()
.InterceptedBy(typeof(UnitOfWorkIInterceptor)).EnableInterfaceInterceptors();

//或者添加一个事务拦截器接口,在仅需要事务处理的类上添加接口
// 增加拦截器处理
builder.RegisterAssemblyTypes(fmocmAssemblies)
	.Where(type => typeof(IUOWInterceptor).IsAssignableFrom(type) && !type.IsAbstract)               
	.InterceptedBy(typeof(UnitOfWorkIInterceptor)).EnableInterfaceInterceptors();

不注释.AsSelf(), 会报错,cannot use interface interception as it provides services that are not publicly visible interfaces. Check your registration of the component to ensure you're not enabling interception and registering it as an internal/private interface type. 不能使用接口拦截,因为它提供的服务不是公开可见的接口。检查组件的注册,以确保没有启用拦截并将其注册为内部/私有接口类型

2、无异步时 

[UnitOfWork]
public long TestTransactionInterceptor(long id)
{
	this._terminalTypeRepository.Insert(new TerminalTypeEntity
	{
		Name = "Interceptor_终端设备",
	});

	this._alarmTypeRepository.Insert(new AlarmTypeEntity
	{
		//AlarmTypeName = "油温过1油温过1油温过高油温过高油温过高油温过高油温过高油温过高油温过高油温过高油温过高油温过高油温过高油温过高油温过高油温过高",
		AlarmTypeName = "速度过快",
		Description = "Interceptor_事务测试",
	});

	return id;
}

 

3、不使用Castle.DynamicProxy,注释掉.InterceptedBy(typeof(UnitOfWorkIInterceptor)).EnableInterfaceInterceptors()

public async Task TestTransactionNoInterceptorAsync(long id)
{
	this._unitOfWork.BeginTran();
	try
	{
		await this._terminalTypeRepository.InsertAsync(new TerminalTypeEntity
		{
			Name = "NoInterceptorAsync",
		}).ConfigureAwait(false);

		this._terminalTypeRepository.Insert(new TerminalTypeEntity
		{
			Name = "NoInterceptorAsync_同步",
		});

		await this._alarmTypeRepository.InsertAsync(new AlarmTypeEntity
		{
			AlarmTypeName = "油温过高油温过高油温过高油温过高油温过高油温过高油温过高油温过高油温过高油温过高油温过高油温过高油温过高油温过高",
			//AlarmTypeName = "速度过快",
			Description = "TestTransactionNoInterceptorAsync",
		}).ConfigureAwait(false);

		this._unitOfWork.Commit();
	}
	catch(Exception ex)
	{
		this._unitOfWork.Rollback();
		throw ex;
	}

	return id;
}

4、判断是否为异步方法参考:http://wayneshao.com/%E5%A6%82%E4%BD%95%E5%88%A4%E5%AE%9A%E4%B8%80%E4%B8%AA%E6%96%B9%E6%B3%95%E6%94%AF%E4%B8%8D%E6%94%AF%E6%8C%81%E5%BC%82%E6%AD%A5.html

public static class ReflectionExtension
{
	public static bool IsAsyncType(this Type type)
	{
		var awaiter = type.GetMethod("GetAwaiter");
		if (awaiter == null)
			return false;
		var retType = awaiter.ReturnType;
		//.NET Core 1.1及以下版本中没有 GetInterface 方法,为了兼容性使用 GetInterfaces
		if (retType.GetInterfaces().All(i => i.Name != "INotifyCompletion"))
			return false;
		if (retType.GetProperty("IsCompleted") == null)
			return false;
		if (retType.GetMethod("GetResult") == null)
			return false;
		return true;
	}
}

调用异步方法参考:https://bbs.csdn.net/topics/390723959

5、异步拦截器方法

public void Intercept(IInvocation invocation)
{
	MethodInfo methodInfo = invocation.MethodInvocationTarget;
	if (methodInfo == null)
		methodInfo = invocation.Method;

	UnitOfWorkAttribute unitOfWork = methodInfo.GetCustomAttributes(true).FirstOrDefault();
	//如果标记了 [UnitOfWork],并且不在事务嵌套中。
	if (unitOfWork != null && dbContext.Committed)
	{
		//开启事务
		dbContext.BeginTran();
		try
		{
			if (methodInfo.ReturnType.IsAsyncType())
			{
				var task = methodInfo.Invoke(invocation.InvocationTarget, invocation.Arguments) as Task;
				task.ContinueWith(x => {
					if (x.Status == TaskStatus.RanToCompletion)
					{
						dbContext.Commit();
					}
					else
					{
						dbContext.Rollback();
					}
				}).ConfigureAwait(false);
				invocation.ReturnValue = task;
			}
			else
			{
				//事务包裹 查询语句 
				//https://github.com/mysql-net/MySqlConnector/issues/405
				invocation.Proceed();
				dbContext.Commit();
			}
		}
		catch (Exception ex)
		{
			//回滚
			dbContext.Rollback();
			throw;
		}
	}
	else
	{
		//如果没有标记[UnitOfWork],直接执行方法
		try
		{
			if (methodInfo.ReturnType.IsAsyncType())
			{
				var task = methodInfo.Invoke(invocation.InvocationTarget, invocation.Arguments) as Task;
				invocation.ReturnValue = task;
			}
			else
			{
				invocation.Proceed();
			}
		}
		catch (Exception ex)
		{
			throw ex;
		}
	}
}

6、待包裹的方法

[UnitOfWork]
public virtual async Task TestTransactionInterceptorAsync(long id)
{
	// 因为外部有事务操作,所以此处要用await再包裹一次,表示等待内部的所有操作完成
	return await Task.Run(async () =>
	{
		await this._terminalTypeRepository.InsertAsync(new TerminalTypeEntity
		{
			Name = "InterceptorAsync_终端设备",
		});

		var result = await this._alarmTypeRepository.InsertAsync(new AlarmTypeEntity
		{
			// 将截断字符串或二进制数据。\r\n语句已终止
			AlarmTypeName = "1_油温过高油温过高油温过高油温过高油温过高油温过高油温过高油温过高油温过高油温过高油温过高油温过高油温过高油温过高",
			//AlarmTypeName = "速度过快",
			Description = "InterceptorAsync_事务测试",
		});

		return result;
	});

	//var task1 = this._terminalTypeRepository.InsertAsync(new TerminalTypeEntity
	//{
	//    Name = "InterceptorAsync_终端设备",
	//});

	//var task2 = this._alarmTypeRepository.InsertAsync(new AlarmTypeEntity
	//{
	//    // 将截断字符串或二进制数据。\r\n语句已终止
	//    AlarmTypeName = "1_油温过高油温过高油温过高油温过高油温过高油温过高油温过高油温过高油温过高油温过高油温过高油温过高油温过高油温过高",
	//    //AlarmTypeName = "速度过快",
	//    Description = "InterceptorAsync_事务测试",
	//});

	//try
	//{
	//    await Task.WhenAll(new Task[] { task1, task2}).ConfigureAwait(false);
	//    return task2.Result;
	//}
	//catch (Exception ex)
	//{
	//    throw ex;
	//}
}

 7、调用方法1:

/// 
/// 异步事务拦截
/// 
/// 
[TestMethod]
public async Task TestTransactionInterceptorAsync()
{
	IocConfig.RegisterDependencies();
	using (var scope = IocConfig.IOCContainer.BeginLifetimeScope())
	{
		try
		{
			var app = scope.Resolve();
			var result = await app.TestTransactionInterceptorAsync(999);
		}
		catch (Exception ex)
		{
			string msg = ex.Message;
		}
	}
}

调用方法2:

/// 
/// 异步事务拦截
/// 
/// 
[TestMethod]
public void TestTransactionInterceptorAsync()
{
	IocConfig.RegisterDependencies();
	using (var scope = IocConfig.IOCContainer.BeginLifetimeScope())
	{
		try
		{
			var app = scope.Resolve();
			var result = app.TestTransactionInterceptorAsync(999);
			 // 没有下面这句,TestTransactionInterceptorAsync方法中的
			 // 有些语句不会执行, 所以尽量用async与await 
			var t = result.Result;
		}
		catch (Exception ex)
		{
			string msg = ex.Message;
		}
	}
}

7、FreeSql中事务的使用

public string BuildBillCode(string tableName, bool isUnitWork = false)
{
	var dbTran = unitOfWork.GetOrBeginTransaction(!isUnitWork);
	try
	{
		var command = @"UPDATE codegenerate 
						   SET CodeNumber = ISNULL(CodeNumber, 0) + 1
						 WHERE tableName = @tableName ";
		var context = freeSql.CreateDbContext();
		freeSql.Ado.ExecuteNonQuery(dbTran, command, new { tableName });
		//CG+年+月+5位
		command = @"SELECT ISNULL(Prefix, '') + CONVERT(VARCHAR(6), GETDATE(), 112) + RIGHT('00000' + CONVERT(VARCHAR(5), CodeNumber), 5) + ISNULL(Suffix, '') 
					  FROM codegenerate 
					 WHERE tableName = @tableName";
		var obj = freeSql.Ado.ExecuteScalar(dbTran, command, new { tableName });
		if (!isUnitWork)
			dbTran.Commit();
		return obj.ToString();
	}
	catch
	{
		if (!isUnitWork)
			dbTran.Rollback();
		throw;
	}
}
[UnitOfWork]
public async Task AddEntityAsync(PurchaseDto dto)
{
	var entity = this._mapper.Map(dto);

	var userInfo = await LoginUserInfo.GetUserData().ConfigureAwait(false);
	entity.Creator = userInfo?.UserName;
	entity.CreateTime = DateTime.Now;
	// 生成编码
	// 不支持事务嵌套,所以要么第二个参数为true
	// 要么把生成序号的代码移除到外部
	// 为false时,会报"Message	"This SqlTransaction has completed; it is no longer usable."
	entity.PurchaseNo = this._codeGenerate.BuildBillCode("cms_purchase", true);
	await this._purchaseRepository.InsertAsync(entity).ConfigureAwait(false);
	
	//throw new Exception("事务异常测试");

	if (dto.PurchaseDetails != null)
	{
		for (int i = 0; i < dto.PurchaseDetails.Count; i++)
		{
			dto.PurchaseDetails[i].PurchaseNo = entity.PurchaseNo;
			dto.PurchaseDetails[i].SerialNo = i + 1;
		}

		var details = this._mapper.Map>(dto.PurchaseDetails);
		await this._purchaseDetailRepository.InsertListAsync(details).ConfigureAwait(false);
	}
}

结论,对于不支持嵌套事务的框架,在UnitWork中不要再包含事务,否则会导致在“事务异常测试”前的SQL语句提交,而不会回滚。

你可能感兴趣的:(C#开发)