[英]How can I correctly use NpgsqlTransaction inside a IAsyncEnumerable emitter function?
我不需要捕获异常,但如果有异常,我确实需要回滚:
public async IAsyncEnumerable<Item> Select()
{
var t = await con.BeginTransactionAsync(token);
try {
var batchOfItems = new List<Item>(); //Buffer, so the one connection can be used while enumerating items
using (var reader = await com.ExecuteReaderAsync(SQL, token))
{
while (await reader.ReadAsync(token))
{
var M = await Materializer(reader, token);
batchOfItems.Add(M);
}
}
foreach (var item in batchOfItems)
{
yield return item;
}
await t.CommitAsync();
}
catch
{
await t.RollbackAsync();
}
finally
{
await t.DisposeAsync();
}
}
(此代码是我正在做的事情的简化版本,用于说明目的)
这失败并显示以下消息:
不能在带有 catch 子句的 try 块的主体中产生一个值
这类似于来自 try/catch 块的 Yield 返回,但它具有新颖的上下文:
这与为什么不能在带有 catch 的 try 块中出现 yield return 不同? . 在我的情况下,上下文更具体:我需要 catch 块来回滚,而不是做任何其他事情。 此外,如您所见,我已经知道答案并将其创建为问答组合。 正如您从答案中看到的那样,该答案与为什么不能在带有捕获的 try 块中产生收益?
如果您可以检查事务是否已提交,您可以将回滚移动到 finally 块,您可以使用IsCompleted
public async IAsyncEnumerable<Item> Select()
{
var t = await con.BeginTransactionAsync(token);
try {
var batchOfItems = new List<Item>(); //Buffer, so the one connection can be used while enumerating items
async using (var reader = await com.ExecuteReaderAsync(SQL, token))
{
while (await reader.ReadAsync(token))
{
var M = await Materializer(reader, token);
batchOfItems.Add(M);
}
}
foreach (var item in batchOfItems)
{
yield return item;
}
await t.CommitAsync();
}
finally
{
if (t.IsCompleted == false) //Implemented on NpgsqlTransaction, but not DbTransaction
await t.RollbackAsync();
await t.DisposeAsync();
}
}
注意:catch 块已被移除,finally 块在开始处添加了两行。
同样的方法也适用于没有IsCompleted
其他DbTransaction
实现
DbTransaction 被认为是在 SqlConnections 上管理事务的最佳方式,但 TransactionScope 也是有效的,并且可能在相关场景中帮助其他人
public async IAsyncEnumerable<Item> Select()
{
using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
con.EnlistTransaction(Transaction.Current); //it's better to open the connection here, then dispose, but this will work
com = con.CreateCommand(); //Probably need a new command object so it has the transaction context
var batchOfItems = new List<Item>(); //Buffer, so the one connection can be used while enumerating items
async using (var reader = await com.ExecuteReaderAsync(SQL, token))
{
while (await reader.ReadAsync(token))
{
var M = await Materializer(reader, token);
batchOfItems.Add(M);
}
}
foreach (var item in batchOfItems)
{
yield return item;
}
scope.Complete(); //Asynch option not available
//No need to have explicit rollback call, instead it's standard for that to happen upon disposal if not completed
}
}
使用 C# 迭代器创建IAsyncEnumerable
的另一种方法是使用第三方库AsyncEnumerator ( package )。
在 C# 8 出现之前,这个库是创建异步枚举的主要资源,它可能仍然有用,因为 AFAIK 它不受本机yield
的限制。 您可以在传递给AsyncEnumerable
构造函数的 lambda 主体中使用try
、 catch
和finally
块,并从这些块中的任何一个调用yield.ReturnAsync
方法。
用法示例:
using Dasync.Collections;
//...
public IAsyncEnumerable<Item> Select()
{
return new AsyncEnumerable<Item>(async yield => // This yield is a normal argument
{
await using var transaction = await con.BeginTransactionAsync(token);
try
{
var batchOfItems = new List<Item>();
await using (var reader = await com.ExecuteReaderAsync(SQL, token))
{
while (await reader.ReadAsync(token))
{
var M = await Materializer(reader, token);
batchOfItems.Add(M);
}
}
foreach (var item in batchOfItems)
{
await yield.ReturnAsync(item); // Instead of yield return item;
}
await transaction.CommitAsync();
}
catch (Exception ex)
{
await transaction.RollbackAsync();
}
});
}
上面例子中的yield
不是 C# yield 上下文关键字,而只是一个同名的参数。 如果你愿意,你可以给它另一个名字。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.