简体   繁体   English

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

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

  • "IAsyncEnumerable" which is relatively new. “IAsyncEnumerable”相对较新。
  • Postgresql (for which the answer uses an internal property) Postgresql(答案使用内部属性)
  • This question has a better title, explicitly referring to "Transaction" context.这个问题有一个更好的标题,明确提到“交易”上下文。 Other contexts with the same error message won't have the same answer.具有相同错误消息的其他上下文不会有相同的答案。

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

相关问题 如何使用 SqlBulkCopy 编写 IAsyncEnumerable - How to use SqlBulkCopy to write an IAsyncEnumerable 我怎样才能“适应”一个任务<ienumerable<t> &gt; 到 IAsyncEnumerable<t> ? </t></ienumerable<t> - How can I "adapt" a Task<IEnumerable<T>> to IAsyncEnumerable<T>? 如何模拟 NpgsqlTransaction 和 NpgsqlConnection? - how to mock NpgsqlTransaction and NpgsqlConnection? 我们如何使用“收益回报”从 MediatR 请求处理程序返回 IAsyncEnumerable? - How can we use 'yield return' to return IAsyncEnumerable from MediatR Request handler? 我可以(或应该)使用 IAsyncEnumerable<T> 而不是任务<ActionResult<IEnumerable<T> &gt;&gt; 在 Web API 控制器中 - Can (or should) I use IAsyncEnumerable<T> instead of Task<ActionResult<IEnumerable<T>>> in a Web API Controller 在构造函数中调用 IAsyncEnumerable - Calling IAsyncEnumerable inside constructor 如何正确使用任务? - How can I use Tasks correctly? 如何编写 IAsyncEnumerable function 以便始终执行清理代码 - How to write an IAsyncEnumerable function so that cleanup code always executes 在 function 中迭代 IAsyncEnumerable 并返回 IAsyncEnumerable 并取消 - Iterating an IAsyncEnumerable in a function returning an IAsyncEnumerable with cancellation 如何等待多个 IAsyncEnumerable - How to await multiple IAsyncEnumerable
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM