Automapper + EF4 + ASP.NET MVC - getting 'context disposed' error (I know why, but how to fix it?)

扶醉桌前 提交于 2019-12-03 14:12:24

It is possible, but the setup is a bit complicated. I use this in my projects with help of Ninject for dependency injection.

AutoMapper has concept of TypeConverters. Converters provide a way to implement complex operations required to convert certain types in a separate class. If converting Category to CategoryVM requires a database lookup you can implement that logic in custom TypeConverter class similar to this:

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

You then to configure AutoMapper to use your converter:

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

Here comes the tricky part. AutoMapper will need to create a new instance of our converter every time you map values, and it will need to provide DbContext instance for constructor. In my projects I use Ninject for dependency injection, and it is configured to use the same instance of DbContext while processing a request. This way the same instance of DbContext is injected both in your controller and in your AutoMapper converter. The trivial Ninject configuration would look like this:

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

You can of course use some sort of factory pattern to get instance of DbContext instead of injecting it in constructors.

Let me know if you have any questions.

I've found a workaround that's not completely hacky. Basically, I tell AutoMapper to ignore the tricky field and I update it myself.

The updated controller looks like this:

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

Still curious how this could be done from within AutoMapper...

The answer from @LeffeBrune is perfect. However, I want to have the same behavior, but I don't want to map every property myself. Basically I just wanted to override the "ConstructUsing".

Here is what I came up with.

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));
};
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!