Khung thực thể: Đã có một DataReader mở được liên kết với Lệnh này


285

Tôi đang sử dụng Entity Framework và thỉnh thoảng tôi sẽ gặp lỗi này.

EntityCommandExecutionException
{"There is already an open DataReader associated with this Command which must be closed first."}
   at System.Data.EntityClient.EntityCommandDefinition.ExecuteStoreCommands...

Mặc dù tôi không làm bất kỳ quản lý kết nối thủ công.

lỗi này xảy ra không liên tục.

mã gây ra lỗi (rút ngắn để dễ đọc):

        if (critera.FromDate > x) {
            t= _tEntitites.T.Where(predicate).ToList();
        }
        else {
            t= new List<T>(_tEntitites.TA.Where(historicPredicate).ToList());
        }

sử dụng mẫu Vứt bỏ để mở kết nối mới mỗi lần.

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());
    }

}

vẫn có vấn đề

tại sao EF không sử dụng lại kết nối nếu nó đã mở.


1
Tôi nhận ra rằng câu hỏi này là cổ xưa, nhưng tôi muốn biết loại predicatehistoricPredicatebiến của bạn là gì. Tôi đã phát hiện ra rằng nếu bạn vượt qua Func<T, bool>để Where()nó sẽ biên dịch và đôi khi làm việc (vì nó không được "ở đâu" trong bộ nhớ). Những gì bạn nên làm là chuyển Expression<Func<T, bool>>đến Where().
James

Câu trả lời:


351

Nó không phải là về kết nối đóng cửa. EF quản lý kết nối chính xác. Sự hiểu biết của tôi về vấn đề này là có nhiều lệnh truy xuất dữ liệu được thực thi trên một kết nối (hoặc một lệnh có nhiều lựa chọn) trong khi DataReader tiếp theo được thực thi trước khi lần đầu tiên hoàn thành việc đọc. Cách duy nhất để tránh ngoại lệ là cho phép nhiều DataReaders lồng nhau = bật ManyActiveResultSets. Một kịch bản khác khi điều này luôn xảy ra là khi bạn lặp qua kết quả của truy vấn (IQueryable) và bạn sẽ kích hoạt tải lười biếng cho thực thể được tải bên trong lần lặp.


2
Điều đó sẽ có ý nghĩa. nhưng chỉ có một lựa chọn trong mỗi phương thức.
Sonic Soul

1
@Sonic: Đó là câu hỏi. Có thể có nhiều hơn một lệnh được thực thi nhưng bạn không thấy nó. Tôi không chắc chắn nếu điều này có thể được theo dõi trong Profiler (ngoại lệ có thể được ném trước khi trình đọc thứ hai được thực thi). Bạn cũng có thể thử truyền truy vấn tới ObjectQuery và gọi ToTraceString để xem lệnh SQL. Thật khó để theo dõi. Tôi luôn bật MARS.
Ladislav Mrnka

2
@Sonic: Không có ý định của tôi là kiểm tra các lệnh SQL đã thực hiện và đã hoàn thành.
Ladislav Mrnka

11
thật tuyệt, vấn đề của tôi là kịch bản thứ hai: 'khi bạn lặp qua kết quả của truy vấn (IQueryable) và bạn sẽ kích hoạt tải lười biếng cho thực thể được tải bên trong lần lặp.'
Amr Elgarhy

6
Kích hoạt MARS rõ ràng có thể có tác dụng phụ xấu: designlimbo.com/?p=235
Søren Boisen

126

Ngoài ra, để sử dụng MARS (ManyActiveResultSets), bạn có thể viết mã của mình để không mở nhiều bộ kết quả.

Những gì bạn có thể làm là lấy dữ liệu về bộ nhớ, theo cách đó bạn sẽ không mở được đầu đọc. Nó thường được gây ra bởi việc lặp qua một tập kết quả trong khi cố gắng mở một tập kết quả khác.

Mã mẫu:

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; }
}

Hãy nói rằng bạn đang thực hiện tra cứu trong cơ sở dữ liệu của mình có chứa:

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"));
}

Chúng ta có thể thực hiện một giải pháp đơn giản cho việc này bằng cách thêm .ToList () như thế này:

var largeBlogs = context.Blogs.Where(b => b.Posts.Count > 5).ToList();

Điều này buộc thực thể thực hiện để tải danh sách vào bộ nhớ, do đó khi chúng ta lặp lại mặc dù trong vòng lặp foreach, nó không còn sử dụng trình đọc dữ liệu để mở danh sách, thay vào đó là trong bộ nhớ.

Tôi nhận ra rằng điều này có thể không được mong muốn nếu bạn muốn tải một số thuộc tính chẳng hạn. Đây chủ yếu là một ví dụ hy vọng giải thích làm thế nào / tại sao bạn có thể gặp vấn đề này, vì vậy bạn có thể đưa ra quyết định phù hợp


7
Giải pháp này đã làm việc cho tôi. Thêm .ToList () ngay sau khi truy vấn và trước khi làm bất cứ điều gì khác với kết quả.
TJKjaer

9
Hãy cẩn thận với điều này và sử dụng thông thường. Nếu bạn đang ăn ToListmột ngàn đối tượng, nó sẽ tăng bộ nhớ một tấn. Trong ví dụ cụ thể này, bạn nên kết hợp truy vấn bên trong với truy vấn đầu tiên để chỉ một truy vấn được tạo chứ không phải hai truy vấn.
kamranicus

4
@subkamran Quan điểm của tôi là chính xác, suy nghĩ về một cái gì đó và chọn những gì phù hợp với tình huống, không chỉ làm. Ví dụ chỉ là một cái gì đó ngẫu nhiên tôi nghĩ ra để giải thích :)
Jim Wolff

3
Chắc chắn, tôi chỉ muốn chỉ ra một cách rõ ràng cho những người sao chép / dán hạnh phúc :)
kamranicus

Đừng bắn tôi, nhưng đây không phải là một giải pháp cho câu hỏi. Từ khi nào "kéo dữ liệu trong bộ nhớ" là một giải pháp cho một vấn đề liên quan đến SQL? Tôi thích trò chuyện với cơ sở dữ liệu, vì vậy tôi sẽ không muốn lấy thứ gì đó trong bộ nhớ "vì nếu không thì sẽ có ngoại lệ SQL bị ném". Tuy nhiên, trong mã bạn được cung cấp, không có lý do gì để liên hệ với cơ sở dữ liệu hai lần. Dễ dàng được thực hiện trong một cuộc gọi. Hãy cẩn thận với những bài viết như thế này. ToList, First, Single, ... Chỉ nên được sử dụng khi cần dữ liệu trong bộ nhớ (vì vậy chỉ có dữ liệu mà bạn MUỐN), chứ không phải khi xảy ra ngoại lệ SQL.
Frederik Prijck 4/214

70

Có một cách khác để khắc phục vấn đề này. Cho dù đó là một cách tốt hơn tùy thuộc vào tình huống của bạn.

Vấn đề xảy ra do tải lười biếng, vì vậy một cách để tránh đó là không lười tải, thông qua việc sử dụng Bao gồm:

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

Nếu bạn sử dụng Includes thích hợp , bạn có thể tránh bật MARS. Nhưng nếu bạn bỏ lỡ một lỗi, bạn sẽ gặp lỗi, vì vậy bật MARS có lẽ là cách dễ nhất để khắc phục.


1
Làm việc như người ở. .Includelà một giải pháp tốt hơn nhiều so với việc kích hoạt MARS và dễ dàng hơn nhiều so với việc viết mã truy vấn SQL của riêng bạn.
Nolonar

15
Nếu bất cứ ai gặp vấn đề mà bạn chỉ có thể viết .Include ("chuỗi") không phải là lambda, bạn cần thêm "bằng System.Data.Entity" vì phương thức mở rộng được đặt ở đó.
Jim Wolff

46

Bạn gặp lỗi này, khi bộ sưu tập bạn đang cố lặp đi lặp lại là loại tải lười biếng (IQueriable).

foreach (var user in _dbContext.Users)
{    
}

Chuyển đổi bộ sưu tập IQueriable thành bộ sưu tập vô số khác sẽ giải quyết vấn đề này. thí dụ

_dbContext.Users.ToList()

Lưu ý: .ToList () tạo một bộ mới mọi lúc và nó có thể gây ra vấn đề về hiệu suất nếu bạn đang xử lý dữ liệu lớn.


1
Giải pháp đơn giản nhất có thể! Big UP;)
Jacob Sobus

1
Tìm nạp danh sách không giới hạn có thể gây ra vấn đề hiệu suất nghiêm trọng! Làm thế nào bất cứ ai có thể nâng cao điều đó?
SandRock

1
@SandRock không dành cho người làm việc cho một công ty nhỏ - SELECT COUNT(*) FROM Users= 5
Simon_Weaver

5
Hãy suy nghĩ kỹ về nó. Một nhà phát triển trẻ đọc Q / A này có thể nghĩ rằng đây là một giải pháp mọi thời đại khi nó hoàn toàn không. Tôi đề nghị bạn chỉnh sửa câu trả lời của mình để cảnh báo người đọc về sự nguy hiểm của việc tìm nạp các danh sách không giới hạn từ db.
SandRock

1
@SandRock Tôi nghĩ rằng đây sẽ là một nơi tốt để bạn liên kết một câu trả lời hoặc bài viết mô tả các thực tiễn tốt nhất.
Sinjai

13

Tôi đã giải quyết vấn đề một cách dễ dàng (thực dụng) bằng cách thêm tùy chọn vào hàm tạo. Vì vậy, tôi chỉ sử dụng khi cần thiết.

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

2
Cảm ơn bạn. Nó đang hoạt động. Tôi vừa thêm
nhiềuActiveResultSets

11

Hãy thử trong chuỗi kết nối của bạn để thiết lập MultipleActiveResultSets=true. Điều này cho phép đa nhiệm trên cơ sở dữ liệu.

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

Điều đó hiệu quả với tôi ... cho dù kết nối của bạn trong app.config hay bạn đặt nó theo lập trình ... hy vọng điều này hữu ích


ManyActiveResultSets = true được thêm vào chuỗi kết nối của bạn sẽ có khả năng giải quyết vấn đề. Điều này không nên được bỏ phiếu.
Aaron Hudon

vâng chắc chắn tôi đã trình bày cách thêm vào chuỗi kết nối của bạn
Mohamed Hocine

4

Ban đầu tôi đã quyết định sử dụng một trường tĩnh trong lớp API của mình để tham chiếu một thể hiện của đối tượng MyDataContext (Trong đó MyDataContext là một đối tượng Ngữ cảnh EF5), nhưng đó là điều dường như tạo ra vấn đề. Tôi đã thêm mã giống như sau vào mỗi phương thức API của mình và điều đó đã khắc phục vấn đề.

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

Như những người khác đã nêu, các đối tượng Bối cảnh dữ liệu của EF KHÔNG phải là luồng an toàn. Vì vậy, việc đặt chúng vào đối tượng tĩnh cuối cùng sẽ gây ra lỗi "trình đọc dữ liệu" trong các điều kiện phù hợp.

Giả định ban đầu của tôi là chỉ tạo một thể hiện của đối tượng sẽ hiệu quả hơn và có khả năng quản lý bộ nhớ tốt hơn. Từ những gì tôi đã thu thập được nghiên cứu về vấn đề này, đó không phải là trường hợp. Trên thực tế, có vẻ hiệu quả hơn khi coi mỗi cuộc gọi đến API của bạn là một sự kiện an toàn theo chuỗi, bị cô lập. Đảm bảo rằng tất cả các tài nguyên được phát hành đúng, khi đối tượng đi ra khỏi phạm vi.

Điều này có ý nghĩa đặc biệt nếu bạn đưa API của mình sang tiến trình tự nhiên tiếp theo, đó sẽ là phơi bày nó dưới dạng API WebService hoặc REST.

Tiết lộ

  • HĐH: Máy chủ Windows 2012
  • .NET: Đã cài đặt 4.5, Project sử dụng 4.0
  • Nguồn dữ liệu: MySQL
  • Khung ứng dụng: MVC3
  • Xác thực: Biểu mẫu

3

Tôi nhận thấy rằng lỗi này xảy ra khi tôi gửi IQueriable cho chế độ xem và sử dụng nó trong một foreach kép, trong đó các foreach bên trong cũng cần sử dụng kết nối. Ví dụ đơn giản (ViewBag.parents có thể là IQueriable hoặc DbSet):

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

    }
}

Giải pháp đơn giản là sử dụng .ToList()trên bộ sưu tập trước khi sử dụng. Cũng lưu ý rằng MARS không hoạt động với MySQL.


CẢM ƠN BẠN! Tất cả mọi thứ ở đây đều nói "các vòng lặp lồng nhau là vấn đề" nhưng không ai nói cách khắc phục. Tôi thực ToList()hiện cuộc gọi đầu tiên để nhận bộ sưu tập từ DB. Sau đó, tôi đã thực hiện một foreachdanh sách đó và các cuộc gọi tiếp theo hoạt động hoàn hảo thay vì đưa ra lỗi.
AlbatrossCafe

@AlbatrossCafe ... nhưng không ai đề cập rằng trong trường hợp đó, dữ liệu của bạn sẽ được tải vào bộ nhớ và truy vấn sẽ được thực thi trong bộ nhớ, thay vì DB
Lightning3

3

Tôi thấy rằng tôi có cùng một lỗi và nó xảy ra khi tôi đang sử dụng Func<TEntity, bool>thay vì Expression<Func<TEntity, bool>>chopredicate .

Khi tôi đã thay đổi tất cả Func'sthànhExpression's ngoại lệ ngừng bị ném.

Tôi tin rằng EntityFramworkcó một số điều thông minh Expression'smà đơn giản là nó không làm vớiFunc's


Điều này cần nhiều upvote. Tôi đã cố gắng tạo một phương thức trong lớp DataContext của mình để tham gia (MyTParent model, Func<MyTChildren, bool> func)để ViewModels của tôi có thể chỉ định một wheremệnh đề nhất định cho phương thức Generic DataContext. Không có gì đã làm việc cho đến khi tôi làm điều này.
Justin

3

2 giải pháp để giảm thiểu vấn đề này:

  1. Buộc bộ nhớ đệm giữ tải lười biếng với .ToList() sau khi truy vấn của bạn, do đó bạn có thể lặp lại thông qua nó để mở DataReader mới.
  2. .Include(/ các thực thể bổ sung mà bạn muốn tải trong truy vấn /) cái này được gọi là tải háo hức, cho phép bạn (thực sự) bao gồm các đối tượng (thực thể) được liên kết trong khi thực hiện truy vấn với DataReader.

2

Một nền tảng tốt giữa việc kích hoạt MARS và truy xuất toàn bộ tập kết quả vào bộ nhớ là chỉ truy xuất ID trong một truy vấn ban đầu, sau đó lặp qua các ID cụ thể hóa từng thực thể khi bạn đi.

Ví dụ: (sử dụng các thực thể mẫu "Blog và Bài viết" như trong câu trả lời này ):

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();
    }
}

Làm điều này có nghĩa là bạn chỉ kéo một vài nghìn số nguyên vào bộ nhớ, trái ngược với hàng ngàn biểu đồ toàn bộ đối tượng, điều này sẽ giảm thiểu việc sử dụng bộ nhớ trong khi cho phép bạn làm việc theo từng mục mà không cần bật MARS.

Một lợi ích tuyệt vời khác của điều này, như đã thấy trong mẫu, là bạn có thể lưu các thay đổi khi bạn lặp qua từng mục, thay vì phải đợi cho đến khi kết thúc vòng lặp (hoặc một số cách giải quyết khác), như cần thiết ngay cả với MARS được kích hoạt (xem tại đâyđây ).


context.SaveChanges();bên trong vòng lặp :(. Điều này không tốt. Nó phải ở bên ngoài vòng lặp.
Jawand Singh

1

Trong trường hợp của tôi, tôi thấy rằng đã thiếu các câu lệnh "await" trước các lệnh gọi myContext.SaveChangesAsync (). Việc thêm chờ đợi trước khi các cuộc gọi không đồng bộ đó khắc phục các sự cố về đọc dữ liệu cho tôi.


0

Nếu chúng tôi cố gắng nhóm một phần các điều kiện của mình thành phương thức Func <> hoặc tiện ích mở rộng, chúng tôi sẽ gặp lỗi này, giả sử chúng tôi có một mã như thế này:

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) { .... }

Điều này sẽ ném ngoại lệ nếu chúng ta cố gắng sử dụng nó trong Where (), điều chúng ta nên làm thay vào đó là xây dựng một Vị ngữ như thế này:

public static Expression<Func<PriceList, bool>> IsCurrent()
{
    return p => (p.ValidFrom == null || p.ValidFrom <= DateTime.Now) &&
                (p.ValidTo == null || p.ValidTo >= DateTime.Now);
}

Hơn nữa có thể được đọc tại: http://www.albahari.com/nutshell/predicatebuilder.aspx


0

Vấn đề này có thể được giải quyết đơn giản bằng cách chuyển đổi dữ liệu thành một danh sách

 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 () thực hiện cuộc gọi nhưng đoạn mã trên vẫn không loại bỏ kết nối. vì vậy _webcontext của bạn vẫn có nguy cơ bị đóng tại thời điểm của dòng 1
Sonic Soul

0

Trong tình huống của tôi, vấn đề xảy ra do đăng ký tiêm phụ thuộc. Tôi đã tiêm một dịch vụ phạm vi yêu cầu đang sử dụng dbcontext vào một dịch vụ đã đăng ký đơn lẻ. Do đó dbcontext đã được sử dụng trong nhiều yêu cầu và do đó xảy ra lỗi.


0

Trong trường hợp của tôi, vấn đề không liên quan gì đến chuỗi kết nối MARS mà là nối tiếp json. Sau khi nâng cấp dự án của tôi từ NetCore2 lên 3 tôi đã gặp lỗi này.

Thêm thông tin có thể được tìm thấy ở đây


-6

Tôi đã giải quyết vấn đề này bằng cách sử dụng phần mã sau đây trước truy vấn thứ hai:

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

bạn có thể thay đổi thời gian ngủ trong vài giây

PD Hữu ích khi sử dụng chủ đề


13
Tự ý thêm Thread. Ngủ trong bất kỳ giải pháp nào là thực tiễn xấu - và đặc biệt xấu khi được sử dụng để vượt qua một vấn đề khác trong đó trạng thái của một số giá trị không hoàn toàn được hiểu. Tôi đã nghĩ rằng "Sử dụng Chủ đề" như được nêu ở dưới cùng của phản hồi có nghĩa là có ít nhất một số hiểu biết cơ bản về phân luồng - nhưng phản hồi này không tính đến bất kỳ bối cảnh nào, đặc biệt là những trường hợp đó là một ý tưởng rất tồi để sử dụng Thread.S ngủ - chẳng hạn như trên một giao diện người dùng.
Mike Tours
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.