1

我有几种方法都结束了:

while (await cursor.MoveNextAsync())
{
    foreach (FieldServiceAppointment appointment in cursor.Current)
    {
        yield return appointment;
    }
}

例如:

public async IAsyncEnumerable<FieldServiceAppointment> GetEventWithWorkOrderType(
    string workOrderType, string customerId)
{
    using IAsyncCursor<FieldServiceAppointment> cursor = await FieldServiceAppointments.FindAsync(
        x => x.BusinessEventTypeCode == workOrderType
            && x.CustomerCode == customerId
        );

    while (await cursor.MoveNextAsync())
    {
        foreach (FieldServiceAppointment appointment in cursor.Current)
        {
            yield return appointment;
        }
    }
}

我想删除这个重复。

如果我尝试将其重构为:

public async IAsyncEnumerable<FieldServiceAppointment> GetEventWithWorkOrderType(
    string workOrderType, string customerId)
{
    using IAsyncCursor<FieldServiceAppointment> cursor = await FieldServiceAppointments.FindAsync(
        x => x.BusinessEventTypeCode == workOrderType
            && x.CustomerCode == customerId
        );
    YieldAppointments(cursor);
}

public async IAsyncEnumerable<FieldServiceAppointment> YieldAppointments(
    IAsyncCursor<FieldServiceAppointment> cursor)
{
    while (await cursor.MoveNextAsync())
    {
        foreach (FieldServiceAppointment appointment in cursor.Current)
        {
            yield return appointment;
        }
    }
}

它不会编译,因为我无法从迭代器返回值。

如果我尝试 return yield return YieldAppointments(cursor);,它将无法编译,因为:

严重性代码描述项目文件行抑制状态错误 CS0266 无法将类型“System.Collections.Generic.IAsyncEnumerable<DataAccessLayer.Entities.Praxedo.FieldServiceAppointment>”隐式转换为“DataAccessLayer.Entities.Praxedo.FieldServiceAppointment”。存在显式转换(您是否缺少演员表?) DataAccessLayer C:\projects\EnpalPraxedoIntegration\DataAccessLayer\DbServices\FieldServiceAutomationDbService.cs 78 Active

所以我试图

yield return (IAsyncEnumerable<FieldServiceAppointment>) YieldAppointments(cursor);

yield return YieldAppointments(cursor) as IAsyncEnumerable <FieldServiceAppointment>;

其中任何一个都会产生以下编译器错误:

严重性代码描述项目文件行抑制状态错误 CS0266 无法将类型“System.Collections.Generic.IAsyncEnumerable<DataAccessLayer.Entities.Praxedo.FieldServiceAppointment>”隐式转换为“DataAccessLayer.Entities.Praxedo.FieldServiceAppointment”。存在显式转换(您是否缺少演员表?) DataAccessLayer C:\projects\EnpalPraxedoIntegration\DataAccessLayer\DbServices\FieldServiceAutomationDbService.cs 78 Active

所以我尝试了:

public async IAsyncEnumerable<FieldServiceAppointment> GetEventWithWorkOrderType(
    string workOrderType, string customerId)
{
    using IAsyncCursor<FieldServiceAppointment> cursor = await FieldServiceAppointments.FindAsync(
        x => x.BusinessEventTypeCode == workOrderType
            && x.CustomerCode == customerId
        );
    yield return await YieldAppointments(cursor);
}

public async Task<FieldServiceAppointment> YieldAppointments(
    IAsyncCursor<FieldServiceAppointment> cursor)
{
    while (await cursor.MoveNextAsync())
    {
        foreach (FieldServiceAppointment appointment in cursor.Current)
        {
            yield return appointment;
        }
    }
}

但这不会编译,因为

严重性代码描述项目文件行抑制状态错误 CS1624“FieldServiceAutomationDbService.YieldAppointments(IAsyncCursor)”的主体不能是迭代器块,因为“任务”不是迭代器接口类型 DataAccessLayer C:\projects\EnpalPraxedoIntegration\DataAccessLayer\DbServices\FieldServiceAutomationDbService。 cs 81 主动

有没有办法使这项工作?

4

2 回答 2

3

YieldAppointments用一个Task给你一个光标的参数化怎么样?

public async IAsyncEnumerable<FieldServiceAppointment> YieldAppointments(Func<Task<IAsyncCursor<FieldServiceAppointment>>> cursorTask)
{
    using var cursor = await cursorTask();
    while (await cursor.MoveNextAsync())
    {
        foreach (FieldServiceAppointment appointment in cursor.Current)
        {
            yield return appointment;
        }
    }
}

GetEventWithWorkOrderType现在您可以在 lambda 中编写(获取光标的位置,以及您可能执行的任何其他异步操作)的第一部分:

public IAsyncEnumerable<FieldServiceAppointment> GetEventWithWorkOrderType(string workOrderType, string customerId)
    => YieldAppointments(async () =>
        await FieldServiceAppointments.FindAsync(
            x => x.BusinessEventTypeCode == workOrderType
                && x.CustomerCode == customerId
        );
    );
于 2021-10-21T10:34:01.667 回答
1

解决此问题的AsyncEnumerableEx.Using一种方法是使用System.Interactive.Async包中的方法,以及Tom Gringauz的此答案ToAsyncEnumerable中的扩展方法。

以下是该AsyncEnumerableEx.Using方法的签名:

public static IAsyncEnumerable<TSource> Using<TSource, TResource>(
    Func<Task<TResource>> resourceFactory,
    Func<TResource, ValueTask<IAsyncEnumerable<TSource>>> enumerableFactory)
    where TResource : IDisposable;

以下是ToAsyncEnumerable从上述答案复制粘贴的扩展方法,增强了取消支持:

public static async IAsyncEnumerable<T> ToAsyncEnumerable<T>(
    this IAsyncCursor<T> asyncCursor,
    [EnumeratorCancellation]CancellationToken cancellationToken = default)
{
    while (await asyncCursor.MoveNextAsync(cancellationToken).ConfigureAwait(false))
        foreach (var current in asyncCursor.Current)
            yield return current;
}

以下是如何结合这两种方法,以解决您的问题:

public IAsyncEnumerable<FieldServiceAppointment> GetEventWithWorkOrderType(
    string workOrderType, string customerId)
{
    return AsyncEnumerableEx.Using(() => FieldServiceAppointments
        .FindAsync(x => x.BusinessEventTypeCode == workOrderType &&
            x.CustomerCode == customerId),
        cursor => new ValueTask<IAsyncEnumerable<FieldServiceAppointment>>(cursor.ToAsyncEnumerable()));
}

AsyncEnumerableEx.Using有一个过于灵活的参数enumerableFactory,(在你的情况下)需要将cursor.ToAsyncEnumerable()调用包装在一个冗长而嘈杂的ValueTask中。AsyncEnumerableEx.Using您可以通过将 包装在自己的Using方法中来摆脱这种烦恼:

public static IAsyncEnumerable<TSource> Using<TSource, TResource>(
    Func<Task<TResource>> resourceFactory,
    Func<TResource, IAsyncEnumerable<TSource>> enumerableFactory)
    where TResource : IDisposable
{
    return AsyncEnumerableEx.Using(resourceFactory,
        resource => new ValueTask<IAsyncEnumerable<TSource>>(
            enumerableFactory(resource)));
}

使用这种方法,解决方案变得更简单,更具可读性:

public IAsyncEnumerable<FieldServiceAppointment> GetEventWithWorkOrderType(
    string workOrderType, string customerId)
{
    return Using(() => FieldServiceAppointments
        .FindAsync(x => x.BusinessEventTypeCode == workOrderType &&
            x.CustomerCode == customerId),
        cursor => cursor.ToAsyncEnumerable());
}

替代方案:以下是一个自给自足且不那么抽象的替代方案。FromAsyncCursor通用方法接受一个asyncCursorFactory, 并返回一个包含游标发出的文档的序列:

public async static IAsyncEnumerable<TDocument> FromAsyncCursor<TDocument>(
    Func<Task<IAsyncCursor<TDocument>>> asyncCursorFactory,
    [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
    using var asyncCursor = await asyncCursorFactory();
    while (await asyncCursor.MoveNextAsync(cancellationToken).ConfigureAwait(false))
        foreach (var current in asyncCursor.Current)
            yield return current;
}

使用示例:

public IAsyncEnumerable<FieldServiceAppointment> GetEventWithWorkOrderType(
    string workOrderType, string customerId)
{
    return FromAsyncCursor(() => FieldServiceAppointments.FindAsync(
        x => x.BusinessEventTypeCode == workOrderType && x.CustomerCode == customerId));
}

EnumeratorCancellation属性可以使用生成的序列,同时观察CancellationToken

var cts = new CancellationTokenSource();
await foreach (var item in GetEventWithWorkOrderType("xxx", "yyy")
    .WithCancellation(cts.Token))
{
    //...
}

尽管该GetEventWithWorkOrderType方法本身不接受 a ,但由于该属性CancellationToken,令牌会神奇地传播到该方法。FromAsyncCursor不过,所有这些都可能有点学术性,因为单个文件不太可能MoveNextAsync花费这么多时间来获取下一批文档,以使添加取消支持成为一个令人信服的提议。

于 2021-10-21T12:43:07.897 回答