问题
I've been struggling trying to figure out why this code disposes dbcontext before the foreach loop. Any clues would be greatly appreciated. Thanks!
[TestMethod]
public void CreatePostAddComment()
{
IQueryable<Post> thePost;
using (var _context = new BlogRepository())
{
_context.AddPost(GetTestPost());
var retrivePost = _context.GetPostByName("TestPost1");
thePost = retrivePost;
}
foreach (var post in thePost)
{
using (var _dbContext = new BlogRepository())
{
_dbContext.AddComment(GetTestComment(post));
}
}
}
回答1:
The using statement is in fact syntactic sugar it is equivalent to : -
[TestMethod]
public void CreatePostAddComment()
{
IQueryable<Post> thePost;
{
var _context = new BlogRepository();
_context.AddPost(GetTestPost());
var retrivePost = _context.GetPostByName("TestPost1");
thePost = retrivePost;
_context.Dispose();
//Note, using is much better, because Dipose is called
//even if there is an exception.
}
foreach (var post in thePost)
{
using (var _dbContext = new BlogRepository())
{
_dbContext.AddComment(GetTestComment(post));
}
}
}
However there are several key problems with your code, only the first of which is the Context Disposed Exception.
Your first issue about the context being disposed is because the EF query is invoked only when you enumerate the actual IQueryable, in this case, at the foreach. In other words, the IQueryable has done no work and is lazy. When the foreach actually NEEDS the information the IQueryable goes to fetch it, by which time the context has already been disposed.
Secondly you can't do work on the pose you just got, because you are using another context. EF will complain that the post object is still attached to the old context. This issue will highlight a more general pattern. You should hold your context for as long as your "unit of work". Generally speaking you dispose after you call DbContext.SaveChanges().
This leads me to your third bug. You didn't call DbContext.SaveChanges(). Nothing is going to happen with this code. You are not going to write to the database, EF validation isn't going to run, nothing will happen except in memory, which is about to go out of scope.
Finally your test method is producing side effects (assuming it will successfully save). This will lead to pain points further down the line. A way to get around this is to begin a transaction before the test, and rollback after the test.
I understand that MbUnit will do the transaction logic for you if you wish to go down that road.
So to sum up, this is what I WOULD do...assuming I understand what you are trying to achieve-
From a stylistic point I hate CRUD methods. Methods like GetPostByName, when you have Linq for that. The problems I have with it include, more code to maintain (if you change over to nHibernate, you need to reimplement GetPostByName). Secondly you will then next find you need a GetPostByDescript, then a GetPostById then a GetPostByDescriptionOrName...etc etc...
PS. With a name like ThePost I assume you are after a single instance....
[Rollback]
[TestMethod]
public void CreatePostAddComment()
{
using(var _context = new BlogRepository())
{
_context.AddPost(GetTestPost());
_context.SaveChanges();
var thePost = _context.Posts.First(x => x.Name =="TestPost1");
_dbContext.AddComment(GetTestComment(post));
_context.SaveChanges();
}
}
来源:https://stackoverflow.com/questions/14801266/why-is-dbcontext-disposed-before-the-foreach-loop