我正在使用实体框架,偶尔我会得到这个错误。
EntityCommandExecutionException
{"There is already an open DataReader associated with this Command which must be closed first."}
at System.Data.EntityClient.EntityCommandDefinition.ExecuteStoreCommands...
即使我没有做任何手动连接管理。
此错误间歇性地发生。
触发错误的代码(为方便阅读而缩短):
if (critera.FromDate > x) {
t= _tEntitites.T.Where(predicate).ToList();
}
else {
t= new List<T>(_tEntitites.TA.Where(historicPredicate).ToList());
}
使用Dispose模式以便每次都打开新的连接。
using (_tEntitites = new TEntities(GetEntityConnection())) {
if (critera.FromDate > x) {
t= _tEntitites.T.Where(predicate).ToList();
}
else {
t= new List<T>(_tEntitites.TA.Where(historicPredicate).ToList());
}
}
仍然有问题
为什么EF不重用一个连接,如果它已经打开。
在启用MARS和将整个结果集检索到内存之间有一个很好的中间地带,那就是在初始查询中只检索id,然后循环遍历实现每个实体的id。
例如(使用这个答案中的“博客和帖子”样本实体):
using (var context = new BlogContext())
{
// Get the IDs of all the items to loop through. This is
// materialized so that the data reader is closed by the
// time we're looping through the list.
var blogIds = context.Blogs.Select(blog => blog.Id).ToList();
// This query represents all our items in their full glory,
// but, items are only materialized one at a time as we
// loop through them.
var blogs =
blogIds.Select(id => context.Blogs.First(blog => blog.Id == id));
foreach (var blog in blogs)
{
this.DoSomethingWith(blog.Posts);
context.SaveChanges();
}
}
这样做意味着您只需将几千个整数拉入内存,而不是将数千个完整的对象图拉入内存,这将最小化内存使用,同时允许您在不启用MARS的情况下逐项工作。
从示例中可以看到,这样做的另一个好处是,您可以在循环遍历每个项时保存更改,而不必等到循环结束(或其他类似的解决方法),即使启用了MARS也需要这样做(参见这里和这里)。
或者使用MARS (MultipleActiveResultSets),你可以写你的代码,这样你就不会打开多个结果集。
您所能做的就是将数据检索到内存中,这样就不会打开读取器。
它通常是由于在尝试打开另一个结果集时遍历一个结果集而引起的。
示例代码:
public class MyContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
}
public class Blog
{
public int BlogID { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
public int PostID { get; set; }
public virtual Blog Blog { get; set; }
public string Text { get; set; }
}
假设你在你的数据库中做一个查找,包含这些:
var context = new MyContext();
//here we have one resultset
var largeBlogs = context.Blogs.Where(b => b.Posts.Count > 5);
foreach (var blog in largeBlogs) //we use the result set here
{
//here we try to get another result set while we are still reading the above set.
var postsWithImportantText = blog.Posts.Where(p=>p.Text.Contains("Important Text"));
}
我们可以通过添加.ToList()来解决这个问题,就像这样:
var largeBlogs = context.Blogs.Where(b => b.Posts.Count > 5).ToList();
这迫使entityframework将列表加载到内存中,因此当我们在foreach循环中遍历它时,它不再使用数据读取器来打开列表,而是在内存中。
我意识到,如果你想偷懒加载一些属性,这可能不是你想要的。
这主要是一个示例,希望解释您如何/为什么会遇到这个问题,这样您就可以做出相应的决定