1

I'm having an issue in Entity Framework 6 where an exception is consistently thrown. For the most part the application works perfectly fine until I try adding a user to a role via a linking table.

The error being thrown is the following:

The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects.

The functionality will happily add the user to the role virtually but as soon as SaveChanges() is called the process falls over.

I'm aware of the how and why for the above error and after doing some research it's due to the the context not being disposed of correctly. So following on from that and looking into the DbContext setup I've realised IDisposable wasn't added to the configuration. Unfortunately, no matter what I've tried incorporating IDisposable at any point within the application still doesn't dispose of the contexts correctly.

So after spending a fair bit of time and having no luck via Google I'm wondering if any of you have a solution or are able to point me in the right direction.

The following is a cutdown version of the Data Layer classes I've implemented:

public class GenericRepository<T> : WebsiteContext, IGenericRepository<T> where T : class
{
    public virtual void Commit()
    {
        SaveChanges();
    }

    public virtual void Delete(int id)
    {
        var record = Set<T>().Find(id);
        if (record == null)
            throw new Exception("Some Message");

        Set<T>().Remove(record);
    }

    // ... ETC
}

public interface IGenericRepository<T> where T : class
{
    void Commit();
    // ... ETC
}

public class WebsiteContext : DbContext, IWebsiteContext
{
    static WebsiteContext()
    {
        Database.SetInitializer<WebsiteContext>(null);
    }

    public WebsiteContext() : base("Name=WebsiteContext") { }

    public IDbSet<User> Users { get; set; }
    // ... ETC

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // ... ETC
    }
}

This implementation is loosely based around the following Stackoverflow question.

Entity Framework 6 Code First - Is Repository Implementation a Good One?

The following is a condensed version of the Service Layer class and method which is causing the issue.

    private IGenericRepository<User> _userRepository;
    private IGenericRepository<ApplicationUserSetting> _userSettingRepository;
    private IGenericRepository<ApplicationRole> _roleRepository;

    public UserManagementService()
    {
        _userRepository = new GenericRepository<User>();
        _roleRepository = new GenericRepository<ApplicationRole>();
        _userSettingRepository = new GenericRepository<ApplicationUserSetting>();
    }

    public void AssignUserRole(AssignRoleModel model)
    {
        var user = _userRepository.GetById(model.UserId);

        if (user == null)
            return;

        var role = _roleRepository.GetById(model.RoleId);

        if (role == null)
            return;

        user.Roles.Add(role);
        _userRepository.Commit();
    }
Community
  • 1
  • 1
Damian
  • 574
  • 1
  • 6
  • 19
  • 2
    Your repository seems to be a subclass of your context (`GenericRepository : WebsiteContext`). If so, that's very problematic. A generic repository is supposed to wrap one `DbSet`. A `DbContext` hosts many `DbSet`s. A repository instance should *receive* a context instance, not *inherit* it. – Gert Arnold Apr 13 '16 at 20:04

3 Answers3

1

The issue, just like the error states, is because you have multiple instances of the type DbContext fetching your entities for you. Each fetched entity is then associated with the DbContext instance that retrieved it. If you want to persist changes to these entities it has to occur on the DbContext instance that it is associated with OR you have to attach it to the DbContext instance it is not associated with.

If you are trying to keep it simple I recommend you implement a DI framework like AutoFac. You can then have a single DbContext instance created per request and have it injected everywhere you need it. It will allow you to keep your existing structure (I am not going to comment on that as I consider that out of scope for this question), the end result would be that each injected GenericRepository instance has an injected WebsiteContext instance but the WebsiteContext instances are shared (all the same instance). The upside of that is no more error but the downside is you do have to be aware that any changes to any entities will result in those changes being persisted as soon as you execute the Save functionality.

Igor
  • 60,821
  • 10
  • 100
  • 175
  • Fair assessment and a sound argument, I was aware of the context issue as you mentioned and as per the exception but I can't argue with you on the Dependency Injection part so I'll look into that tomorrow when I come back to working on it. – Damian Apr 13 '16 at 20:32
  • 1
    I've adjusted my code accordingly and your suggestion was hard to argue with, moving over to IoC has fixed the issue. Thanks for the help! – Damian Apr 15 '16 at 19:16
0

Using multiple repositories causes the issue. Just use one repository (= one db context) and have different methods for getting the individual types.

E.g. _repository.Get(id)

Sebastian Brand
  • 547
  • 4
  • 10
0

It's way out of scope to point out how your current implementation could be made to work, but if you did want to use more than one context, you can despite what others have said.

If you do, you will have to detach the entity from the previous context first.

Community
  • 1
  • 1
Kit
  • 20,354
  • 4
  • 60
  • 103