问题描述
我不需要捕获异常,但是如果有异常,我确实需要回滚:
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块的主体中产生值
这类似于Yield return from a try/catch block,但这具有新颖的上下文:
这与Why can't yield return appear inside a try block with a catch?不同。就我而言,上下文更具体:我需要catch块来回滚,而不做其他任何事情。另外,正如您所看到的,我已经知道答案了,并将其创建为Q&A组合。正如您从答案中看到的那样,该答案与Why can't yield return appear inside a try block with a catch?
不相关解决方法
如果您可以检查事务是否已提交,则可以将“回滚”移到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块的开头增加了两行。
同样的方法也可以在没有DbTransaction
的其他IsCompleted
实现中使用
请参阅https://stackoverflow.com/a/7245193/887092
,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);
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#iterator创建IAsyncEnumerable
的另一种方法是使用第三方库AsyncEnumerator(package)。
该库是C#8问世之前创建异步枚举的主要资源,它仍然有用,因为AFAIK不受本机yield
的限制。您可以在lambda主体中将try
,catch
和finally
块传递给AsyncEnumerable
构造函数,并从任何一个调用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 contextual keyword,而只是具有相同名称的参数。您可以根据需要给它起另一个名字。