实体框架:已经有一个打开的 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 不重用它。

【问题讨论】:

我意识到这个问题很古老,但我很想知道您的 predicatehistoricPredicate 变量是什么类型。我发现如果你将Func&lt;T, bool&gt; 传递给Where(),它会编译并且有时会工作(因为它在内存中执行“位置”)。您应该做的是将Expression&lt;Func&lt;T, bool&gt;&gt; 传递给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);

如果您使用适当的Includes,则可以避免启用 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()。 注意这一点并使用常识。如果您ToListing 一千个对象,它将增加大量内存。在这个特定示例中,您最好将内部查询与第一个查询结合起来,这样只会生成一个查询而不是两个。 @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&lt;TEntity, bool&gt; 而不是 Expression&lt;Func&lt;TEntity, bool&gt;&gt; 为您的 predicate 时发生了。

一旦我将所有Func's 更改为Expression's,异常就会停止抛出。

我相信EntityFramworkExpression's 做了一些聪明的事情,而Func's 根本没有这样做

【讨论】:

这需要更多的支持。我试图在我的 DataContext 类中制作一个方法,采用 (MyTParent model, Func&lt;MyTChildren, bool&gt; 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 问题

例外:已经有一个打开的 DataReader 与此 Connection 关联,必须先关闭

C# 中已经有一个打开的 Datareader 关联

已经有一个打开的 DataReader 与此命令关联,必须先关闭

已经有一个打开的 DataReader 与此命令关联,必须先关闭

调用另一个 repo 时出错 - '已经有一个打开的 DataReader 与此命令关联,必须先关闭'