web-dev-qa-db-ja.com

オートマッパーで非同期結果をマッピングする

私たちはangularjsアプリケーションのWeb.Apiアプリケーションを作成しています。 Web.Apiはjsonの結果を返します。

ステップ1はデータを取得することでした:

    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());
    }

この瞬間、オートマッパーは混乱します。最初に、次のマッパールールがありました。Mapper.CreateMap();

これは、WebAPIメソッドが完全に非同期になるまで機能しました。例外はそれがからの地図が欠けていたと言った

 Task<ICollection<Data>> to Task<ICollection<DataItem>>.

マッパーはに変更されました

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

構成を検証するときに、結果をマップできないと文句を言います。マッピングをどのように構成する必要がありますか?

13
Patrick

非同期データフェッチをMap呼び出しから移動する必要があります。

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

または、AutoMapperLINQプロジェクションを使用することもできます。

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

あなたのリポジトリがIQueryableを直接公開しているかどうかはわかりません(リポジトリは使用していません)。私たちのアプリは最近、ほとんど独占的に2番目のバージョンを使用しています。

24
Jimmy Bogard

これを行うタスク拡張メソッドをいくつか追加することもできます。

    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;
    }
}
1
Will