An entity object cannot be referenced by multiple

2019-08-04 09:32发布

问题:

I understand there are a lot of duplicates to this question, but I couldn't find one that fits my scenario.

So I am using the ASP.NET MVC 4 + Entity Framework + Ninject using repository pattern (I see many mentions of repository + unit of work pattern? That could be a potential fix to my problem but I don't know how to implement it).

When I try to add a new post, I get "An entity object cannot be referenced by multiple instances of IEntityChangeTracker" error on the following line of code

context.Posts.Add(post);

Here is my full implementation:

Concrete repository

public class EFBlogRepository : IBlogRepository
{
    private readonly EFDbContext context;

    public EFBlogRepository(EFDbContext dbcontext)
    {
        context = dbcontext;
    }

    //add post
    public int AddPost(Post post)
    {
        context.Posts.Add(post);
        context.SaveChanges();
        return post.PostID;
    }

    public Category Category(int id)
    {
        return context.Categories.FirstOrDefault(c => c.CategoryID == id);
    }

    public Tag Tag(int id)
    {
        return context.Tags.FirstOrDefault(t => t.TagID == id);
    }
}

Interface

public interface IBlogRepository
{
    int AddPost(Post post);
    Category Category(int id);
    Tag Tag(int id);
}

My controller

public class AdminController : Controller
{
    private IBlogRepository repository;

    public AdminController(IBlogRepository repo)
    {
        repository = repo;
    }

    [HttpPost]
    public ContentResult AddPost(Post post)
    {
        string json;

        ModelState.Clear();

        if (TryValidateModel(post))
        {
            var id = repository.AddPost(post);

            json = JsonConvert.SerializeObject(new
            {
                id = id,
                success = true,
                message = "Post added successfully."
            });
        }
        else
        {
            json = JsonConvert.SerializeObject(new
            {
                id = 0,
                success = false,
                message = "Failed to add the post."
            });
        }
        return Content(json, "application/json");
    }
}

I don't think any of the above are the root of the problem, I think the problem is in my custom model binder

public class PostModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var post = (Post)base.BindModel(controllerContext, bindingContext);

        var repository = DependencyResolver.Current.GetService<IBlogRepository>();

        if (post.Category != null)
            post.Category = repository.Category(post.Category.CategoryID);

        var tags = bindingContext.ValueProvider.GetValue("Tags").AttemptedValue.Split(',');

        if (tags.Length > 0)
        {
            post.Tags = new List<Tag>();

            foreach (var tag in tags)
            {
                post.Tags.Add(repository.Tag(int.Parse(tag.Trim())));
            }
        }

        return post;
    }
}

and my global.asax.cs

ModelBinders.Binders.Add(typeof(Post), new PostModelBinder());

This is my Ninject dependency resolver

public class NinjectDependencyResolver: IDependencyResolver
{
    private IKernel kernel;

    public NinjectDependencyResolver()
    {
        kernel = new StandardKernel();
        AddBindings();
    }

    public object GetService(Type serviceType)
    {
        return kernel.TryGet(serviceType);
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        return kernel.GetAll(serviceType);
    }

    private void AddBindings()
    {
        kernel.Bind<IBlogRepository>().To<EFBlogRepository>();
        kernel.Bind<IAuthProvider>().To<FormsAuthProvider>();
    }
}

回答1:

You should bind your context in ninject bindings as InRequestScope

kernel.Bind<EFDbContext >().To<EFDbContext >().InRequestScope();

As the error says, one entity cannot be bound to more than one EF context. It seems that you are retrieving the entity from one context and then adding it to a different one. Using the line above you are telling Ninject to use the same context instance to serve all dependencies in the same HTTP request.

Two repositories are being created. One in the controller IBlogRepository repository and the other in the model binder var repository = DependencyResolver.Current.GetService<IBlogRepository>(). Before the fix each repository have a new instance of the context, causing the error. After the fix, both repositories will share the same instance of the context.