How to scope out Dbcontexts (to prevent singleton context for entire application)
I was wondering how do you scope out your Dbcontexts in Entity Framework so you don't use a single Dbcont开发者_高级运维ext for your entire application. I am new to Entity Framework and have been reading tutorials, but they all used a single Dbcontext as an example, so EF is pretty much a blackbox for me right now.
Let's say for example I have 3 models:
- Post
- User
- Comment
Each model is related to each other (A Post belongs to User, Comment belongs to User and Post). Do I make a Dbcontext for each one individually? But that wouldn't be correct since they are all related, or would I make a Dbcontext for each scenario that I need? For example, if I only need to query for Post and Comments and not user, that would be a PostCommentsContext. And then we would have a PostUserCommentContext...
The best solution would be to use a Unit of Work to wrap the Data Context, as well as managing the connection lifetime and allowing you to work with multiple Repositories (if you were so inclined to go down that path).
Summary of implementation:
- Create an interface (
IUnitOfWork
) which exposes properties for yourDbSet
's, as well as a single method called Commit - Create an implementation (
EntityFrameworkUnitOfWork
), implementing as required. Commit simply calls SaveChanges on the base class (DbContext
), and also provides a good hook-in for last minute logic. - Your controller accepts a
IUnitOfWork
, use DI (preferably) to resolve aEntityFrameworkUnitOfWork
, with a HTTP-context scoped lifetime setting (StructureMap is good for this) - (optional, but recommended) create a Repository which also takes the
IUnitOfWork
, and work off that via your Controller.
HTH
EDIT - In Response to Comments
Oh, how can you do work that involves creating records in multiple models then? i.e., create a new user and a new post in the same transaction.
Given your using ASP.NET MVC, your controllers should accept an IUnitOfWork
in their constructor.
Here's an example, based on what you asked
public SomeController : Controller
{
private IUnitOfWork _unitOfWork;
private IUserRepo _userRepo;
private IPostRepo _postRepo;
public SomeController(IUnitOfWork unitOfWork, IUserRepo userRepo, IPostRepo postRepo)
{
_unitOfWork = unitOfWork; // use DI to resolve EntityFrameworkUnitOfWork
_userRepo = userRepo;
_postRepo = postRepo;
}
[HttpPost]
public ActionResult CreateUserAndPost(User user, Post post)
{
// at this stage, a HTTP request has come in, been resolved to be this Controller
// your DI container would then see this Controller needs a IUnitOfWork, as well
// as two Repositories. DI smarts will resolve each dependency.
// The end result is a single DataContext (wrapped by UoW) shared by all Repos.
try
{
userRepo.Add(user);
postRepo.Add(post);
// nothing has been sent to DB yet, only two objects in EF graph set to EntityState.Added
_unitOfWork.Commit(); // two INSERT's pushed to DB
}
catch (Exception exc)
{
ModelState.AddError("UhOh", exc.ToString());
}
}
}
And one more question, what does the HTTP-context scoped lifetime do?
Objects in DI-talk have scope management settings that include per thread, per session, per http request, singleton, etc.
HTTP-context scoped is the recommended setting for web apps. It means "new up a context when a HTTP request comes in, and get rid of it when the request is finished".
Use 1 DbContext! That will make life easier for you. Don't worry about performance, data that isn't needed or queried won't be loaded and won't consume any resources.
public class UserContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Post> Posts { get; set; }
public DbSet<Comment> Comments { get; set; }
}
For some scenarios you might want 2 or more contexts.
A context like the one above to hold all the front-end data needed for your application to work and another context for - as an example - to store reports generated from that front-end data, and which is only used in the back-end of you application.
I am experimenting with UnitofWork, here is what I have come up with...
First I created a IUnitofWork that only contains one method. Commit();
Then my dbContext looks like this
public class myContext : DbContext, IUnitOfWork
{
public DbSet<Users> Users { get; set; }
public DbSet<Addresses> Address { get; set; }
public void Save()
{
SaveChanges();
}
}
My repository classes take a UnitofWork in their ctors.
public class UserRepository : IRepository<Position>
{
private myContext _context;
public UserRepository (IUnitOfWork unitOfWork)
{
if (unitOfWork == null)
throw new ArgumentNullException("unitOfWork");
_context = unitOfWork as myContext;
}
/// other methods ///
}
Then the code in the controller would be something like this
_unitOfWork = new myContext();
_userDB = new UserRepository(_unitOfWork);
_addressDB = new AddressRepository(_unitOfWork);
_userDB.Add(newUser);
_addresesDB.Add(newAddress);
_unitOfWork.Save();
I have debugged and proved that no data is commited until the Save method of the _unitOfWork is called. Very cool stuff!!
精彩评论