Automapper + EF4 + ASP.NET MVC - 获取“上下文处理”错误(我知道原因,但是如何修复它?)

时间:2012-08-01 01:19:05

标签: asp.net-mvc entity-framework closures automapper

我在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始终使用当前上下文而不是初始上下文?

有没有办法使用automapper的“实例”而不是静态Mapper类? 如果可以,建议每次都重新创建映射吗?我担心反思慢下来。

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

3 个答案:

答案 0 :(得分:7)

有可能,但设置有点复杂。我在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的实例,而不是在构造函数中注入它。

如果您有任何问题,请与我们联系。

答案 1 :(得分:2)

我找到了一个并非完全黑客的解决方法。 基本上,我告诉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中完成...

答案 2 :(得分: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));
};