Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Map async result with automapper

Tags:

We are createing a Web.Api application of a angularjs application. The Web.Api returns a json result.

Step one was getting the data:

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

That worked like a charm. Then we made the data repo async and we changed to code to work with it.

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

Stil no problems. Now we wanted to make my Web.Api full async awayt so we changed it to:

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

At this moment Automapper gets confused. First we had the following mapper rule: Mapper.CreateMap();

This worked until the web api method became full async. The exception said it was missing a map from

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

The mapper was changed to

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

When validating the configuration it complains that Result cannot be mapped. How should we configure the mappings?

like image 500
Patrick Avatar asked Sep 16 '15 13:09

Patrick


People also ask

Can AutoMapper map collections?

AutoMapper supports polymorphic arrays and collections, such that derived source/destination types are used if found.

What is AutoMapper in C#?

AutoMapper in C# is a library used to map data from one object to another. It acts as a mapper between two objects and transforms one object type into another. It converts the input object of one type to the output object of another type until the latter type follows or maintains the conventions of AutoMapper.

Where is AutoMapper configuration?

Where do I configure AutoMapper? ¶ Configuration should only happen once per AppDomain. That means the best place to put the configuration code is in application startup, such as the Global.


2 Answers

You need to move the async data fetch out of the Map call:

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

Alternatively, you can use AutoMapper LINQ projections:

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

I don't know if your repository exposes IQueryable directly (we don't use repositories). Our apps use the second version pretty much exclusively these days.

like image 64
Jimmy Bogard Avatar answered Sep 24 '22 19:09

Jimmy Bogard


Jose's solution proved quite useful to me. I did, however, re-target the extension method to extend IMapper which allowed me to remove the singleton Mapper reference.

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;
    }
}
like image 26
Will Avatar answered Sep 25 '22 19:09

Will