使用自动映射器映射异步结果

Map async result with automapper

我们正在创建 angularjs 应用程序的 Web.Api 应用程序。 Web.Api returns 一个 json 结果。

第一步是获取数据:

    public List<DataItem>> GetData()
    {
        return Mapper.Map<List<DataItem>>(dataRepository.GetData());
    }

这很有效。然后我们使数据回购异步,我们更改代码以使用它。

    public List<DataItem>> GetData()
    {
        return Mapper.Map<List<DataItem>>(dataRepository.GetDataAsync().Result);
    }

还是没问题。现在我们想让我的 Web.Api 完全异步,所以我们将其更改为:

    public async Task<List<DataItem>> GetData()
    {
        return await Mapper.Map<Task<List<DataItem>>>(dataRepository.GetDataAsync());
    }

此时 Automapper 感到困惑。首先,我们有以下映射器规则: Mapper.CreateMap();

这一直有效,直到网络 api 方法变得完全异步。异常表示它缺少来自

的地图
 Task<ICollection<Data>> to Task<ICollection<DataItem>>.

映射器已更改为

Mapper.CreateMap<Task<List<Data>>, Task<List<DataItem>>>();

验证配置时,它抱怨无法映射结果。我们应该如何配置映射?

您需要将异步数据提取移出地图调用:

var data = await dataRepository.GetDataAsync();
return Mapper.Map<List<DataItem>>(data);

或者,您可以使用 AutoMapper LINQ 投影:

var data = await dbContext.Data.ProjectTo<DataItem>().ToListAsync();

我不知道您的存储库是否直接公开了 IQueryable(我们不使用存储库)。这些天我们的应用程序几乎只使用第二个版本。

您还可以添加一些任务扩展方法来为您执行此操作:

    public static Task<TReturn> Convert<T, TReturn>(this Task<T> task)
    {
        if (task == null)
            throw new ArgumentNullException(nameof(task));

        var tcs = new TaskCompletionSource<TReturn>();

        task.ContinueWith(t => tcs.TrySetCanceled(), TaskContinuationOptions.OnlyOnCanceled);
        task.ContinueWith(t =>
        {
            tcs.TrySetResult(Mapper.Map<T, TReturn>(t.Result));
        }, TaskContinuationOptions.OnlyOnRanToCompletion);
        task.ContinueWith(t => tcs.TrySetException(t.Exception), TaskContinuationOptions.OnlyOnFaulted);

        return tcs.Task;
    }


    public static Task<List<TReturn>> ConvertEach<T, TReturn>(this Task<List<T>> task)
    {
        if (task == null)
            throw new ArgumentNullException(nameof(task));

        var tcs = new TaskCompletionSource<List<TReturn>>();

        task.ContinueWith(t => tcs.TrySetCanceled(), TaskContinuationOptions.OnlyOnCanceled);
        task.ContinueWith(t =>
        {
            tcs.TrySetResult(t.Result.Select(Mapper.Map<T, TReturn>).ToList());
        }, TaskContinuationOptions.OnlyOnRanToCompletion);
        task.ContinueWith(t => tcs.TrySetException(t.Exception), TaskContinuationOptions.OnlyOnFaulted);

        return tcs.Task;
    }

Jose 的解决方案对我很有用。但是,我确实将扩展方法重新定位为扩展 IMapper,这允许我删除单例 Mapper 引用。

public static class MapperExtensions
{
    public static Task<TResult> MapAsync<TSource, TResult>(this IMapper mapper, Task<TSource> task)
    {
        if (task == null)
        {
            throw new ArgumentNullException(nameof(task));
        }

        var tcs = new TaskCompletionSource<TResult>();

        task
            .ContinueWith(t => tcs.TrySetCanceled(), TaskContinuationOptions.OnlyOnCanceled);

        task
            .ContinueWith
            (
                t =>
                {
                    tcs.TrySetResult(mapper.Map<TSource, TResult>(t.Result));
                },
                TaskContinuationOptions.OnlyOnRanToCompletion
            );

        task
            .ContinueWith
            (
                t => tcs.TrySetException(t.Exception),
                TaskContinuationOptions.OnlyOnFaulted
            );

        return tcs.Task;
    }
}

我最近也遇到了这个问题。我想保留该方法的异步部分,因此,我最终得到了一个将等待操作包装在异步任务中的解决方案。

public async Task<ActionResult<ProjectDTO>> GetProject(long id)
        {
             return await Task<ProjectDTO>.Run(
                async () => {
                        var project = await _context.MDb.FindAsync(id);
                        return _mapper.Map<ProjectDTO>(project);            
                }
            );
        }

我很想听听你的 thoughts/comments。