如何在IAsyncEnumerable发射器函数中正确使用NpgsqlTransaction?

问题描述

我不需要捕获异常,但是如果有异常,我确实需要回滚:

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,但这具有新颖的上下文:

  • “ IAsyncEnumerable”,它是较新的。
  • Postgresql(其答案使用内部属性
  • 该问题的标题更好,明确地指向“交易”上下文。带有相同错误消息的其他上下文将没有相同的答案。

这与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的另一种方法是使用第三方库AsyncEnumeratorpackage)。

该库是C#8问世之前创建异步枚举的主要资源,它仍然有用,因为AFAIK不受本机yield的限制。您可以在lambda主体中将trycatchfinally块传递给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,而只是具有相同名称的参数。您可以根据需要给它起另一个名字。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...