通过IAsyncEnumerable

问题描述

在我的存储库代码中,我得到了一个一次性的数据库实例。然后,我用它返回我的对象​​类型的IAsyncEnumable。我遇到的问题是,在枚举完全发生之前就已处置了数据库对象-因此,连接是从其下面关闭的。解决这个问题的模式是什么? (如果很重要-不应该-这就是npoco)。

我正在编辑一个问题,以说它特定于IAsyncEnumerable,这样在这种情况下等待的逐行读取更合适,而不是组装整个结果列表并立即返回。 >

    public IAsyncEnumerable<T_AccountViewProperty> RetrieveManyAsync(AccountViewId input)
    {
        var database = GetDatabase();
        return database.Query<T_AccountViewProperty>()
            .Where(x => x.AccountViewId == (int)input)
            .ToEnumerableAsync()
            .disposeWhenCompleted(database);     // <-- is this a thing?
    }

解决方法

不,DisposeWhenCompleted(database)无关紧要。但是,如果您为其编写扩展方法,则可能是这样。例如:

public static async IAsyncEnumerable<T> DisposeWhenCompleted<T>(
    this IAsyncEnumerable<T> source,IDisposable disposable)
{
    using (disposable)
    {
        await foreach (T t in source)
        {
            yield return t;
        }
    }
}

那将做您想要的。也就是说,我发现using语句在显式使用时更好。恕我直言,上面的结构并不清楚,只是将using放在方法本身中,即使后者更冗长:

public IAsyncEnumerable<T_AccountViewProperty> RetrieveManyAsync(AccountViewId input)
{
    using (var database = GetDatabase())
    {
        var result = database.Query<T_AccountViewProperty>()
            .Where(x => x.AccountViewId == (int)input)
            .ToEnumerableAsync()

        await foreach (T_AccountViewProperty x in result)
        {
            yield return x;
        }
    }
}

如上所述,这样做的另一个好处是,它效率更高,尤其是当您要处置多个项目时(如果有多个项目,您可以只链接DisposeWhenCompleted()方法调用,但这会创建一个每个需要处理的项目都有一个新的迭代器。

就像常规的迭代器方法一样,C#编译器将确保在将代码重新编写为状态机时,finally块创建的隐式using不会被执行,直到迭代实际完成为止。完成。实际上,对于常规async方法也是如此,就像原始的迭代器方法和新的async迭代器方法一样,它们具有与方法在代码返回之前返回给调用者相同的属性。该方法实际上已经完成。

这很有意义,因为常规的async方法及其对应的迭代器本质上都是原始迭代器方法的后代。这三种方法都具有将方法重写为状态机的相同基本概念。

,

根据彼得·杜尼奥(Peter Duniho)的回答,我最终采取的策略是这样的扩展方法:

public static class TaskExtensions
{
    public static Task<TResult> DisposeAfter<TResult>(this Task<TResult> input,IDisposable disposable)
    {
        TResult ContinuationFunction(Task<TResult> t)
        {
            using (disposable) return t.Result;
        }

        return input.ContinueWith(ContinuationFunction,TaskContinuationOptions.OnlyOnRanToCompletion);
    }
}

这样使用:

    public Task<T_Account> RetrieveAsync(AccountId input)
    {
        var database = GetDatabase();
        return database.Query<T_Account>()
            .SingleAsync(x => x.AccountId == (int) input)
            .DisposeAfter(database);
    }

解决“是否需要“ OnlyOnRanToCompletion”是Future Bryan的问题,但这确实可以满足我的需求。