实体框架:此命令已经关联了一个打开的DataReader

331

我正在使用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
我知道这个问题很古老了,但我很想知道你的“predicate”和“historicPredicate”变量是什么类型。我发现如果你将Func<T, bool>传递给Where(),它会编译并且有时会工作(因为它在内存中执行“where”)。你应该传递Expression<Func<T, bool>>Where() - James
17个回答

404

这不是关闭连接的问题。EF正确管理连接。我理解这个问题是在单个连接上执行多个数据检索命令(或单个命令中有多个选择),而下一个DataReader在第一个DataReader完成读取之前就被执行了。避免异常的唯一方法是允许多个嵌套的DataReaders = 打开MultipleActiveResultSets。另一个场景是当您遍历查询结果(IQueryable)并且在迭代中触发加载实体的延迟加载时,总是会出现这种情况。


3
但是每个方法中只有一个选择,这样才有意义。 - Sonic Soul
2
@Sonic:我的意图是检查已执行和完成的SQL命令。 - Ladislav Mrnka
13
好的,我的问题出在第二种情况上:当您迭代查询结果(IQueryable),并且在迭代过程中触发加载实体的延迟加载机制。 - Amr Elgarhy
7
开启MARS功能可能会产生负面影响:http://www.designlimbo.com/?p=235 - Søren Boisen
1
使用多个数据上下文怎么样?这是不好的实践吗? - Hossein Shahdoost
显示剩余17条评论

153

除了使用 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();

这将强制Entity Framework将列表加载到内存中,因此当我们在foreach循环中迭代它时,它不再使用数据读取器来打开列表,而是在内存中。

我意识到如果您想要懒加载某些属性,则可能不希望出现这种情况。 这主要是一个示例,希望能解释为什么/如何可能会出现此问题,以便您可以相应地做出决策。


10
这个解决方案对我有用。在查询后,在对结果进行任何其他操作之前添加 .ToList()。 - Torben Junker Kjær
12
请注意并运用常识。如果你对一千个对象进行ToList操作,它将大量增加内存。在这个特定的例子中,最好将内部查询与第一个查询合并,以便只生成一个查询而不是两个。 - kamranicus
4
我的意思正是这样,考虑一件事情并选择适合情况的做法,而不仅仅是盲目地去做。这个例子只是我随意想出来用来解释的 :) - Jim Wolff
3
当然,我只是想明确指出这点,以便那些喜欢复制粘贴的人能够理解。 :) - kamranicus
不要打我,但这绝不是问题的解决方案。自从什么时候“在内存中提取数据”成为SQL相关问题的解决方案了?我喜欢与数据库交流,所以我绝不会更喜欢将某些东西拉入内存“因为否则会抛出SQL异常”。尽管如此,在您提供的代码中,没有理由两次联系数据库。可以轻松地一次调用完成。小心像这样的帖子。ToList、First、Single等应该仅在需要数据(因此仅需要的数据)时使用,而不是在发生SQL异常时使用。 - Frederik Prijck
如前所述,应该使用常识,但我可以想到许多情况,例如您可能希望将某些内容缓存到内存字典中。提供的示例非常基本,仅用于解释为什么会出现此问题。 - Jim Wolff

79

还有另一种克服这个问题的方法。是否更好取决于您的情况。

这个问题是由于懒加载引起的,因此避免它的一种方法是不使用懒加载,通过使用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
19
如果有人遇到只能编写 .Include("string") 而无法编写 lambda 表达式的问题,需要添加 "using System.Data.Entity",因为扩展方法位于该命名空间中。 - Jim Wolff

62
当您尝试迭代一种惰性加载的集合(IQueriable)时,会出现此错误。
foreach (var user in _dbContext.Users)
{    
}

将IQueryable集合转换为其他可枚举集合将解决此问题。示例:

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

2
最简单的解决方案!加油 ;) - Jacob Sobus
2
获取无界限的列表可能会导致严重的性能问题!怎么可能有人点赞呢? - SandRock
1
@SandRock 这不适用于在小公司工作的人 - SELECT COUNT(*) FROM Users = 5 - Simon_Weaver
5
请再考虑一下。一个年轻的开发者读到这个问答可能会认为这是一个永久的解决方案,但实际上并不是。我建议您编辑您的答案,警告读者从数据库获取无限列表的危险性。 - SandRock
1
@SandRock 我认为这是你链接一个描述最佳实践的答案或文章的好地方。 - Sinjai
显示剩余3条评论

34
尝试在连接字符串中设置MultipleActiveResultSets=true,这将允许在数据库上进行多任务处理。
Server=yourserver ;AttachDbFilename=database;User Id=sa;Password=blah ;MultipleActiveResultSets=true;App=EntityFramework

对我来说没问题...无论是在 app.config 中连接还是在程序中设置连接... 希望这有帮助。


在你的连接字符串中添加MultipleActiveResultSets=true很可能会解决这个问题。这不应该被投票否决。 - Aaron Hudon
是的,当然。我已经演示了如何向您的连接字符串添加内容。 - Mohamed Hocine
1
@AaronHudon 我认为通常情况下,你需要解释为什么事情会起作用,而不仅仅是陈述答案。 - Captain Prinny

17

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

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

4

我最初决定在我的API类中使用静态字段来引用MyDataContext对象的实例(其中MyDataContext是一个EF5上下文对象),但这似乎引起了问题。我在每个API方法中添加了以下代码,解决了问题。

using(MyDBContext db = new MyDBContext())
{
    //Do some linq queries
}

正如其他人所说,EF数据上下文对象不是线程安全的。因此将它们放置在静态对象中最终会在正确条件下导致“数据读取器”错误。
我最初的假设是创建一个对象实例会更有效率,并提供更好的内存管理。从我研究这个问题时收集到的信息来看,情况并非如此。事实上,似乎将每次对API的调用视为隔离的、线程安全的事件更加有效率。确保所有资源在对象超出范围时得到适当释放。
特别是如果你将API带入下一个自然阶段,即将其公开为Web服务或REST API,则这一点尤其有意义。 披露
  • 操作系统:Windows Server 2012
  • .NET:已安装4.5,项目使用4.0
  • 数据源:MySQL
  • 应用程序框架:MVC3
  • 身份验证:表单

3

我注意到当我将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
@AlbatrossCafe ...但是没有人提到在这种情况下,您的数据将被加载到内存中,并且查询将在内存中执行,而不是在数据库中。 - Lightning3

3
我发现我遇到了相同的错误,这个错误是在使用 Func<TEntity, bool> 而不是 Expression<Func<TEntity, bool>> 作为 predicate 时出现的。
一旦我将所有的 Func 改成了 Expression,异常就不再抛出了。
我相信 EntityFramworkExpression 做了一些聪明的处理,而使用 Func 则没有这样的处理。

这篇文章需要更多的赞。我试图在我的DataContext类中编写一个方法,接受一个(MyTParent model, Func<MyTChildren, bool> func)参数,以便我的ViewModels可以为通用的DataContext方法指定特定的where子句。一切都不起作用,直到我做了这个。 - Justin

3

解决此问题的两种方法:

  1. 在查询后使用 .ToList() 保持惰性加载,并强制进行内存缓存,这样您就可以通过打开新的 DataReader 进行迭代。
  2. .Include(/您想要在查询中加载的其他实体/) 这被称为急切加载,它允许您在 DataReader 执行期间包括关联的对象(实体)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接