实体框架:已经有与此命令关联的打开的DataReader


285

我正在使用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不重用它。


1
我意识到这个问题很古老,但是我很想知道您predicatehistoricPredicate变量的类型。我发现,如果传递Func<T, bool>Where()它,它将编译并且有时可以工作(因为它在内存中执行“位置”)。您应该做的就是传递Expression<Func<T, bool>>Where()
詹姆斯

Answers:


351

这与关闭连接无关。EF正确管理连接。我对这个问题的理解是,在第一个完成读取之前执行下一个DataReader时,在单个连接上执行多个数据检索命令(或具有多个选择的单个命令)。避免该异常的唯一方法是允许多个嵌套DataReaders =启用MultipleActiveResultSets。始终发生这种情况的另一种情况是,当您遍历查询结果(IQueryable)时,您将为迭代中的已加载实体触发延迟加载。


2
那是有道理的。但每种方法中只有一个选择。
Sonic Soul

1
@Sonic:这是问题。也许执行的命令不止一个,但是您看不到它。我不确定是否可以在事件探查器中对此进行跟踪(可以在执行第二个阅读器之前引发异常)。您也可以尝试将查询强制转换为ObjectQuery并调用ToTraceString以查看SQL命令。很难追踪。我总是打开火星。
Ladislav Mrnka'2

2
@Sonic:我的意图不是检查已执行和已完成的SQL命令。
Ladislav Mrnka'2

11
很好,我的问题是第二种情况:“当您遍历查询结果(IQueryable)时,您将在迭代过程中触发已加载实体的延迟加载。”
Amr Elgarhy 2011年

6
启用MARS 显然有不良副作用:designlimbo.com/?p=235
索伦Boisen

126

除了使用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循环中对其进行迭代时,它不再使用数据读取器来打开列表,而是在内存中。

我意识到,如果您想延迟加载某些属性,则可能不需要这样做。这主要是一个示例,希望可以解释为什么/为什么会出现此问题,因此您可以做出相应的决定


7
这个解决方案对我有用。在查询之后和对结果执行任何其他操作之前,立即添加.ToList()。
TJKjaer 2012年

9
对此要小心,并使用常识。如果要ToList处理一千个对象,则将增加大量内存。在此特定示例中,最好将内部查询与第一个查询结合使用,这样只会生成一个查询,而不是两个查询。
kamranicus

4
@subkamran我的意思恰恰是,考虑一些事情并选择适合这种情况的事情,而不仅仅是做。该示例只是我想解释的随机内容:)
Jim Wolff

3
绝对,我只想为复制/粘贴满意的人明确指出这一点:)
kamranicus 2013年

不要开枪打我,但这绝不是解决问题的办法。从什么时候开始“将数据拖入内存”成为SQL相关问题的解决方案?我喜欢和数据库聊天,所以绝不希望在内存中添加某些内容,因为否则会引发SQL异常。但是,在提供代码的情况下,没有理由两次联系数据库。只需一个电话即可轻松完成。注意此类帖子。ToList,First,Single,……仅应在内存中需要数据时使用(因此仅当您需要时才使用),否则在发生SQL异常时不应该使用。
Frederik Prijck

70

还有另一种方法可以解决此问题。更好的方法取决于您的情况。

该问题是由延迟加载引起的,因此,避免这种情况的一种方法是通过使用Include来避免延迟加载:

var results = myContext.Customers
    .Include(x => x.Orders)
    .Include(x => x.Addresses)
    .Include(x => x.PaymentMethods);

如果使用适当Include的,则可以避免启用MARS。但是,如果您遗漏了一个错误,则会收到错误消息,因此启用MARS可能是修复该错误的最简单方法。


1
像魅力一样工作。.Include这是比启用MARS更好的解决方案,并且比编写自己的SQL查询代码容易得多。
Nolonar 2014年

15
如果有人遇到只能写.include(“ string”)而不是lambda的问题,则需要添加“ using System.Data.Entity”,因为扩展方法位于此处。
Jim Wolff 2014年

46

当您尝试迭代的集合有点延迟加载(IQueriable)时,会出现此错误。

foreach (var user in _dbContext.Users)
{    
}

将IQueriable集合转换为其他可枚举的集合将解决此问题。例

_dbContext.Users.ToList()

注意:.ToList()每次都会创建一个新集合,如果要处理大数据,可能会导致性能问题。


1
最简单的解决方案!
大举

1
提取无边界列表可能会导致严重的性能问题!谁能支持?
桑德洛克'16

1
@SandRock不适合在小型公司工作的人- SELECT COUNT(*) FROM Users= 5
Simon_Weaver

5
三思而后行。读过此问答的年轻开发人员可能会认为这绝对是一个历史悠久的解决方案。建议您编辑答案,以警告读者有关从db获取无边界列表的危险。
桑德洛克(SandRock)

1
@SandRock我认为这是您链接描述最佳实践的答案或文章的好地方。
Sinjai

13

通过将选项添加到构造函数中,我轻松(实用)解决了该问题。因此,我仅在需要时使用它。

public class Something : DbContext
{
    public Something(bool MultipleActiveResultSets = false)
    {
        this.Database
            .Connection
            .ConnectionString = Shared.ConnectionString /* your connection string */
                              + (MultipleActiveResultSets ? ";MultipleActiveResultSets=true;" : "");
    }
...

2
谢谢。工作正常 我只是直接在web.config中的连接字符串中添加了MultipleActiveResultSets = true
Mosharaf Hossain

11

尝试输入连接字符串来设置MultipleActiveResultSets=true。这允许在数据库上执行多任务。

Server=yourserver ;AttachDbFilename=database;User Id=sa;Password=blah ;MultipleActiveResultSets=true;App=EntityFramework

这对我有用...无论您是在app.config中还是以编程方式进行设置...都希望对您有所帮助


添加到您的连接字符串中的MultipleActiveResultSets = true可能会解决该问题。这不应该被否决。
亚伦·休顿

是相信我AVE演示了如何添加到您的连接字符串
穆罕默德奥西纳

4

我最初决定在我的API类中使用静态字段来引用MyDataContext对象的实例(其中MyDataContext是EF5 Context对象),但这似乎是造成问题的原因。我向我的每个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
  • 身份验证:表格

3

我注意到当我向视图发送IQueriable并将其在double foreach中使用时会发生此错误,其中内部foreach也需要使用连接。简单示例(ViewBag.parents可以是IQueriable或DbSet):

foreach (var parent in ViewBag.parents)
{
    foreach (var child in parent.childs)
    {

    }
}

简单的解决方案是在使用.ToList()集合之前使用它。另请注意,MARS不适用于MySQL。


谢谢!这里的所有内容都说“嵌套循环是问题”,但是没有人说如何解决。我把ToList()我的第一次调用从DB的集合。然后,我foreach在该列表上执行了一个操作,随后的调用工作完美,而没有给出错误。
AlbatrossCafe

@AlbatrossCafe ...但是没有人提到在那种情况下,您的数据将被加载到内存中,并且查询将在内存中执行,而不是在数据库中执行
Lightning3

3

我发现我也遇到了同样的错误,并且当您使用a Func<TEntity, bool>代替a 时发生了Expression<Func<TEntity, bool>>predicate

一旦我改变了所有Func'sExpression's就停止抛出异常。

我相信这会EntityFramwork做一些聪明的事情,Expression's而这些事情根本就做不到Func's


这需要更多的支持。我试图在DataContext类中设计一个方法,以(MyTParent model, Func<MyTChildren, bool> func)使ViewModels可以where为Generic DataContext方法指定某个子句。在执行此操作之前,没有任何工作。
贾斯汀

3

2种解决此问题的解决方案:

  1. 强制执行内存缓存,以.ToList()在查询后保持延迟加载,因此您可以通过迭代来打开新的DataReader。
  2. .Include(// 要在查询中加载的其他实体 /)这称为“预加载”,它允许您(实际上)在使用DataReader执行查询的过程中(确实)包括关联的对象(实体)。

2

在启用MARS和将整个结果集检索到内存之间的一个很好的中间立场是,仅在初始查询中检索ID,然后在遍历各个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(请参见此处此处)。


context.SaveChanges();。内环路:(这是不好的,它必须是循环之外。
Jawand辛格


0

如果我们尝试将部分条件分组为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()中使用它,则会抛出异常,而我们应该做的是构建这样的谓词:

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


0

只需将数据转换为列表即可解决此问题

 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行时仍有被关闭的风险
Sonic Soul

0

在我的情况下,问题是由于依赖项注入注册而发生的。我正在将使用dbcontext的按请求范围的服务注入到单例注册的服务中。因此,dbcontext在多个请求中使用,因此出现错误。


0

就我而言,这个问题与MARS连接字符串无关,而与json序列化有关。将项目从NetCore2升级到3后,出现此错误。

更多信息可以在这里找到


-6

我在第二个查询之前使用以下代码部分解决了此问题:

 ...first query
 while (_dbContext.Connection.State != System.Data.ConnectionState.Closed)
 {
     System.Threading.Thread.Sleep(500);
 }
 ...second query

您可以在几毫秒内更改睡眠时间

PD使用线程时有用


13
在任何解决方案中随意添加Thread.Sleep都是不好的做法-尤其是当它用于避免另一个无法完全理解某些值的状态的问题时,这尤其糟糕。我本以为响应末尾所述的“使用线程”将意味着至少对线程有一些基本的了解-但是此响应未考虑任何上下文,尤其是在那些情况非常糟糕的情况下使用Thread.Sleep-例如在UI线程上。
迈克·图尔斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.