[英]How can I correctly use NpgsqlTransaction inside a IAsyncEnumerable emitter function?
I don't need to catch the exception, but I do need to Rollback if there is an exception:我不需要捕获异常,但如果有异常,我确实需要回滚:
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();
}
}
(This code is a simplified version of what I am doing, for illustration purposes) (此代码是我正在做的事情的简化版本,用于说明目的)
This fails with the message:这失败并显示以下消息:
cannot yield a value in the body of a try block with a catch clause
不能在带有 catch 子句的 try 块的主体中产生一个值
This is similar to Yield return from a try/catch block , but this has novel context:这类似于来自 try/catch 块的 Yield 返回,但它具有新颖的上下文:
This is not the same as Why can't yield return appear inside a try block with a catch?这与为什么不能在带有 catch 的 try 块中出现 yield return 不同? .
. In my case, the context is more specific: I need the catch block to Rollback, not to do anything else.
在我的情况下,上下文更具体:我需要 catch 块来回滚,而不是做任何其他事情。 Also, as you can see, I already know the answer and created this as a Q&A combo.
此外,如您所见,我已经知道答案并将其创建为问答组合。 As you can see from the answer, that answer isn't relevant to Why can't yield return appear inside a try block with a catch?
正如您从答案中看到的那样,该答案与为什么不能在带有捕获的 try 块中产生收益?
You can move the Rollback to the finally block if you can check whether or not the transaction was committed, which you can do using IsCompleted
如果您可以检查事务是否已提交,您可以将回滚移动到 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();
}
}
Note: The catch block has been removed, and the finally block has two lines added to the start.注意:catch 块已被移除,finally 块在开始处添加了两行。
This same approach can also work on other DbTransaction
implementations that don't have IsCompleted
同样的方法也适用于没有
IsCompleted
其他DbTransaction
实现
see https://stackoverflow.com/a/7245193/887092见https://stackoverflow.com/a/7245193/887092
DbTransaction is considered the best way to manage transactions on SqlConnections, but TransactionScope is also valid, and might help others in related scenarios 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
}
}
An alternative to creating the IAsyncEnumerable
by using a C# iterator could be to use the third-party library AsyncEnumerator ( package ).使用 C# 迭代器创建
IAsyncEnumerable
的另一种方法是使用第三方库AsyncEnumerator ( package )。
This library was the main resource for creating asynchronous enumerables before the advent of C# 8, and it may still be useful because AFAIK it doesn't suffer by the limitations of the native yield
.在 C# 8 出现之前,这个库是创建异步枚举的主要资源,它可能仍然有用,因为 AFAIK 它不受本机
yield
的限制。 You are allowed to have try
, catch
and finally
blocks in the body of the lambda passed to the AsyncEnumerable
constructor, and invoke the yield.ReturnAsync
method from any of these blocks.您可以在传递给
AsyncEnumerable
构造函数的 lambda 主体中使用try
、 catch
和finally
块,并从这些块中的任何一个调用yield.ReturnAsync
方法。
Usage example:用法示例:
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();
}
});
}
The yield
in the above example is not the C# yield contextual keyword , but just an argument with the same name.上面例子中的
yield
不是 C# yield 上下文关键字,而只是一个同名的参数。 You could give it another name if you want.如果你愿意,你可以给它另一个名字。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.