繁体   English   中英

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

[英]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 返回,但它具有新颖的上下文:

  • “IAsyncEnumerable”相对较新。
  • Postgresql(答案使用内部属性)
  • 这个问题有一个更好的标题,明确提到“交易”上下文。 具有相同错误消息的其他上下文不会有相同的答案。

这与为什么不能在带有 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实现

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)) 
        {
            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 主体中使用trycatchfinally块,并从这些块中的任何一个调用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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM