6

我在 MVC 控制器操作中有这个非常基本的代码。它将模型类映射Operation到一个非常基本OperationVM的视图模型类。

public class OperationVM: Operation 
{
    public CategoryVM CategoryVM { get; set; }
}

我需要加载完整的类别列表才能创建 CategoryVM 实例。
这是我(尝试)创建一个List<OperationVM>在视图中显示的方法。

public class OperationsController : Controller {

    private SomeContext context = new SomeContext ();

    public ViewResult Index()
    {
        var ops = context.Operations.Include("blah...").ToList();
        Mapper.CreateMap<Operation, OperationVM>()
            .ForMember(
                dest => dest.CategoryVM, 
                opt => opt.MapFrom(
                    src => CreateCatVM(src.Category, context.Categories)
                    //  trouble here ----------------^^^^^^^
                )
            );
        var opVMs = ops.Select(op => Mapper.Map<Operation, OperationVM>(op))
                       .ToList();

        return View(opVMs);
    }
}

我第一次点击页面时一切都很好。问题是,映射器对象是静态的。因此,在调用 时Mapper.CreateMap(),当前的实例DbContext保存在给 CreateMap() 的闭包中。

我第二次点击页面时,静态地图已经到位,仍然使用对初始的引用,现在已处置,DbContext.

确切的错误是:

The operation cannot be completed because the DbContext has been disposed.

问题是:如何使 AutoMapper 始终使用当前上下文而不是初始上下文?

有没有办法使用自动映射器的“实例”而不是静态Mapper类?如果可以,是否建议每次都重新创建映射?我担心反射速度变慢。

我阅读了一些关于自定义解析器的信息,但我遇到了类似的问题 - 如何让自定义解析器使用当前上下文?

4

3 回答 3

8

这是可能的,但设置有点复杂。我在 Ninject 的帮助下在我的项目中使用它来进行依赖注入。

AutoMapper 有 TypeConverters 的概念。转换器提供了一种方法来实现在单独的类中转换某些类型所需的复杂操作。如果将 Category 转换为 CategoryVM 需要数据库查找,您可以在自定义 TypeConverter 类中实现该逻辑,类似于:

using System;
using AutoMapper;

public class CategoryToCategoryVMConverter : 
        TypeConverter<Category, CategoryVM>
{
    public CategoryToCategoryVMConverter(DbContext context)
    {
        this.Context = context;
    }

    private DbContext Context { get; set; }

    protected override CategoryVM ConvertCore(Category source)
    {
        // use this.Context to lookup whatever you need
        return CreateCatVM(source, this.Context.Categories);
    }
}

然后您配置 AutoMapper 以使用您的转换器:

Mapper.CreateMap<Category, CategoryVM>().ConvertUsing<CategoryToCategoryVMConverter>();

棘手的部分来了。每次映射值时,AutoMapper 都需要创建转换器的新实例,并且需要为构造函数提供 DbContext 实例。在我的项目中,我使用 Ninject 进行依赖注入,它被配置为在处理请求时使用相同的 DbContext 实例。这样,在控制器和 AutoMapper 转换器中都注入了相同的 DbContext 实例。简单的 Ninject 配置如下所示:

Bind<DbContext>().To<SomeContext>().InRequestScope();

您当然可以使用某种工厂模式来获取 DbContext 的实例,而不是在构造函数中注入它。

如果您有任何问题,请告诉我。

于 2012-09-07T18:29:19.497 回答
2

我找到了一个不完全hacky的解决方法。基本上,我告诉 AutoMapper 忽略棘手的字段,然后我自己更新它。

更新后的控制器如下所示:

public class OperationsController : Controller {

    private SomeContext context = new SomeContext ();

    public ViewResult Index()
    {
        var ops = context.Operations.Include("blah...").ToList();
        Mapper.CreateMap<Operation, OperationVM>()
            .ForMember(dest => dest.CategoryVM, opt => opt.Ignore());

        var opVMs = ops.Select(
            op => {
                var opVM = Mapper.Map<Operation, OperationVM>(op);
                opVM.CategoryVM = CreateCatVM(op.Category, context.Categories);
                return opVM;
            })
            .ToList();

        return View(opVMs);
    }
}

仍然很好奇如何从 AutoMapper 中完成这...

于 2012-08-01T02:14:41.603 回答
0

@LeffeBrune 的答案是完美的。但是,我希望有相同的行为,但我不想自己映射每个属性。基本上我只是想覆盖“ConstructUsing”。

这是我想出的。

public static class AutoMapperExtension
{
    public static void ConstructUsingService<TSource, TDestination>(this IMappingExpression<TSource, TDestination> mappingExression, Type typeConverterType)
    {
        mappingExression.ConstructUsing((ResolutionContext ctx) =>
        {
            var constructor = (IConstructorWithService<TSource, TDestination>)ctx.Options.ServiceCtor.Invoke(typeConverterType);
            return constructor.Construct((TSource)ctx.SourceValue);
        });
    }
}

public class CategoryToCategoryVMConstructor : IConstructorWithService<Category, CategoryVM>
{
    private DbContext dbContext;

    public DTOSiteToHBTISiteConverter(DbContext dbContext)
    {
        this.dbContext = dbContext;
    }

    public CategoryVM Construct(Category category)
    {
        // Some commands here
        if (category.Id > 0)
        {
            var vmCategory = dbContext.Categories.FirstOrDefault(m => m.Id == category.Id);
            if (vmCategory == null)
            {
                throw new NotAllowedException();
            }

            return vmCategory;
        }

        return new CategoryVM();
    }
}

// Initialization
Mapper.Initialize(cfg =>
{
    cfg.ConstructServicesUsing(type => nInjectKernelForInstance.Get(type));
    cfg.CreateMap<Category, CategoryVM>().ConstructUsingService(typeof(CategoryToCategoryVMConstructor));
};
于 2015-03-05T21:44:04.640 回答