实体框架:已经有一个打开的 DataReader 与此命令关联
Posted
技术标签:
【中文标题】实体框架:已经有一个打开的 DataReader 与此命令关联【英文标题】:Entity Framework: There is already an open DataReader associated with this Command 【发布时间】:2011-06-19 13:12:58 【问题描述】:我正在使用实体框架,有时我会收到此错误。
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 不重用它。
【问题讨论】:
我意识到这个问题很古老,但我很想知道您的predicate
和 historicPredicate
变量是什么类型。我发现如果你将Func<T, bool>
传递给Where()
,它会编译并且有时会工作(因为它在内存中执行“位置”)。您应该做的是将Expression<Func<T, bool>>
传递给Where()
。
【参考方案1】:
这不是关闭连接。 EF 正确管理连接。我对这个问题的理解是,在单个连接(或具有多个选择的单个命令)上执行了多个数据检索命令,而下一个 DataReader 在第一个完成读取之前执行。避免该异常的唯一方法是允许多个嵌套的 DataReaders = 打开 MultipleActiveResultSets。经常发生这种情况的另一种情况是,当您迭代查询结果 (IQueryable) 时,您将触发迭代内加载实体的延迟加载。
【讨论】:
这是有道理的。但每个方法中只有一个选择。 @Sonic:这就是问题所在。也许执行了不止一个命令,但您看不到它。我不确定这是否可以在 Profiler 中进行跟踪(在执行第二个读取器之前可以抛出异常)。您还可以尝试将查询强制转换为 ObjectQuery 并调用 ToTraceString 以查看 SQL 命令。很难追踪。我总是打开 MARS。 @Sonic:我无意检查已执行和已完成的 SQL 命令。 太好了,我的问题是第二种情况:'当您遍历查询结果 (IQueryable) 时,您将触发迭代内加载实体的延迟加载。' 启用 MARS can 显然有不好的副作用:designlimbo.com/?p=235【参考方案2】:还有另一种方法可以解决这个问题。这是否更好取决于您的情况。
问题是由延迟加载引起的,因此避免它的一种方法是不延迟加载,通过使用 Include:
var results = myContext.Customers
.Include(x => x.Orders)
.Include(x => x.Addresses)
.Include(x => x.PaymentMethods);
如果您使用适当的Include
s,则可以避免启用 MARS。但是如果你错过了一个,你就会得到错误,因此启用 MARS 可能是修复它的最简单方法。
【讨论】:
工作就像一个魅力。.Include
是比启用 MARS 更好的解决方案,也比编写自己的 SQL 查询代码容易得多。
如果有人遇到您只能编写 .Include("string") 而不是 lambda 的问题,您需要添加“使用 System.Data.Entity”,因为扩展方法位于那里.【参考方案3】:
除了使用 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 循环中迭代它时,它不再使用数据读取器打开列表,而是在内存中。
我意识到,如果您想延迟加载某些属性,这可能不是您所希望的。 这主要是一个示例,希望能解释您如何/为什么会遇到此问题,以便您可以做出相应的决定
【讨论】:
这个解决方案对我有用。在查询之后和对结果执行任何其他操作之前添加 .ToList()。 注意这一点并使用常识。如果您ToList
ing 一千个对象,它将增加大量内存。在这个特定示例中,您最好将内部查询与第一个查询结合起来,这样只会生成一个查询而不是两个。
@subkamran 我的观点正是如此,思考一些事情并选择适合情况的事情,而不仅仅是做事。这个例子只是我想解释的随机的东西:)
当然,我只是想为喜欢复制/粘贴的人明确指出 :)
别开枪,但这绝不是问题的解决方案。从什么时候开始“将数据拉入内存”成为 SQL 相关问题的解决方案?我喜欢与数据库聊天,所以我绝不愿意在内存中拉一些东西“因为否则会引发 SQL 异常”。尽管如此,在您提供的代码中,没有理由两次联系数据库。一个电话轻松搞定。小心这样的帖子。 ToList, First, Single, ... 应该只在内存中需要数据时使用(所以只有你想要的数据),而不是在发生 SQL 异常时使用。【参考方案4】:
我通过向构造函数添加选项轻松(实用)解决了这个问题。因此,我只在需要时使用它。
public class Something : DbContext
public Something(bool MultipleActiveResultSets = false)
this.Database
.Connection
.ConnectionString = Shared.ConnectionString /* your connection string */
+ (MultipleActiveResultSets ? ";MultipleActiveResultSets=true;" : "");
...
【讨论】:
谢谢。它正在工作。我只是在 web.config 中直接在连接字符串中添加了 MultipleActiveResultSets=true【参考方案5】:当您尝试迭代的集合是一种延迟加载 (IQueriable) 时,您会收到此错误。
foreach (var user in _dbContext.Users)
将 IQueriable 集合转换为其他可枚举集合将解决此问题。 例子
_dbContext.Users.ToList()
注意:.ToList() 每次都会创建一个新集合,如果您正在处理大数据,它可能会导致性能问题。
【讨论】:
最简单的解决方案!大起来 ;) 获取无限列表可能会导致严重的性能问题!怎么会有人对此表示赞同? @SandRock 不适合为小公司工作的人 -SELECT COUNT(*) FROM Users
= 5
三思而后行。阅读此 Q/A 的年轻开发人员可能会认为这是一个历久弥新的解决方案,但事实并非如此。我建议您编辑您的答案,以警告读者从 db 获取无限列表的危险。
@SandRock 我认为这将是您链接描述最佳实践的答案或文章的好地方。【参考方案6】:
我在第二次查询之前使用以下代码部分解决了这个问题:
...first query
while (_dbContext.Connection.State != System.Data.ConnectionState.Closed)
System.Threading.Thread.Sleep(500);
...second query
您可以以毫秒为单位更改睡眠时间
P.D.使用线程时很有用
【讨论】:
在任何解决方案中任意添加 Thread.Sleep 都是不好的做法 - 当用于回避某个值的状态未完全理解的不同问题时尤其糟糕。我原以为响应底部所述的“使用线程”意味着至少对线程有一些基本的了解-但是此响应没有考虑任何上下文,尤其是在那些情况下这是一个非常糟糕的主意使用 Thread.Sleep - 例如在 UI 线程上。【参考方案7】:我最初决定在我的 API 类中使用静态字段来引用 MyDataContext 对象的实例(其中 MyDataContext 是 EF5 上下文对象),但这似乎是造成问题的原因。我在我的每个 API 方法中添加了类似以下的代码,从而解决了问题。
using(MyDBContext db = new MyDBContext())
//Do some linq queries
正如其他人所说,EF 数据上下文对象不是线程安全的。因此将它们放在静态对象中最终会在适当的条件下导致“数据读取器”错误。
我最初的假设是只创建对象的一个实例会更有效,并且可以提供更好的内存管理。从我收集到的研究这个问题的资料来看,情况并非如此。事实上,将对 API 的每次调用视为一个独立的、线程安全的事件似乎更有效。确保在对象超出范围时正确释放所有资源。
这很有意义,特别是如果您将 API 带到下一个自然进程,即将其公开为 WebService 或 REST API。
披露
操作系统:Windows Server 2012 .NET:安装 4.5,项目使用 4.0 数据来源:mysql 应用程序框架:MVC3 身份验证:表单【讨论】:
【参考方案8】:我注意到当我将 IQueriable 发送到视图并在双 foreach 中使用它时会发生此错误,其中内部 foreach 也需要使用连接。简单示例(ViewBag.parents 可以是 IQueriable 或 DbSet):
foreach (var parent in ViewBag.parents)
foreach (var child in parent.childs)
简单的解决方案是在使用之前在集合上使用.ToList()
。另请注意,MARS 不适用于 MySQL。
【讨论】:
谢谢!这里的所有内容都说“嵌套循环是问题”,但没有人说如何解决它。我在第一次调用时输入了ToList()
,以从数据库中获取集合。然后我在该列表上做了一个foreach
,随后的调用完美地工作而不是给出错误。
@AlbatrossCafe ...但是没有人提到在这种情况下您的数据将被加载到内存中,并且查询将在内存中执行,而不是在数据库中执行【参考方案9】:
在启用 MARS 和将整个结果集检索到内存之间的一个很好的中间立场是,在初始查询中仅检索 ID,然后在执行过程中遍历实现每个实体的 ID。
例如(使用 this answer 中的“博客和帖子”示例实体):
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 也需要(请参阅 here 和 here)。
【讨论】:
context.SaveChanges();
inside loop :( 。这不好。它必须在循环外。【参考方案10】:
尝试在您的连接字符串中设置MultipleActiveResultSets=true
。
这允许对数据库进行多任务处理。
Server=yourserver ;AttachDbFilename=database;User Id=sa;Password=blah ;MultipleActiveResultSets=true;App=EntityFramework
这对我有用...无论是您在 app.config 中的连接还是以编程方式设置它... 希望这有帮助
【讨论】:
MultipleActiveResultSets=true 添加到您的连接字符串可能会解决问题。这不应该被否决。 是的,我已经演示了如何添加到您的连接字符串 @AaronHudon 我相信,通常情况下,您想解释为什么事情会起作用,而不仅仅是陈述答案。【参考方案11】:我发现我有同样的错误,当我使用 Func<TEntity, bool>
而不是 Expression<Func<TEntity, bool>>
为您的 predicate
时发生了。
一旦我将所有Func's
更改为Expression's
,异常就会停止抛出。
我相信EntityFramwork
对Expression's
做了一些聪明的事情,而Func's
根本没有这样做
【讨论】:
这需要更多的支持。我试图在我的 DataContext 类中制作一个方法,采用(MyTParent model, Func<MyTChildren, bool> func)
,以便我的 ViewModels 可以为 Generic DataContext 方法指定某个 where
子句。在我这样做之前没有任何效果。【参考方案12】:
如果我们尝试将部分条件分组到 Func 或扩展方法中,我们会得到这个错误,假设我们有这样的代码:
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
【讨论】:
【参考方案13】:这个问题只要把数据转换成列表就可以解决
var details = _webcontext.products.ToList();
if (details != null)
Parallel.ForEach(details, x =>
Products obj = new Products();
obj.slno = x.slno;
obj.ProductName = x.ProductName;
obj.Price = Convert.ToInt32(x.Price);
li.Add(obj);
);
return li;
【讨论】:
ToList() 进行调用,但上面的代码仍然没有处理连接。所以您的 _webcontext 在第 1 行时仍有被关闭的风险【参考方案14】:在我的情况下,由于依赖注入注册而出现问题。我将使用 dbcontext 的每个请求范围服务注入到单例注册服务中。因此,在多个请求中使用了 dbcontext,因此出现了错误。
【讨论】:
【参考方案15】:缓解此问题的两种解决方案:
-
使用
.ToList()
强制内存缓存保持延迟加载
查询,因此您可以通过它打开一个新的 DataReader 进行迭代。
.Include
(/您要在查询中加载的其他实体/)这个
被称为急切加载,它允许您(确实)包括
在执行查询期间关联的对象(实体)
数据读取器。
【讨论】:
【参考方案16】:就我而言,我发现在 myContext.SaveChangesAsync() 调用之前缺少“等待”语句。在这些异步调用之前添加等待解决了我的数据读取器问题。
【讨论】:
【参考方案17】:在我的情况下,这个问题与 MARS 连接字符串无关,而是与 json 序列化有关。 将我的项目从 NetCore2 升级到 3 后,出现此错误。
更多信息可以找到here
【讨论】:
以上是关于实体框架:已经有一个打开的 DataReader 与此命令关联的主要内容,如果未能解决你的问题,请参考以下文章
例外:已经有一个打开的 DataReader 与此 Connection 关联,必须先关闭
已经有一个打开的 DataReader 与此命令关联,必须先关闭