7

I have a ASP.NET 5 (running on 4.6.2, not Core) application. I wanted to use the ProjectTo<>() method of AutoMapper to project the results from the database to my viewmodels.

I've tried alot of tests, but it seems that the map solely cannot be found when using the ProjectTo<>(). Using mapper.Map<>() on different locations with the same model and viewmodel perfectly works.

I guess there is something wrong with how AutoMapper works with my DI (Autofac), but I can't figure out what.

Anyway, the code:

Startup.Cs

 public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            (...)

            // Autofac DI
            AutofacContainer = AutofacLoader.Configure(services).Build();

            return AutofacContainer.Resolve<IServiceProvider>();
        }

AutofacLoader.cs

public static ContainerBuilder Configure(IServiceCollection services)
        {
            var builder = new ContainerBuilder();

(...)


            // AutoMapper
            builder.RegisterModule<AutoMapperModule>();

            if (services != null)
            { 
                builder.Populate(services);

            }
            return builder;
        }

AutoMapperModule.cs

public class AutoMapperModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        var mapping = new MapperConfiguration(cfg =>
        {
            cfg.AddProfile(new Core.Mappings.AutoMapperProfileConfiguration());
            cfg.AddProfile(new Dieet.Core.Mappings.AutoMapperProfileConfiguration());
        });
        builder.RegisterInstance(mapping.CreateMapper()).As<IMapper>().AutoActivate();
    }
}

The test that fails with 'Missing map from Patient to PatientViewModel. Create using Mapper.CreateMap'.

   [Fact]
    public async void InfohosServiceReturnsPatientViewModels()
    {
        var db = _container.Resolve<IInfohosDb>();

        var search = new PaginatedSearchBase();
        search.OrderBy = "Naam";

        var mapper = _container.Resolve<IMapper>();

        var result = await search.PagedResultAsAsync<Patient,PatientViewModel >(null,db.Patienten,mapper);
    }

PaginatedSearchBase

public class PaginatedSearchBase
{
    public string OrderBy { get; set; }
    public bool OrderDescending { get; set; }
    public int Page { get; set; } = 1;
    public int PageSize { get; set; } = 10;
}

And finally the extension that calls the ProjectTo

public static class PagedResultExtensions
{
    public static async Task<PagedResult<T>> PagedResultAsync<T>(this PaginatedSearchBase vm, ICollection<Expression<Func<T, bool>>> whereCollection, IEnumerable<T> context) where T : class
    {
        int totalCount;
        var query = PrepareQuery(vm, whereCollection, context, out totalCount);

        return new PagedResult<T>
        {
            Results = await query.ToListAsync(),
            Page = vm.Page,
            PageSize = vm.PageSize,
            Total = totalCount
        };
    }
    public static async Task<PagedResult<TAs>> PagedResultAsAsync<T, TAs>(this PaginatedSearchBase vm, ICollection<Expression<Func<T, bool>>> whereCollection, IEnumerable<T> context, IMapper mapper) where T : class
    {
        int totalCount;
        var query = PrepareQuery(vm, whereCollection, context, out totalCount);

        return new PagedResult<TAs>
        {
----------> Results = await query.ProjectTo<TAs>(mapper).ToListAsync(),
            Page = vm.Page,
            PageSize = vm.PageSize,
            Total = totalCount
        };
    }

    private static IQueryable<T> PrepareQuery<T>(PaginatedSearchBase vm, ICollection<Expression<Func<T, bool>>> whereCollection, IEnumerable<T> context,
        out int totalCount) where T : class
    {
        var query = context.AsQueryable();
        if (whereCollection != null)
        {
            foreach (var w in whereCollection)
            {
                if (w != null)
                {
                    query = query.Where(w);
                }
            }
        }
        // Order by
        query = query.OrderBy($"{vm.OrderBy} {(vm.OrderDescending ? "DESC" : "ASC")}");

        // Total rows
        totalCount = query.Count();

        // Paging
        query = query.Skip((vm.Page - 1)*vm.PageSize).Take(vm.PageSize);
        return query;
    }
}

For information, I'm using versions:

  • "Autofac": "4.0.0-rc1-177"
  • "Autofac.Extensions.DependencyInjection": "4.0.0-rc1-177"
  • "AutoMapper": "4.2.1"

Edit:

A new test that I did to check if the mappings really work:

var mapper = _container.Resolve<IMapper>();
        var p = new Patient();
        p.Naam = "Test";
        var vm = mapper.Map<PatientViewModel>(p);

        vm.Naam.ShouldBeEquivalentTo("Test");

This test passes

Edit 2:

When I use the Map<> in a Select() instead, it works too, so it's really the ProjectTo<>() that fails:

var results = await query.ToListAsync();
        return new PagedResult<TAs>
        {
            Results = results.Select(mapper.Map<TAs>).ToList(),
            Page = vm.Page,
            PageSize = vm.PageSize,
            Total = totalCount
        };

This works, but it requires the mapper to be included and not be injected and it doesn't use the ProjectTo that Automapper has for database access...

4

2 回答 2

13

我遇到了同样的问题,但设法让它工作。这是由于 Automapper 最近采取的举措,不再让整个 API 使用静态方法。现在一切都是基于实例的,静态扩展方法不再知道映射配置,现在必须将它们传递给方法。我最终将 MapperConfiguration 的一个实例注册为 IConfigurationProvider(我正在使用 Unity)

container.RegisterInstance(typeof (IConfigurationProvider), config);

这被注入到我的查询处理程序中:

[Dependency]
public IConfigurationProvider MapperConfigurationProvider { get; set; }

最后,将 MapperConfigurationProvider 传递给对 ProjectTo 的调用:

.ProjectTo<Payment>(MapperConfigurationProvider);

希望这可以帮助。

于 2016-06-07T14:34:24.523 回答
5

您不必专门将 ConfigurationProvider 添加到 DI。如果您已经将 IMapper 添加到 DI,则可以从 Mapper 本身读取 ConfigurationProvider。示例:我遇到了同样的问题,并创建了一个包含注入的 IMapper 的基类:

public abstract class ServiceBase { public IMapper Mapper { get; set; } }

这个类在我所有使用 AutoMapper 的服务中被继承。现在,每当我的任何服务需要映射某些东西时,他们都会这样做:

    return context.SomeEntity
        .Where(e => e.Id == filter.Id)
        .ProjectTo<EntityDto>(Mapper.ConfigurationProvider).ToList();

随着 Mapper 被注入。只要你把完全配置好的Mapper放到DI里面就可以了。

container.Register(Component.For<IMapper>().UsingFactoryMethod(x =>
    {
        return new AutoMapperConfig().ConfigureMapper();
    })
    );
于 2018-11-07T12:53:27.880 回答