Search code examples
c#entity-frameworkentity-framework-4repositorydatacontext

How do you centralize the Entity Framework data context in a web application?


In our application we use the repository pattern to retrieve and persist data from our data storage medium. The medium we opted to use is Entity Framework 4. This seems to be a very clean way to do things and has worked great 99% of the time.

Now we are encountering an issue. We have two repositories, like this:

public class UserRepository : IUserRepository
{
    Entities dataContext = new Entities();

    public User GetUser(string username)
    {
        return dataContext.Users.SingleOrDefault(x => x.Username == username);
    }

    // ... more CRUD-style methods that are not relevant to this question.

    public void SaveChanges()
    {
        dataContext.SaveChanges();
    }
}

public RoleRepository : IRoleRepository
{
    Entities dataContext = new Entities();

    public Role GetRole(string name)
    {
        return dataContext.Roles.SingleOrDefault(x => x.Name == name);
    }

    // ... more CRUD-style methods that are not relevant to this question.

    public void SaveChanges()
    {
        dataContext.SaveChanges();
    }
}

Users and Roles actually have a many to many relationship in the Entity Framework model. Sometimes we want to take an existing user and an existing role and associate the two. Normally this would work great if you do a short sample code snippet like this:

Entities dataContext = new Entities();
Role roleToAdd = dataContext.Roles.Single(x => x.Name == "Admin");
User user = dataContext.Users.Single(x => x.Username == "Fred");
user.Roles.Add(roleToAdd);
dataContext.SaveChanges();

This works great because both entities are being retrieved from the same EF data context object. However, in our application each repository creates its own data context object. So when we try to do the same as the above with our own architecture:

UserRepository userRepo = new UserRepository();
RoleRepository roleRepo = new RoleRepository();
User user = userRepo.GetUser("Fred");
Role roleToAdd = roleRepo.GetRole("Admin");
user.Roles.Add(roleToAdd);
userRepo.SaveChanges();

We get this error:

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

What is the best way to centralize this data context? Obviously I don't want to duplicate a GetRole method within the UserRepository because that would be redundant and silly. I could just do a more verbose method on the UserRepository that takes in a username and a rolename, then uses the same data context to retrieve and associate them, like this:

public void AddUserToRole(string username, string role)
{
    User user = dataContext.Users.Single(x => x.Username == username);
    Role roleToAdd = dataContext.Roles.Single(x => x.Name == role);
    user.Roles.Add(roleToAdd);
}

I could then just do:

userRepo.AddUserToRole("Fred", "Admin");
userRepo.SaveChanges();

But is that the best way to accomplish this? Is there a better way to centralize the EF data context on each request so that all repositories use the same one instead of creating their own? If so, how would I do that?


Solution

  • Use constructor injection on the repository to pass the context.

    public class UserRepository : IUserRepository
    {
        Entities dataContext;
    
        public UserRepository(Entities entities)
        {
           this.dataContext = entities;
        }
    
        public User GetUser(string username)
        {
            return dataContext.Users.SingleOrDefault(x => x.Username == username);
        }
    
        // ... more CRUD-style methods that are not relevant to this question.
    
        public void SaveChanges()
        {
            dataContext.SaveChanges();
        }
    }
    

    Tell your DI container to request-scope the context lifetime.

    E.g., with AutoFac you would:

    builder.RegisterType<Entities>().InstancePerHttpRequest();
    builder.RegisterType<UserRepository>().As<IUserRepository>().InstancePerHttpRequest();
    builder.RegisterControllers(typeof(MvcApplication).Assembly);