我正在使用Entity Framework,偶尔我会收到此错误。
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为什么不重用连接?
答案 0 :(得分:337)
这不是关闭连接。 EF正确管理连接。我对这个问题的理解是,在单个连接上执行多个数据检索命令(或者具有多个选择的单个命令),而在第一个DataReader完成读取之前执行下一个DataReader。避免异常的唯一方法是允许多个嵌套的DataReaders =启用MultipleActiveResultSets。总是发生这种情况的另一种情况是,当您遍历查询结果(IQueryable)时,您将在迭代内触发加载实体的延迟加载。
答案 1 :(得分:119)
除了使用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();
这迫使实体框架将列表加载到内存中,因此当我们在foreach循环中迭代它时,它不再使用数据读取器打开列表,而是在内存中。
我意识到如果你想延迟加载某些属性,这可能不是所希望的。 这主要是一个例子,希望能够解释你如何/为什么会遇到这个问题,这样你就可以做出相应的决定
答案 2 :(得分:65)
还有另一种方法可以解决这个问题。是否更好的方式取决于您的情况。
问题是由延迟加载引起的,所以避免它的一种方法是不要通过使用Include来延迟加载:
var results = myContext.Customers
.Include(x => x.Orders)
.Include(x => x.Addresses)
.Include(x => x.PaymentMethods);
如果使用适当的Include
,则可以避免启用MARS。但如果你错过了一个,你就会得到错误,所以启用MARS可能是解决它的最简单的方法。
答案 3 :(得分:39)
当您尝试迭代的集合是一种延迟加载(IQueriable)时,会出现此错误。
foreach (var user in _dbContext.Users)
{
}
将IQueriable集合转换为其他可枚举集合将解决此问题。 例子
_dbContext.Users.ToList()
注意:.ToList()每次创建一个新集合,如果处理大数据,它可能会导致性能问题。
答案 4 :(得分:12)
通过向构造函数添加选项,我轻松(实用)解决了问题。因此,我只在需要时使用它。
public class Something : DbContext
{
public Something(bool MultipleActiveResultSets = false)
{
this.Database
.Connection
.ConnectionString = Shared.ConnectionString /* your connection string */
+ (MultipleActiveResultSets ? ";MultipleActiveResultSets=true;" : "");
}
...
答案 5 :(得分:5)
尝试在你的连接字符串中设置&#34; MultipleActiveResultSets = true&#34; 这允许在数据库上进行多任务处理。 &#34; Server = yourserver; AttachDbFilename = database; User Id = sa; Password = blah; MultipleActiveResultSets = true; App = EntityFramework&#34; 这对我有用...无论你在app.config中的连接还是以编程方式设置它... 希望这有用
答案 6 :(得分:4)
我最初决定在我的API类中使用静态字段来引用MyDataContext对象的实例(其中MyDataContext是EF5 Context对象),但这似乎是产生问题的原因。我在每个API方法中添加了类似以下内容的代码,并修复了问题。
using(MyDBContext db = new MyDBContext())
{
//Do some linq queries
}
正如其他人所说,EF数据上下文对象不是线程安全的。因此将它们放在静态对象中最终会在正确的条件下导致“数据读取器”错误。
我最初的假设是只创建一个对象实例会更有效率,并提供更好的内存管理。从我收集的研究这个问题的情况来看,情况并非如此。实际上,将每次调用API视为一个孤立的线程安全事件似乎更有效。确保所有资源都已正确发布,因为该对象超出了范围。
这是有道理的,特别是如果您将API带入下一个将其作为WebService或REST API公开的自然进程。
<强>披露强>
答案 7 :(得分:3)
我注意到当我向视图发送IQueriable并在双foreach中使用它时会发生此错误,其中内部foreach也需要使用连接。简单示例(ViewBag.parents可以是IQueriable或DbSet):
foreach (var parent in ViewBag.parents)
{
foreach (var child in parent.childs)
{
}
}
简单的解决方案是在使用前对集合使用.ToList()
。另请注意,MARS不适用于MySQL。
答案 8 :(得分:2)
启用MARS和将整个结果集检索到内存之间的良好中间点是在初始查询中仅检索ID,然后循环遍历实现每个实体的ID。
例如(使用this answer中的“博客和帖子”示例实体):
def cb(n, k):
b = []
if n == 0:
b.append(0)
if n < k:
b.append(n)
if n == k:
b.append(10)
else:
a = n // k
b.append(n - ((n // k) * k))
if a < k:
b.append(a)
else:
cb(a, k)
return b
print(cb(22, 3))
执行此操作意味着您只需将几千个整数拉入内存,而不是数千个完整的对象图,这样可以最大限度地减少内存使用量,同时使您能够在不启用MARS的情况下逐项工作。
这样做的另一个好处是,如示例所示,您可以在循环每个项目时保存更改,而不必等到循环结束(或其他一些此类变通办法),就像即使启用了MARS也需要(请参阅here和here)。
答案 9 :(得分:1)
我发现我遇到了同样的错误,当我使用Func<TEntity, bool>
代替Expression<Func<TEntity, bool>>
predicate
时发生错误。
将所有Func's
更改为Expression's
后,异常停止被抛出。
我相信EntityFramwork
使用Expression's
做一些聪明的事情,它与Func's
答案 10 :(得分:1)
就我而言,我发现在myContext.SaveChangesAsync()调用之前缺少“等待”语句。在这些异步调用之前添加等待,可以为我解决数据读取器问题。
答案 11 :(得分:0)
如果我们尝试将部分条件分组为Func&lt;&gt;或者扩展方法我们会得到这个错误,假设我们有这样的代码:
public static Func<PriceList, bool> IsCurrent()
{
return p => (p.ValidFrom == null || p.ValidFrom <= DateTime.Now) &&
(p.ValidTo == null || p.ValidTo >= DateTime.Now);
}
Or
public static IEnumerable<PriceList> IsCurrent(this IEnumerable<PriceList> prices) { .... }
如果我们尝试在Where()中使用它,这将抛出异常,我们应该做的是构建一个像这样的Predicate:
public static Expression<Func<PriceList, bool>> IsCurrent()
{
return p => (p.ValidFrom == null || p.ValidFrom <= DateTime.Now) &&
(p.ValidTo == null || p.ValidTo >= DateTime.Now);
}
可以在以下网址阅读更多内容:http://www.albahari.com/nutshell/predicatebuilder.aspx
答案 12 :(得分:0)
只需将数据转换为列表
即可解决此问题{{1}}
答案 13 :(得分:0)
在我的情况下,问题是由于依赖注入注册而发生的。我正在注入一个使用dbcontext到单件注册服务的每个请求范围服务。因此dbcontext在多个请求中使用,因此出错。
答案 14 :(得分:0)
2种解决此问题的方法:
.ToList()
之后保持延迟加载
查询,这样您就可以遍历它,打开一个新的DataReader。.Include
(//您要在查询中加载的其他实体 /)
称为“预先加载”,它允许您(实际上)包括
执行查询过程中的关联对象(实体)
DataReader。答案 15 :(得分:0)
就我而言,该问题与MARS连接字符串无关,但与json序列化无关。 将项目从NetCore2升级到3后,出现此错误。
可以找到更多信息here
答案 16 :(得分:-6)
我在第二个查询之前使用以下代码部分解决了这个问题:
...first query
while (_dbContext.Connection.State != System.Data.ConnectionState.Closed)
{
System.Threading.Thread.Sleep(500);
}
...second query
你可以用毫秒来改变睡眠时间
Pd积。使用线程时很有用