Ninject MVC 2, EF 4, AutoMapper — ObjectContext удален

Полностью переработано:

Хорошо, я использую Ninject с расширением MVC 2 в качестве своего контейнера DI и AutoMapper в качестве своего объекта ‹--> сопоставитель модели представления. Я получаю сообщение об ошибке «ObjectContext is disposed» в моей модели представления -> сопоставление сущностей. Мой код ниже.

Привязки Ninject:

public class DIModule : NinjectModule
{
    public override void Load()
    {
        this.Bind<HGEntities>().ToSelf().InRequestScope();
        this.Bind<IArticleRepository>().To<HGArticleRepository>().InRequestScope();
        this.Bind<IGameRepository>().To<HGGameRepository>().InRequestScope();
        this.Bind<INewsRepository>().To<HGNewsRepository>().InRequestScope();
        this.Bind<ErrorController>().ToSelf().InRequestScope();
        this.Bind<HGController>().ToSelf().InRequestScope();
    }
}

Мой репозиторий:

public class HGGameRepository : IGameRepository, IDisposable
{
    private HGEntities _context;

    public HGGameRepository(HGEntities context)
    {
        this._context = context;
    }

    // methods

    public void SaveGame(Game game)
    {
        if (game.GameID > 0)
        {
            _context.ObjectStateManager.ChangeObjectState(game, System.Data.EntityState.Modified);
        }
        else
        {
            _context.Games.AddObject(game);
        }

        _context.SaveChanges();
    }

    public void Dispose()
    {
        if (this._context != null)
        {
            this._context.Dispose();
        }
    }
}

Конструктор моего контроллера, который включает в себя определения моей карты AutoMapper:

    public AdminController(IArticleRepository articleRepository, IGameRepository gameRepository, INewsRepository newsRepository)
    {
        _articleRepository = articleRepository;
        _gameRepository    = gameRepository;
        _newsRepository    = newsRepository;

        Mapper.CreateMap<Game, AdminGameViewModel>()
            .BeforeMap((s, d) =>
            {
                int platCount = s.Platforms.Count;
                var plats = s.Platforms.ToArray();
                d.PlatformIDs = new int[platCount];

                for (int i = 0; i < platCount; ++i)
                {
                    d.PlatformIDs[i] = plats[i].PlatformID;
                }
            })
            .ForMember(dest => dest.Pros, opt => opt.MapFrom(src => src.Pros.Split(new char[] {'|'})))
            .ForMember(dest => dest.Cons, opt => opt.MapFrom(src => src.Cons.Split(new char[] {'|'})))
            .ForMember(dest => dest.PlatformIDs, opt => opt.Ignore());

        Mapper.CreateMap<AdminGameViewModel, Game>()
            .BeforeMap((s, d) =>
            {
                if (d.Platforms != null && d.Platforms.Count > 0)
                {
                    var oldPlats = d.Platforms.ToArray();

                    foreach (var oldPlat in oldPlats)
                    {
                        d.Platforms.Remove(oldPlat);
                    }
                }

                foreach (var platId in s.PlatformIDs)
                {
                    var plat = _gameRepository.GetPlatform(platId);
                    d.Platforms.Add(plat);
                }
            })
            .ForMember(dest => dest.Platforms, opt => opt.Ignore())
            .ForMember(dest => dest.BoxArtPath, opt => opt.Ignore())
            .ForMember(dest => dest.IndexImagePath, opt => opt.Ignore())
            .ForMember(dest => dest.Cons, opt => opt.MapFrom(src => string.Join("|", src.Cons)))
            .ForMember(dest => dest.Pros, opt => opt.MapFrom(src => string.Join("|", src.Pros)))
            .ForMember(dest => dest.LastModified, opt => opt.UseValue(DateTime.Now));
    }

Здесь важно второе сопоставление.

Далее мой метод редактирования:

    [HttpPost]
    public ActionResult EditGame([Bind(Prefix="GameData")]AdminGameViewModel formData)
    {
        Game game = _gameRepository.GetGame(formData.GameID);

        if (ModelState.IsValid)
        {
            game = AutoMapper.Mapper.Map<AdminGameViewModel, Game>(formData, game);

    // it dies here, so the rest of the method is immaterial
    }

Наконец, трассировка стека:

[ObjectDisposedException: The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.]
System.Data.Objects.ObjectContext.EnsureConnection() +87
System.Data.Objects.ObjectQuery`1.GetResults(Nullable`1 forMergeOption) +90
System.Data.Objects.ObjectQuery`1.System.Collections.Generic.IEnumerable<T>.GetEnumerator() +96
System.Linq.Enumerable.FirstOrDefault(IEnumerable`1 source) +182
System.Data.Objects.ELinq.ObjectQueryProvider.<GetElementFunction>b__1(IEnumerable`1 sequence) +74
System.Data.Objects.ELinq.ObjectQueryProvider.ExecuteSingle(IEnumerable`1 query, Expression queryRoot) +95
System.Data.Objects.ELinq.ObjectQueryProvider.System.Linq.IQueryProvider.Execute(Expression expression) +163
System.Linq.Queryable.FirstOrDefault(IQueryable`1 source, Expression`1 predicate) +300
HandiGamer.Domain.Concrete.HGGameRepository.GetPlatform(Int32 id) in C:\Users\Kevin\documents\visual studio 2010\Projects\HandiGamer\HandiGamer.Domain\Concrete\HGGameRepository.cs:68
HandiGamer.WebUI.Controllers.AdminController.<.ctor>b__a(AdminGameViewModel s, Game d) in C:\Users\Kevin\documents\visual studio 2010\Projects\HandiGamer\HandiGamer\Controllers\AdminController.cs:56
AutoMapper.<>c__DisplayClass1b.<BeforeMap>b__1a(Object src, Object dest) +139
AutoMapper.TypeMap.<get_BeforeMap>b__0(Object src, Object dest) +118
AutoMapper.Mappers.PropertyMapMappingStrategy.Map(ResolutionContext context, IMappingEngineRunner mapper) +196
AutoMapper.Mappers.TypeMapMapper.Map(ResolutionContext context, IMappingEngineRunner mapper) +256
AutoMapper.MappingEngine.AutoMapper.IMappingEngineRunner.Map(ResolutionContext context) +459

[AutoMapperMappingException: 

Mapping types:
AdminGameViewModel -> Game
HandiGamer.WebUI.ViewModels.AdminGameViewModel -> HandiGamer.Domain.Entities.Game

Destination path:
Game

Source value:
HandiGamer.WebUI.ViewModels.AdminGameViewModel]
AutoMapper.MappingEngine.AutoMapper.IMappingEngineRunner.Map(ResolutionContext context) +537
AutoMapper.MappingEngine.Map(Object source, Object destination, Type sourceType, Type destinationType, Action`1 opts) +179
AutoMapper.MappingEngine.Map(TSource source, TDestination destination, Action`1 opts) +190
AutoMapper.MappingEngine.Map(TSource source, TDestination destination) +146
AutoMapper.Mapper.Map(TSource source, TDestination destination) +105
HandiGamer.WebUI.Controllers.AdminController.EditGame(AdminGameViewModel formData) in C:\Users\Kevin\documents\visual studio 2010\Projects\HandiGamer\HandiGamer\Controllers\AdminController.cs:323
lambda_method(Closure , ControllerBase , Object[] ) +162
System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters) +51
System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters) +409
System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters) +52
System.Web.Mvc.<>c__DisplayClassd.<InvokeActionMethodWithFilters>b__a() +127
System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func`1 continuation) +436
System.Web.Mvc.<>c__DisplayClassf.<InvokeActionMethodWithFilters>b__c() +61
System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodWithFilters(ControllerContext controllerContext, IList`1 filters, ActionDescriptor actionDescriptor, IDictionary`2 parameters) +305
System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName) +830
System.Web.Mvc.Controller.ExecuteCore() +136
System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext) +111
System.Web.Mvc.ControllerBase.System.Web.Mvc.IController.Execute(RequestContext requestContext) +39
System.Web.Mvc.<>c__DisplayClass8.<BeginProcessRequest>b__4() +65
System.Web.Mvc.Async.<>c__DisplayClass1.<MakeVoidDelegate>b__0() +44
System.Web.Mvc.Async.<>c__DisplayClass8`1.<BeginSynchronous>b__7(IAsyncResult _) +42
System.Web.Mvc.Async.WrappedAsyncResult`1.End() +141
System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +54
System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +40
System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) +52
System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result) +38
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +690
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +194

Следуя процессу в отладчике, мое соединение ObjectContext (HGEntities) остается неповрежденным до тех пор, пока не будет вызвана моя модель представления -> игровая карта. По какой-то причине соединение обрывается в этой точке. Любые идеи относительно того, почему это происходит?


person Major Productions    schedule 10.05.2012    source источник
comment
попробуйте сделать сопоставление вручную, посмотрите, сработает ли это   -  person Omu    schedule 14.05.2012
comment
Почему вы размещаете контекст в репозитории? Вы не должны этого делать. Позвольте Ninject справиться с этим. Почему вы определяете правила для контроллеров? В этом нет необходимости.   -  person LukLed    schedule 15.05.2012


Ответы (3)


Основываясь на документации, они фактически отговаривают вас от использования фабрик. Я использую их, но возможно, теперь это может решить вашу проблему. Я получал эту ошибку в какой-то момент своего собственного DI с помощью ninject, но трудно сказать, почему вы получаете свое. Для меня это было вызвано отношениями и ленивой загрузкой. С включенной ленивой загрузкой ваши объекты удерживают ссылку на ObjectContext, поэтому они могут заполнять связанные объекты и коллекции связанных объектов.

В итоге, я думаю, было бы полезно, если бы вы разместили трассировку стека для своей ошибки, чтобы мы могли помочь вам точно узнать, когда она возникает.

Надеюсь это поможет.

Изменить: я думаю, проблема в вашем методе сохранения. Если вы избавляетесь от контекста вашего объекта, когда вы заставляете игру редактировать его. Я не думаю, что это будет в ObjectStateManager, чтобы увидеть вашу строку:

_context.ObjectStateManager.ChangeObjectState(game, System.Data.EntityState.Modified);

Ваш текущий игровой объект, который вы получили от AutoMapper, не привязан ни к какому ObjectContext. Вот метод, который я использую для обновлений:

public void Update(TEntity entity)
{
    object originalItem;

    EntityKey key = Context.CreateEntityKey(Context.GetEntitySet<TEntity>().Name, entity);

    if (Context.TryGetObjectByKey(key, out originalItem))
    {
        Context.ApplyCurrentValues(key.EntitySetName, entity);
    }
}

Не забудьте позвонить Context.SaveChanges();

Я не сам это придумал, нашел где-то в сети. Это работает хорошо для меня. Я бы изменил ваш метод сохранения, чтобы включить его. Если вам нужна помощь, дайте мне знать. Еще одна вещь, которую я бы порекомендовал (небольшой), - это установить для контекста объекта значение null в вашем методе dispose.

удачи!

Редактировать: Хорошо, еще одна попытка... исходя из конструктора вашего контроллера администратора, есть ли у вас частные переменные для ваших репозиториев?

private IGameRepository _gameRepository;

public AdminController(IArticleRepository articleRepository, IGameRepository gameRepository, INewsRepository newsRepository)
    {
        _articleRepository = articleRepository;
        _gameRepository    = gameRepository;
        _newsRepository    = newsRepository;

Контроллеры создаются ninject? Я не вижу AdminController в вашем списке привязок. Я не вижу атрибута [Inject] ни в одном из ваших конструкторов. Вы просто пропустили это, когда отправили в стек, или их там нет?

Моя другая мысль заключается в том, что если у вас нет проблем с памятью (утечек) с приложением InSingletonScope() для вашего контекста, это может быть подходящим ответом.

Извините за все вопросы, но я просто пытаюсь понять проблему лучше.

person bytebender    schedule 11.05.2012
comment
Я опубликую трассировку стека позже сегодня, когда у меня будет доступ к моей машине разработки. Когда я тестирую, внедрение ObjectContext с помощью InSingletonScope() «исправляет» его, позволяя мне обновлять объекты без каких-либо исключений. Меня беспокоит только кэширование/поточность. Я не совсем уверен, как работает многопоточность, поскольку я работаю с PHP. - person Major Productions; 11.05.2012
comment
Это имело бы смысл. (InSingletonScope()) просто добавьте комментарий после того, как вы опубликуете свое редактирование. - person bytebender; 11.05.2012
comment
Также обнаружил, что если мои контроллеры установлены на InRequestScope(), но мой ObjectContext установлен на InSingletonScope(), я получаю ту же самую ошибку удаления, только раньше. Кажется, это работает только в том случае, если контроллеры находятся в InTransientScope() по умолчанию, а OC - InSingletonScope(). - person Major Productions; 12.05.2012
comment
Привет, спасибо за ответ. К сожалению, я не вызываю Save до завершения сопоставления, в самом конце метода Edit моего контроллера. GetGame просто возвращает _context.Games.SingleOrDefault(g => g.id == id); Отладчик также проверяет, существует ли соединение ObjectContext после этого оператора. Он удаляется только тогда, когда доходит до кода сопоставления, прежде чем я даже попытаюсь вызвать GetPlatform. Это почти похоже на то, что AutoMapper заставляет HtmlContext.Current изменить свое значение, или есть какая-то другая странная проблема с областью действия. - person Major Productions; 15.05.2012
comment
Контроллеры являются производными от HGController, который является производным от базового класса Controller. HGController позволяет мне выдавать определенные HTTP-коды, если что-то идет не по плану (например, 404, если пользователь запрашивает что-то, чего не существует, или 500 для более общей ошибки). HGController привязан сам к себе в моем модуле Ninject, InRequestScope. На самом деле все вводится правильно, насколько я вижу. Все привязки IRepository -> ConcreteRepository работают. Это просто ObjectContext портит работу. - person Major Productions; 15.05.2012
comment
Чтобы ответить на ваши вопросы ( :P ), да, у меня есть приватные поля для репозиториев. Я использую базовую установку Nuget расширения Ninject MVC 2, поэтому почти все обрабатывается файлом NinjectWebCommon.cs, который был установлен в моей папке App_Start. Все, что я сделал, это создал DIModule, показанный выше, и соединил его вместе. - person Major Productions; 15.05.2012
comment
Я думаю о том, чтобы загрузить все это на github, чтобы вам/кому было проще увидеть мой код. - person Major Productions; 15.05.2012

Я вижу, что IDisposable реализован неправильно. Может быть, это может быть причиной вашего исключения, связанного с удалением?

person Jo Inge Arnes    schedule 20.07.2012

Мой собственный статический картограф работает отлично:

public static class GameMapper
{
    public static Game MapFromEditModelToGame(IGameRepository repo, AdminGameViewModel formData, Game newGame)
    {
        newGame.GameID = formData.GameID;
        newGame.GameTitle = formData.GameTitle;
        newGame.GenreID = formData.GenreID;
        newGame.LastModified = DateTime.Now;
        newGame.ReviewScore = (short)formData.ReviewScore;
        newGame.ReviewText = formData.ReviewText;
        newGame.Cons = String.Join("|", formData.Cons);
        newGame.Pros = String.Join("|", formData.Pros);
        newGame.Slug = formData.Slug;

        if (newGame.Platforms != null && newGame.Platforms.Count > 0)
        {
            var oldPlats = newGame.Platforms.ToArray();

            foreach (var oldPlat in oldPlats)
            {
                newGame.Platforms.Remove(oldPlat);
            }
        }

        foreach (var platId in formData.PlatformIDs)
        {
            var plat = repo.GetPlatform(platId);
            newGame.Platforms.Add(plat);
        }

        return newGame;
    }
}

Вызывается как:

game = GameMapper.MapFromEditModelToGame(_gameRepository, formData, game);

Сравните приведенное выше с моим определением карты AutoMapper:

Mapper.CreateMap<AdminGameViewModel, Game>()
.BeforeMap((s, d) =>
{
    if (d.Platforms != null && d.Platforms.Count > 0)
    {
        var oldPlats = d.Platforms.ToArray();

        foreach (var oldPlat in oldPlats)
        {
            d.Platforms.Remove(oldPlat);
        }
    }

    foreach (var platId in s.PlatformIDs)
    {
        var plat = _gameRepository.GetPlatform(platId);
        d.Platforms.Add(plat);
    }
})
.ForMember(dest => dest.Platforms, opt => opt.Ignore())
.ForMember(dest => dest.BoxArtPath, opt => opt.Ignore())
.ForMember(dest => dest.IndexImagePath, opt => opt.Ignore())
.ForMember(dest => dest.Cons, opt => opt.MapFrom(src => string.Join("|", src.Cons)))
.ForMember(dest => dest.Pros, opt => opt.MapFrom(src => string.Join("|", src.Pros)))
.ForMember(dest => dest.LastModified, opt => opt.UseValue(DateTime.Now));

Единственная реальная разница в том, что я передаю репо в качестве аргумента.

Весь остальной код — мой контроллер, мой DI и т. д. — точно такой же. Единственная разница в том, что я использую свой собственный неуклюжий картограф. Не идеальная ситуация, но что-то.

person Major Productions    schedule 15.05.2012