Ninject: entity object cannot be referenced by multiple instances of IEntityChangeTracker
Asked Answered
A

3

1

I am starting to use Ninject in my MVC5 code-first app. Here's my NinjectWebCommon.cs:

private static IKernel CreateKernel()
    {
        var kernel = new StandardKernel();
        try
        {
            kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
            kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();

            kernel.Bind<CMSContext>()
                .ToSelf()
                //.InSingletonScope();
                .InRequestScope();

            kernel.Bind<IExecutiveRepository>()
                .To<ExecutiveRepository>();

            kernel.Bind<IExecutiveSectionRepository>()
                .To<ExecutiveSectionRepository>();

            kernel.Bind<IExecutiveSectionMappingRepository>()
                .To<ExecutiveSectionMappingRepository>();

            kernel.Bind<IUserRepository>()
                .To<UserRepository>();

            kernel.Bind<IContentRepository>()
                .To<ContentRepository>();

            RegisterServices(kernel);
            return kernel;
        }
        catch
        {
            kernel.Dispose();
            throw;
        }
    }

I tried .InSingletonScope() as well as .InRequestScope() but I still get the 'entity object cannot be referenced by multiple instances of IEntityChangeTracker' error. Here is my Interface:

    public interface IExecutiveRepository : IDisposable
{
    IEnumerable<Executive> GetExecutives();
    Executive GetExecutiveById(int executiveId);
    void InsertExecutive(Executive executive);
    void UpdateExecutive(Executive executive);
    void DeleteExecutive(int executiveId);
    void Save();
}

Here is my concrete:

 public class ExecutiveRepository : IExecutiveRepository, IDisposable
{
    private CMSContext context;

    public ExecutiveRepository(CMSContext context)
    {
        this.context = context;
    }

    public IEnumerable<Executive> GetExecutives()
    {
        return context.Executives.ToList();
    }

    public Executive GetExecutiveById(int id)
    {
        return context.Executives.Find(id);
    }

    public void InsertExecutive(Executive executive)
    {
        context.Executives.Add(executive);
    }

    public void DeleteExecutive(int executiveId)
    {
        Executive executive = context.Executives.Find(executiveId);
        context.Executives.Remove(executive);
    }

    public void UpdateExecutive(Executive executive)
    {
        context.Entry(executive).State = EntityState.Modified;
    }

    public void Save()
    {
        context.SaveChanges();
    }

    private bool disposed = false;

    protected virtual void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                context.Dispose();
            }
        }
        this.disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

Here is the controller(top pertinent part):

 public class ExecutiveController : Controller
{
    private IExecutiveRepository executiveRepository;
    private IUserRepository userRepository;
    private IExecutiveSectionRepository executiveSectionRepository;
    private IExecutiveSectionMappingRepository executiveSectionMappingRepository;
    private IContentRepository contentRepository;
    private Ninject.IKernel _kernel = new StandardKernel();

    //[Inject]
    public ExecutiveController()
    {
        executiveRepository = _kernel.Get<ExecutiveRepository>();
        userRepository = _kernel.Get<UserRepository>();
        executiveSectionRepository = _kernel.Get<ExecutiveSectionRepository>();
        executiveSectionMappingRepository = _kernel.Get<ExecutiveSectionMappingRepository>();
        contentRepository = _kernel.Get<ContentRepository>();
    }
 ...

Not sure what I am doing wrong but upon adding a new 'Executive' it bombs... I do understand it's trying to use separate contexts and that's the problem, but I 'm just not sure how to fix it. Apparently, the line in the NinjectWebCommon.cs class:

 kernel.Bind<CMSContext>()
                .ToSelf()
                //.InSingletonScope();
                .InRequestScope();

Is supposed to be the fix, but it isn't... any ideas/suggestions?

Arquit answered 24/6, 2015 at 13:19 Comment(0)
J
3

You should be using NUGET package Ninject.Web.Mvc if you aren't already. This configures your application ready to use Ninject, other than your bindings. It looks like you are reasonably familiar with the bindings side of things already from what I can see in your CreateKernel() method.

Once your bindings are in place, you should not be creating Kernels in your controllers, this is because the Ninject.Web.Mvc library configures Ninject to create your controllers for you under the hood. Therefore any dependencies that you add to them should be automatically resolved.

So, you can use constructor injection to resolve your dependencies:

public class ExecutiveController : Controller
{
    private IExecutiveRepository ExecutiveRepository;
    private IUserRepository UserRepository;
    private IExecutiveSectionRepository ExecutiveSectionRepository;
    private IExecutiveSectionMappingRepository ExecutiveSectionMappingRepository;
    private IContentRepository ContentRepository;

    public ExecutiveController(
         IExecutiveRepository executiveRepository,
         IUserRepository userRepository,
         IExecutiveSectionRepository executiveSectionRepository,
         IExecutiveSectionMappingRepository executiveSectionMappingRepository,
         IContentRepository contentRepository)
    {

         // Set the field values
         this.ExecutiveRepository = executiveRepository,
         this.UserRepository = userRepository,
         this.ExecutiveSectionRepository = executiveSectionRepository,
         this.ExecutiveSectionMappingRepository = executiveSectionMappingRepository,
         this.ContentRepository = contentRepository;
    }

    public ActionResult Index(int id)
    {
        // Use one of your dependencies...
        var executive = this.executiveRepository.GetExecutiveById(id);
    }
}

Or you can use the [Inject] attribute which has the same effect:

public class ExecutiveController : Controller
{
    [Inject]
    public IExecutiveRepository executiveRepository { get; set; }

    [Inject]
    public IUserRepository userRepository { get; set; }

    [Inject]
    public IExecutiveSectionRepository executiveSectionRepository { get; set; }

    [Inject]
    public IExecutiveSectionMappingRepository executiveSectionMappingRepository { get; set; }

    [Inject]
    public IContentRepository contentRepository { get; set; }

    public ExecutiveController()
    {

    }

    public ActionResult Index(int id)
    {
        // Use one of your dependencies...
        var executive = this.executiveRepository.GetExecutiveById(id);
    }
}
Jaundiced answered 24/6, 2015 at 14:50 Comment(5)
Sorry, I've missed out a vital bit of the code... 1 secJaundiced
I've added the // Set the field values now.. it wouldn't have worked without setting the values within the controller.Jaundiced
Fantastic. Thank you very much. I HAD it before but learning Ninject AND repository pattern at once befuddled me. This more than clarified. Appreciate it! Marked you up.Tycho
Glad to have helped. It's really confusing at first I must admit :)Jaundiced
Glad it wasn't just me ;)Tycho
V
2

You're creating a kernel per controller.

InRequestScope only ensures one instance per request per kernel.

So you need to adapt your setup of the kernel so there's only one kernel per web application. See:

Vasodilator answered 24/6, 2015 at 13:34 Comment(6)
"Adapt your setup of the kernel"...This is how it came setup from NuGet. All I did was add those binding lines in the NinjectwebCommon.cs file. Upon reviewing the links you provided, there seems to be a canyon sized rift between the version from Nuget and what you propose. Apples to Oranges it seems. I'm not able to follow from one to the otherTycho
I got a notification that you added more to this, but it's not showing on the article... weird. At the top of SO, If I click the icon with the red '1' it shows you said "Add an OnActivation(x => {...}); to the binding. Add a breakpoint to it. Then check if at that..."... but in the actual article I see nothing... ???Tycho
@Vasodilator is correct. You should install Ninject.Web.Mvc in your application and then use the methods of injecting as recommended, such as adding the [Inject] attribute or constructor injection. If you are creating an instance of a kernel directly in your MVC application (other than for setting the bindings) then you're doing it wrong. The kernel is used for setting up your bindings to interfaces and then the dependencies are automatically resolved.Jaundiced
Ninject IS installed, that's how I got the NinjectWebCommon.cs file... ok, well I put all the code up and no one is saying, 'here change this to this'.. so all this theory and high level talk isn't helping me.Tycho
I've added an answer now, I really hope that it helps.Jaundiced
@Beau that was my original post where i suggested a way to get closer to the issue. But i then spotted that you do a new StandardKernel() in the controller and that was clearly the issue. So i edited the post. You got it all already fixed now so i refrain from further explanations ;-)Vasodilator
S
-1

This may not answer the question. But I tend to use the IDbContextFactory that EF provides you with and do something like this:

public interface IDefaultContextFactory : IDbContextFactory<CMSContext> {}

public class DefaultContextFactory : IDefaultContextFactory 
{
    private readonly Lazy<CMSContext> lazyContext = new Lazy<CMSContext>(() => new CMSContext());

    public CMSContext Create() 
    {
        return lazyContext.Value;
    }
}

Then you just bind that, and when you need the context you can do something like this:

public class ExecutiveRepository : IExecutiveRepository, IDisposable
{
    private readonly CMSContext context;

    public ExecutiveRepository(IDefaultContextFactory contextFactory)
    {
        this.context = contextFactory.Create();
    }
}

I believe @BatteryBackupUnit is correct, I would also consider using the above pattern for contexts.

Scheer answered 24/6, 2015 at 13:34 Comment(10)
yea not really helpful, but thanks. I need to use Ninject like the title says.Tycho
What? You are using Ninject.. This is a better way of injecting the context into your repositories. In fact this is agnostic of what Dependency Injection framework you are usingScheer
My office/coworkers use Ninject. I MUST use ninject. Not an option/choice. It's in the title. If my title was, 'I'm using Ninject but I want something agnostic"... then your answer would be good. But it isn't so it's not.Tycho
I think you are vastly misunderstanding what I'm saying. This will WORK for ninject and ANY OTHER dependency injection framework. It is just a BETTER way of doing what you are trying to achieve.Scheer
There is a library for Ninject for creating factories by simply creating an Interface and it does all of the wiring up for you. You don't need to code a factory like thisJaundiced
Yeah but then you are creating a dependency on your dependency injection framework.... not sure you would really want to do that, too much.Scheer
I don't know what you mean. You're either using Ninject or you're not.Jaundiced
@Coulton In case you want to abstract code into components, or a new library - basically for reusabilityScheer
I haven't come across this issue, so I'd have to take your word for it - it's an interesting discussion to me! If I want to use the Ninject Factory extension, all I need to do is create a single interface with a Create() method, such as IMyContextFactory which can be replaced with any implementation, just like any other dependency in an application that is resolved by Ninject. I'm not sure why it makes it hard to abstract code out? Thanks for clarifying :)Jaundiced
@Coulton oh I see, no that is cool, I like that feature!! that sounds pretty good to be honest. I get what you mean now.Scheer

© 2022 - 2024 — McMap. All rights reserved.