SqlException từ Entity Framework - giao dịch mới là không được phép vì có chủ đề khác đang chạy trong phiên


600

Tôi hiện đang nhận được lỗi này:

System.Data.SqlClient.SqlException: Giao dịch mới không được phép vì có các luồng khác đang chạy trong phiên.

trong khi chạy mã này:

public class ProductManager : IProductManager
{
    #region Declare Models
    private RivWorks.Model.Negotiation.RIV_Entities _dbRiv = RivWorks.Model.Stores.RivEntities(AppSettings.RivWorkEntities_connString);
    private RivWorks.Model.NegotiationAutos.RivFeedsEntities _dbFeed = RivWorks.Model.Stores.FeedEntities(AppSettings.FeedAutosEntities_connString);
    #endregion

    public IProduct GetProductById(Guid productId)
    {
        // Do a quick sync of the feeds...
        SyncFeeds();
        ...
        // get a product...
        ...
        return product;
    }

    private void SyncFeeds()
    {
        bool found = false;
        string feedSource = "AUTO";
        switch (feedSource) // companyFeedDetail.FeedSourceTable.ToUpper())
        {
            case "AUTO":
                var clientList = from a in _dbFeed.Client.Include("Auto") select a;
                foreach (RivWorks.Model.NegotiationAutos.Client client in clientList)
                {
                    var companyFeedDetailList = from a in _dbRiv.AutoNegotiationDetails where a.ClientID == client.ClientID select a;
                    foreach (RivWorks.Model.Negotiation.AutoNegotiationDetails companyFeedDetail in companyFeedDetailList)
                    {
                        if (companyFeedDetail.FeedSourceTable.ToUpper() == "AUTO")
                        {
                            var company = (from a in _dbRiv.Company.Include("Product") where a.CompanyId == companyFeedDetail.CompanyId select a).First();
                            foreach (RivWorks.Model.NegotiationAutos.Auto sourceProduct in client.Auto)
                            {
                                foreach (RivWorks.Model.Negotiation.Product targetProduct in company.Product)
                                {
                                    if (targetProduct.alternateProductID == sourceProduct.AutoID)
                                    {
                                        found = true;
                                        break;
                                    }
                                }
                                if (!found)
                                {
                                    var newProduct = new RivWorks.Model.Negotiation.Product();
                                    newProduct.alternateProductID = sourceProduct.AutoID;
                                    newProduct.isFromFeed = true;
                                    newProduct.isDeleted = false;
                                    newProduct.SKU = sourceProduct.StockNumber;
                                    company.Product.Add(newProduct);
                                }
                            }
                            _dbRiv.SaveChanges();  // ### THIS BREAKS ### //
                        }
                    }
                }
                break;
        }
    }
}

Mô hình # 1 - Mô hình này nằm trong cơ sở dữ liệu trên Dev Server của chúng tôi. Mô hình số 1 http://content.screencast.com/users/Keith.Barrows/ Folders / Jing / media / bdb2b000-6e60-4af0-a7a1-2bb6b05d8bc1/Model1.png

Mô hình # 2 - Mô hình này nằm trong cơ sở dữ liệu trên Máy chủ Prod của chúng tôi và được cập nhật mỗi ngày bằng các nguồn cấp dữ liệu tự động. văn bản thay thế http://content.screencast.com/users/Keith.Barrows/printers/Jing/media/4260259f-bce6-43d5-9d2a-017bd9a980d4/Model2.png

Lưu ý - Các mục được khoanh tròn màu đỏ trong Mô hình # 1 là các trường tôi sử dụng để "ánh xạ" sang Mô hình # 2. Hãy bỏ qua các vòng tròn màu đỏ trong Mô hình # 2: đó là từ một câu hỏi khác mà tôi đã trả lời.

Lưu ý: Tôi vẫn cần đưa vào kiểm tra isDelatted để tôi có thể xóa nó khỏi DB1 nếu nó đã hết hàng tồn kho của khách hàng.

Tất cả những gì tôi muốn làm, với mã cụ thể này, là kết nối một công ty trong DB1 với một máy khách trong DB2, lấy danh sách sản phẩm của họ từ DB2 và XÁC NHẬN nó trong DB1 nếu nó chưa có ở đó. Lần đầu tiên thông qua nên là một hàng tồn kho đầy đủ. Mỗi lần nó được chạy ở đó sau khi không có gì xảy ra trừ khi hàng tồn kho mới xuất hiện trên thức ăn qua đêm.

Vì vậy, câu hỏi lớn - làm thế nào để tôi giải quyết lỗi giao dịch tôi đang nhận được? Tôi có cần bỏ và tạo lại bối cảnh của mình mỗi lần qua các vòng lặp (không có ý nghĩa với tôi) không?


6
Đây là câu hỏi chi tiết nhất tôi từng thấy.

9
Bất cứ ai bỏ lỡ các thủ tục lưu trữ chưa?
David

Câu trả lời:


690

Sau nhiều lần nhổ tóc tôi phát hiện ra rằng các foreachvòng là thủ phạm. Điều cần phải xảy ra là gọi cho EF nhưng trả nó về một IList<T>loại mục tiêu đó sau đó lặp lại trênIList<T> .

Thí dụ:

IList<Client> clientList = from a in _dbFeed.Client.Include("Auto") select a;
foreach (RivWorks.Model.NegotiationAutos.Client client in clientList)
{
   var companyFeedDetailList = from a in _dbRiv.AutoNegotiationDetails where a.ClientID == client.ClientID select a;
    // ...
}

14
Vâng, điều này cũng khiến tôi đau đầu. Tôi gần như ngã khỏi ghế khi phát hiện ra vấn đề! Tôi hiểu các lý do kỹ thuật đằng sau vấn đề này, nhưng điều này không trực quan và nó không giúp nhà phát triển rơi vào "hố thành công" blog.msdn.com/brada/archive/2003/10/02/50420. aspx
Bác sĩ Jones

9
Điều đó có tệ cho hiệu năng của các bộ dữ liệu lớn không? Nếu bạn có một triệu hồ sơ trong bảng. ToList () sẽ hút tất cả chúng vào bộ nhớ. Tôi đang gặp phải vấn đề này và đang tự hỏi liệu những điều sau đây có khả thi không a) Tách thực thể b) Tạo một ObjectContext mới và gắn thực thể tách rời với nó. c) Gọi SaveChanges () trên ObjectContext mới d) Tách thực thể khỏi ObjectContext mới e) Đính kèm lại với ObjectContext cũ
Abhijeet Patel

150
Vấn đề là bạn không thể gọi SaveChangestrong khi bạn vẫn lấy kết quả từ DB. Do đó, một giải pháp khác chỉ là lưu các thay đổi khi vòng lặp đã hoàn thành.
vẽ Noakes

4
Cũng bị cắn, tôi đã thêm nó vào Microsoft Connect: connect.microsoft.com/VisualStudio/feedback/details/612369/ Hãy cảm thấy thoải mái khi bỏ phiếu.
Ian Mercer

36
Các nhà phát triển của chúng tôi có xu hướng nối thêm .ToList () vào bất kỳ truy vấn LINQ nào mà không nghĩ đến hậu quả. Đây phải là lần đầu tiên thêm .ToList () thực sự hữu ích!
Marc

267

Như bạn đã xác định, bạn không thể lưu từ bên trong foreachvẫn đang vẽ từ cơ sở dữ liệu thông qua một trình đọc hoạt động.

Gọi ToList()hoặc ToArray()là tốt cho các tập dữ liệu nhỏ, nhưng khi bạn có hàng ngàn hàng, bạn sẽ tiêu tốn một lượng lớn bộ nhớ.

Tốt hơn là tải các hàng trong khối.

public static class EntityFrameworkUtil
{
    public static IEnumerable<T> QueryInChunksOf<T>(this IQueryable<T> queryable, int chunkSize)
    {
        return queryable.QueryChunksOfSize(chunkSize).SelectMany(chunk => chunk);
    }

    public static IEnumerable<T[]> QueryChunksOfSize<T>(this IQueryable<T> queryable, int chunkSize)
    {
        int chunkNumber = 0;
        while (true)
        {
            var query = (chunkNumber == 0)
                ? queryable 
                : queryable.Skip(chunkNumber * chunkSize);
            var chunk = query.Take(chunkSize).ToArray();
            if (chunk.Length == 0)
                yield break;
            yield return chunk;
            chunkNumber++;
        }
    }
}

Đưa ra các phương thức mở rộng ở trên, bạn có thể viết truy vấn của mình như sau:

foreach (var client in clientList.OrderBy(c => c.Id).QueryInChunksOf(100))
{
    // do stuff
    context.SaveChanges();
}

Đối tượng truy vấn bạn gọi phương thức này phải được đặt hàng. Điều này là do Entity Framework chỉ hỗ trợ IQueryable<T>.Skip(int)các truy vấn theo thứ tự, điều này có ý nghĩa khi bạn cho rằng nhiều truy vấn cho các phạm vi khác nhau yêu cầu thứ tự phải ổn định. Nếu việc đặt hàng không quan trọng đối với bạn, chỉ cần đặt hàng theo khóa chính là có thể có một chỉ mục được nhóm.

Phiên bản này sẽ truy vấn cơ sở dữ liệu theo lô 100. Lưu ý SaveChanges()được gọi cho mỗi thực thể.

Nếu bạn muốn cải thiện đáng kể thông lượng của mình, bạn nên gọi SaveChanges()ít thường xuyên hơn. Sử dụng mã như thế này thay thế:

foreach (var chunk in clientList.OrderBy(c => c.Id).QueryChunksOfSize(100))
{
    foreach (var client in chunk)
    {
        // do stuff
    }
    context.SaveChanges();
}

Điều này dẫn đến các cuộc gọi cập nhật cơ sở dữ liệu ít hơn 100 lần. Tất nhiên, mỗi cuộc gọi đó sẽ mất nhiều thời gian hơn để hoàn thành, nhưng cuối cùng bạn vẫn đi ra phía trước. mileage của bạn có thể thay đổi, nhưng điều này là thế giới nhanh hơn đối với tôi.

Và nó nhận được xung quanh ngoại lệ bạn đã thấy.

EDIT Tôi đã xem lại câu hỏi này sau khi chạy SQL Profiler và cập nhật một số điều để cải thiện hiệu suất. Đối với bất kỳ ai quan tâm, đây là một số SQL mẫu hiển thị những gì được tạo bởi DB.

Vòng lặp đầu tiên không cần bỏ qua bất cứ điều gì, vì vậy đơn giản hơn.

SELECT TOP (100)                     -- the chunk size 
[Extent1].[Id] AS [Id], 
[Extent1].[Name] AS [Name], 
FROM [dbo].[Clients] AS [Extent1]
ORDER BY [Extent1].[Id] ASC

Các cuộc gọi tiếp theo cần bỏ qua các kết quả trước đó, vì vậy giới thiệu cách sử dụng row_number:

SELECT TOP (100)                     -- the chunk size
[Extent1].[Id] AS [Id], 
[Extent1].[Name] AS [Name], 
FROM (
    SELECT [Extent1].[Id] AS [Id], [Extent1].[Name] AS [Name], row_number()
    OVER (ORDER BY [Extent1].[Id] ASC) AS [row_number]
    FROM [dbo].[Clients] AS [Extent1]
) AS [Extent1]
WHERE [Extent1].[row_number] > 100   -- the number of rows to skip
ORDER BY [Extent1].[Id] ASC

17
Cảm ơn. Lời giải thích của bạn hữu ích hơn nhiều so với câu được đánh dấu là "Đã trả lời".
Wagner da Silva

1
Điều đó thật tuyệt. chỉ một điều: Nếu bạn đang truy vấn trên một cột và cập nhật giá trị của cột đó, bạn cần phải là kho của chunkNumber ++; . Giả sử bạn có một cột "ModifiedDate" và bạn đang truy vấn .Where (x => x.ModifiedDate! = Null) và ở cuối phần trước, bạn đặt giá trị cho ModifiedDate. Theo cách này, bạn không lặp lại một nửa số hồ sơ vì một nửa số hồ sơ đang bị bỏ qua.
Arvand

Thật không may trên các bộ dữ liệu khổng lồ, bạn sẽ nhận được OutofMemoryException - xem giải thích trong tập dữ liệu lớn của khung Entity, ngoại trừ bộ nhớ . Tôi đã mô tả cách làm mới bối cảnh của bạn từng đợt trong SqlException từ Entity Framework - Giao dịch mới không được phép vì có các luồng khác đang chạy trong phiên
Michael Freidgeim

Tôi nghĩ rằng điều này sẽ làm việc. bỏ qua var = 0; const int lấy = 100; Danh sách <Nhân viên> emps; while ((emps = db.Emprocod.Skip (bỏ qua) .Take (Take) .ToList ()). Count> 0) {Skip + = Take; foreach (var emp in emps) {// Thực hiện ở đây}} Tôi sẽ đưa ra câu trả lời này nhưng nó sẽ bị chôn vùi dưới đống câu trả lời bên dưới và nó liên quan đến câu hỏi này.
jwize

123

Hiện tại chúng tôi đã đăng phản hồi chính thức cho lỗi được mở trên Connect . Cách giải quyết mà chúng tôi đề xuất như sau:

Lỗi này là do Entity Framework tạo ra một giao dịch ngầm trong cuộc gọi SaveChanges (). Cách tốt nhất để khắc phục lỗi là sử dụng một mẫu khác (nghĩa là không lưu trong khi đang đọc) hoặc bằng cách tuyên bố rõ ràng một giao dịch. Dưới đây là ba giải pháp có thể:

// 1: Save after iteration (recommended approach in most cases)
using (var context = new MyContext())
{
    foreach (var person in context.People)
    {
        // Change to person
    }
    context.SaveChanges();
}

// 2: Declare an explicit transaction
using (var transaction = new TransactionScope())
{
    using (var context = new MyContext())
    {
        foreach (var person in context.People)
        {
            // Change to person
            context.SaveChanges();
        }
    }
    transaction.Complete();
}

// 3: Read rows ahead (Dangerous!)
using (var context = new MyContext())
{
    var people = context.People.ToList(); // Note that this forces the database
                                          // to evaluate the query immediately
                                          // and could be very bad for large tables.

    foreach (var person in people)
    {
        // Change to person
        context.SaveChanges();
    }
} 

6
Nếu bạn thực hiện lộ trình Giao dịch, chỉ cần ném vào Giao diện viên có thể không khắc phục được - đừng quên kéo dài Thời gian chờ nếu việc bạn đang làm có thể mất nhiều thời gian - ví dụ: nếu bạn sẽ gỡ lỗi tương tác với mã tạo ra Cuộc gọi DB. Đây là mã kéo dài thời gian chờ giao dịch đến một giờ: bằng cách sử dụng (var giao dịch = giao dịch mới (TransactionScopeOption.Required, TimeSpan mới (1, 0, 0)))
Chris Moschini

Tôi đã gặp phải lỗi này ngay lần đầu tiên tôi đã lạc đề từ "con đường hướng dẫn" thành một ví dụ thực tế! Tuy nhiên, đối với tôi, giải pháp đơn giản hơn, TIẾT KIỆM SAU KHI NÓ, thì tốt hơn! (Tôi nghĩ rằng 99% trường hợp này là như vậy và chỉ có 1% thực sự PHẢI thực hiện cơ sở dữ liệu lưu bên trong vòng lặp)
nhện

Tổng. Tôi chỉ va vào lỗi này. Rất khó chịu. Đề xuất thứ 2 hoạt động như một cơ duyên đối với tôi cùng với việc chuyển SaveChanges của tôi vào vòng lặp. Tôi nghĩ rằng có các thay đổi lưu bên ngoài vòng lặp là tốt hơn cho các thay đổi theo đợt. Nhưng không sao. Tôi đoán là không?! :(
Ông Young

Không làm việc cho tôi .NET 4.5. Khi sử dụng TransactionScope, tôi gặp lỗi sau "Nhà cung cấp cơ sở không thành công trên EnlistTransaction. {" Trình quản lý giao dịch đối tác đã vô hiệu hóa hỗ trợ cho các giao dịch từ xa / mạng. (Ngoại lệ từ HRESULT: 0x8004D025) "}". Tôi kết thúc việc làm ngoài vòng lặp.
Diganta Kumar

Sử dụng TransactionScope rất nguy hiểm, vì bảng bị khóa trong thời gian của toàn bộ giao dịch.
Michael Freidgeim

19

Thật vậy, bạn không thể lưu các thay đổi bên trong một foreachvòng lặp trong C # bằng Entity Framework.

context.SaveChanges() phương thức hoạt động như một cam kết trên một hệ thống cơ sở dữ liệu thông thường (RDMS).

Chỉ cần thực hiện tất cả các thay đổi (Entity Framework sẽ lưu trữ bộ đệm) và sau đó lưu tất cả chúng cùng một lúc gọi SaveChanges()sau vòng lặp (bên ngoài nó), giống như một lệnh cam kết cơ sở dữ liệu.

Điều này hoạt động nếu bạn có thể lưu tất cả các thay đổi cùng một lúc.


2
Tôi nghĩ thật thú vị khi thấy "hệ thống cơ sở dữ liệu thông thường (RDMS)" ở đây
Dinerdo

1
Điều này có vẻ sai, vì liên tục gọi SaveChanges là ổn trong 90% bối cảnh trong EF.
Pxtl

Có vẻ như việc liên tục gọi SaveChanges là ổn, trừ khi vòng lặp foreach đang lặp lại trên một Thực thể db.
kerbasaurus

1
Aha! Mang bối cảnh bên trong cho mỗi vòng lặp! (pffft ... tôi đã nghĩ gì? ..) Cảm ơn!
Adam Cox

18

Chỉ cần đặt context.SaveChanges()sau khi kết thúc foreach(vòng lặp) của bạn .


Đây là lựa chọn tốt hơn mà tôi đã tìm ra trong trường hợp của mình do lưu bên trong foreach
Almeida

2
Đây không phải luôn luôn là một lựa chọn.
Pxtl

9

Luôn sử dụng lựa chọn của bạn làm Danh sách

Ví dụ:

var tempGroupOfFiles = Entities.Submited_Files.Where(r => r.FileStatusID == 10 && r.EventID == EventId).ToList();

Sau đó lặp qua Bộ sưu tập trong khi lưu thay đổi

 foreach (var item in tempGroupOfFiles)
             {
                 var itemToUpdate = item;
                 if (itemToUpdate != null)
                 {
                     itemToUpdate.FileStatusID = 8;
                     itemToUpdate.LastModifiedDate = DateTime.Now;
                 }
                 Entities.SaveChanges();

             }

1
Đây không phải là thực hành tốt ở tất cả. Bạn không nên thực hiện SaveChang thường xuyên nếu bạn không cần và bạn chắc chắn không nên "Luôn sử dụng lựa chọn của mình làm Danh sách"
Dinerdo

@Dinerdo nó thực sự phụ thuộc vào kịch bản. Trong trường hợp của tôi, tôi có 2 vòng lặp foreach. Bên ngoài có truy vấn db là danh sách. Ví dụ, điều này dẫn qua các thiết bị phần cứng. Foreach bên trong lấy một số dữ liệu từ mỗi thiết bị. Theo yêu cầu, tôi cần lưu vào cơ sở dữ liệu dữ liệu sau khi từng dữ liệu được truy xuất từ ​​mỗi thiết bị. Nó không phải là một tùy chọn để lưu tất cả dữ liệu vào cuối quá trình. Tôi đã gặp lỗi tương tự nhưng giải pháp của mzonerz đã hoạt động.
jstuardo

@jstuardo Ngay cả với đợt?
Dinerdo

@Dinerdo Tôi đồng ý rằng nó không phải là một thực hành tốt ở cấp độ triết học. Tuy nhiên, một số tình huống tồn tại trong đó bên trong vòng lặp for, mã gọi một phương thức khác (giả sử phương thức AddToLog ()) bao gồm lệnh gọi db.SaveChanges () cục bộ. Trong tình huống này, bạn không thể thực sự kiểm soát cuộc gọi tới db. Thay đổi. Trong trường hợp này, sử dụng ToList () hoặc một cấu trúc tương tự sẽ hoạt động theo đề xuất của mzonerz. Cảm ơn!
A. Varma

Trong thực tế, điều này sẽ làm tổn thương bạn nhiều hơn nó sẽ giúp. Tôi đứng trước những gì tôi đã nói - ToList () chắc chắn không nên được sử dụng mọi lúc và lưu các thay đổi sau mỗi mục duy nhất là điều cần tránh ở bất cứ nơi nào có thể trong một ứng dụng hiệu suất cao. Đây sẽ là một bản sửa lỗi tạm thời IMO. Dù bạn đăng nhập phương pháp nào thì cũng nên lý tưởng tận dụng bộ đệm.
Dinerdo

8

FYI: từ một cuốn sách và một số dòng được điều chỉnh bởi vì nó vẫn còn hiệu lực:

Gọi phương thức SaveChanges () bắt đầu một giao dịch tự động khôi phục tất cả các thay đổi được duy trì cho cơ sở dữ liệu nếu một ngoại lệ xảy ra trước khi lặp lại hoàn thành; nếu không thì giao dịch cam kết. Bạn có thể muốn áp dụng phương pháp sau mỗi lần cập nhật hoặc xóa thực thể thay vì sau khi lặp lại hoàn thành, đặc biệt là khi bạn đang cập nhật hoặc xóa số lượng lớn thực thể.

Nếu bạn cố gắng gọi SaveChanges () trước khi tất cả dữ liệu được xử lý, bạn sẽ phải chịu một giao dịch "Giao dịch mới không được phép vì có các luồng khác đang chạy trong phiên". Ngoại lệ xảy ra do SQL Server không cho phép bắt đầu một giao dịch mới trên một kết nối có mở SqlDataReader, ngay cả với Bộ bản ghi hoạt động nhiều lần (MARS) được kích hoạt bởi chuỗi kết nối (chuỗi kết nối mặc định của EF cho phép MARS)

Đôi khi tốt hơn để hiểu tại sao mọi thứ đang xảy ra ;-)


1
Một cách tốt để tránh điều này là khi bạn có một đầu đọc mở để mở cái thứ hai và đặt những thao tác đó vào đầu đọc thứ hai. Đây là thứ bạn có thể cần khi cập nhật tổng thể / chi tiết trong khung thực thể. Bạn mở kết nối đầu tiên cho bản ghi chính và bản thứ hai cho bản ghi chi tiết. Nếu bạn chỉ đọc thì sẽ không có vấn đề gì. các vấn đề xảy ra trong quá trình cập nhật.
Herman Van Der Blom

Giải thích hữu ích. bạn nói đúng, thật tốt để hiểu tại sao mọi thứ đang xảy ra.
Dov Miller

8

Tạo danh sách truy vấn của bạn thành .ToList () và nó sẽ hoạt động tốt.


1
Vui lòng cung cấp một ví dụ thay vì chỉ đăng một giải pháp.
Ronnie Oosting

5

Tôi đã nhận được vấn đề tương tự nhưng trong một tình huống khác. Tôi đã có một danh sách các mục trong một hộp danh sách. Người dùng có thể nhấp vào một mục và chọn xóa nhưng tôi đang sử dụng một Proc được lưu trữ để xóa mục đó vì có rất nhiều logic liên quan đến việc xóa mục đó. Khi tôi gọi Proc được lưu trữ, thao tác xóa sẽ hoạt động tốt nhưng mọi cuộc gọi tới SaveChanges trong tương lai sẽ gây ra lỗi. Giải pháp của tôi là gọi cho Proc được lưu trữ bên ngoài EF và điều này hoạt động tốt. Vì một số lý do khi tôi gọi cho Proc được lưu trữ bằng cách sử dụng cách làm của EF, nó sẽ để lại một cái gì đó mở.


3
Có vấn đề tương tự gần đây: lý do trong trường hợp của tôi là SELECTtuyên bố trong thủ tục được lưu trữ tạo ra tập kết quả trống và nếu tập kết quả đó không được đọc, SaveChangesđã ném ngoại lệ đó.
n0rd

Điều tương tự với kết quả chưa đọc từ SP, cảm ơn rất nhiều về gợi ý)
Pavel K

4

Dưới đây là 2 tùy chọn khác cho phép bạn gọi SaveChanges () trong mỗi vòng lặp.

Tùy chọn đầu tiên là sử dụng một DBContext để tạo các đối tượng danh sách của bạn để lặp qua, sau đó tạo DBContext thứ 2 để gọi SaveChanges () trên. Đây là một ví dụ:

//Get your IQueryable list of objects from your main DBContext(db)    
IQueryable<Object> objects = db.Object.Where(whatever where clause you desire);

//Create a new DBContext outside of the foreach loop    
using (DBContext dbMod = new DBContext())
{   
    //Loop through the IQueryable       
    foreach (Object object in objects)
    {
        //Get the same object you are operating on in the foreach loop from the new DBContext(dbMod) using the objects id           
        Object objectMod = dbMod.Object.Find(object.id);

        //Make whatever changes you need on objectMod
        objectMod.RightNow = DateTime.Now;

        //Invoke SaveChanges() on the dbMod context         
        dbMod.SaveChanges()
    }
}

Tùy chọn thứ 2 là lấy danh sách các đối tượng cơ sở dữ liệu từ DBContext, nhưng chỉ chọn id. Và sau đó lặp qua danh sách id (có lẽ là int) và lấy đối tượng tương ứng với từng int và gọi SaveChanges () theo cách đó. Ý tưởng đằng sau phương thức này là lấy một danh sách lớn các số nguyên, hiệu quả hơn rất nhiều sau đó lấy một danh sách lớn các đối tượng db và gọi .ToList () trên toàn bộ đối tượng. Dưới đây là một ví dụ về phương pháp này:

//Get the list of objects you want from your DBContext, and select just the Id's and create a list
List<int> Ids = db.Object.Where(enter where clause here)Select(m => m.Id).ToList();

var objects = Ids.Select(id => db.Objects.Find(id));

foreach (var object in objects)
{
    object.RightNow = DateTime.Now;
    db.SaveChanges()
}

Đây là một sự thay thế tuyệt vời mà tôi nghĩ và đã làm, nhưng điều này cần phải được nâng cao. Lưu ý: i) bạn có thể lặp lại dưới dạng vô số, điều này tốt cho các tập hợp rất lớn; ii) Bạn có thể sử dụng lệnh NoTracking để tránh sự cố khi tải quá nhiều bản ghi (nếu đó là kịch bản của bạn); iii) Tôi cũng thực sự thích tùy chọn chỉ khóa chính - điều đó rất thông minh vì bạn đang tải ít dữ liệu hơn vào bộ nhớ, nhưng bạn không xử lý Take / Skip trên bộ dữ liệu tiềm ẩn động.
Todd

4

Nếu bạn gặp lỗi này do foreach và bạn thực sự cần lưu một thực thể đầu tiên bên trong vòng lặp và sử dụng danh tính được tạo thêm trong vòng lặp, như trong trường hợp của tôi, giải pháp đơn giản nhất là sử dụng một DBContext khác để chèn thực thể sẽ trả về Id và sử dụng Id này trong bối cảnh bên ngoài

Ví dụ

    using (var context = new DatabaseContext())
    {
        ...
        using (var context1 = new DatabaseContext())
        {
            ...
               context1.SaveChanges();
        }                         
        //get id of inserted object from context1 and use is.   
      context.SaveChanges();
   }

2

Vì vậy, trong dự án tôi đã gặp vấn đề chính xác như vậy, vấn đề không nằm ở foreachhoặc .toList()nó thực sự nằm trong cấu hình AutoFac mà chúng tôi đã sử dụng. Điều này tạo ra một số tình huống kỳ lạ là lỗi trên đã bị ném nhưng cũng có một loạt các lỗi tương đương khác bị ném.

Đây là sửa lỗi của chúng tôi: Thay đổi điều này:

container.RegisterType<DataContext>().As<DbContext>().InstancePerLifetimeScope();
container.RegisterType<DbFactory>().As<IDbFactory>().SingleInstance();
container.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerRequest();

Đến:

container.RegisterType<DataContext>().As<DbContext>().As<DbContext>();
container.RegisterType<DbFactory>().As<IDbFactory>().As<IDbFactory>().InstancePerLifetimeScope();
container.RegisterType<UnitOfWork>().As<IUnitOfWork>().As<IUnitOfWork>();//.InstancePerRequest();

Bạn có thể giải thích những gì bạn nghĩ vấn đề là? Bạn đã giải quyết điều này bằng cách tạo Dbcontext mới mỗi lần?
Eran otzap

2

Tôi biết đó là một câu hỏi cũ nhưng tôi đã gặp phải lỗi này ngày hôm nay.

và tôi thấy rằng, lỗi này có thể được đưa ra khi kích hoạt bảng cơ sở dữ liệu bị lỗi.

để biết thông tin của bạn, bạn cũng có thể kiểm tra các kích hoạt bảng của mình khi bạn gặp lỗi này.


2

Tôi cần phải đọc một Kết quả lớn và cập nhật một số hồ sơ trong bảng. Tôi đã cố gắng sử dụng các đoạn như đề xuất trong câu trả lời của Drew Noakes .

Thật không may sau 50000 hồ sơ tôi đã nhận được OutofMemoryException. Câu trả lời Thực thể khung dữ liệu lớn, ngoại lệ bộ nhớ giải thích, rằng

EF tạo bản sao dữ liệu thứ hai sử dụng để phát hiện thay đổi (để nó có thể tiếp tục thay đổi cơ sở dữ liệu). EF giữ bộ thứ hai này trong suốt thời gian tồn tại của bối cảnh và bộ này sẽ khiến bạn hết bộ nhớ.

Đề xuất là tạo lại bối cảnh của bạn cho mỗi đợt.

Vì vậy, tôi đã truy xuất các giá trị Tối thiểu và Tối đa của khóa chính - các bảng có các khóa chính là số nguyên tăng tự động. Sau đó, tôi đã truy xuất từ ​​các khối cơ sở dữ liệu của các bản ghi bằng cách mở ngữ cảnh cho mỗi khối. Sau khi xử lý bối cảnh chunk đóng lại và giải phóng bộ nhớ. Nó đảm bảo rằng việc sử dụng bộ nhớ không tăng lên.

Dưới đây là đoạn trích từ mã của tôi:

  public void ProcessContextByChunks ()
  {
        var tableName = "MyTable";
         var startTime = DateTime.Now;
        int i = 0;
         var minMaxIds = GetMinMaxIds();
        for (int fromKeyID= minMaxIds.From; fromKeyID <= minMaxIds.To; fromKeyID = fromKeyID+_chunkSize)
        {
            try
            {
                using (var context = InitContext())
                {   
                    var chunk = GetMyTableQuery(context).Where(r => (r.KeyID >= fromKeyID) && (r.KeyID < fromKeyID+ _chunkSize));
                    try
                    {
                        foreach (var row in chunk)
                        {
                            foundCount = UpdateRowIfNeeded(++i, row);
                        }
                        context.SaveChanges();
                    }
                    catch (Exception exc)
                    {
                        LogChunkException(i, exc);
                    }
                }
            }
            catch (Exception exc)
            {
                LogChunkException(i, exc);
            }
        }
        LogSummaryLine(tableName, i, foundCount, startTime);
    }

    private FromToRange<int> GetminMaxIds()
    {
        var minMaxIds = new FromToRange<int>();
        using (var context = InitContext())
        {
            var allRows = GetMyTableQuery(context);
            minMaxIds.From = allRows.Min(n => (int?)n.KeyID ?? 0);  
            minMaxIds.To = allRows.Max(n => (int?)n.KeyID ?? 0);
        }
        return minMaxIds;
    }

    private IQueryable<MyTable> GetMyTableQuery(MyEFContext context)
    {
        return context.MyTable;
    }

    private  MyEFContext InitContext()
    {
        var context = new MyEFContext();
        context.Database.Connection.ConnectionString = _connectionString;
        //context.Database.Log = SqlLog;
        return context;
    }

FromToRange là một cấu trúc đơn giản với các thuộc tính From và To.


Tôi không thấy bạn đã "làm mới" bối cảnh của bạn như thế nào. Có vẻ như bạn chỉ đơn giản là tạo ra một bối cảnh mới cho mỗi đoạn.
Suncat2000

@ Suncat2000, bạn đã đúng, bối cảnh nên là một đối tượng sống ngắn stackoverflow.com/questions/43474112/
Kẻ

2

Chúng tôi bắt đầu thấy lỗi này "Giao dịch mới không được phép vì có các luồng khác đang chạy trong phiên" sau khi di chuyển từ EF5 sang EF6.

Google đã đưa chúng tôi đến đây nhưng chúng tôi không gọi SaveChanges() bên trong vòng lặp. Các lỗi đã được đưa ra khi thực hiện một thủ tục được lưu trữ bằng cách sử dụng ObjectContext.ExecuteFunction bên trong một vòng lặp foreach đọc từ DB.

Bất kỳ cuộc gọi nào đến ObjectContext.ExecuteFunction đều kết thúc chức năng trong một giao dịch. Bắt đầu một giao dịch trong khi đã có một trình đọc mở gây ra lỗi.

Có thể vô hiệu hóa gói SP trong giao dịch bằng cách đặt tùy chọn sau.

_context.Configuration.EnsureTransactionsForFunctionsAndCommands = false;

Các EnsureTransactionsForFunctionsAndCommandstùy chọn cho phép người SP để chạy mà không cần tạo giao dịch riêng của mình và các lỗi không còn được nâng lên.

Thuộc tính DbContextConfiguration.EnsureTransilitiesForFiftsAndCommands


1

Tôi cũng đã phải đối mặt với vấn đề tương tự.

Dưới đây là nguyên nhân và giải pháp.

http://bloss.msdn.com/b/cbiyikoglu/archive/2006/11/21/mars-transilities-and-sql-error-3997-3988-or-3983.aspx

Đảm bảo trước khi thực hiện các lệnh thao tác dữ liệu như chèn, cập nhật, bạn đã đóng tất cả các trình đọc SQL hoạt động trước đó.

Lỗi phổ biến nhất là các hàm đọc dữ liệu từ db và trả về giá trị. Ví dụ, các hàm như isRecordExist.

Trong trường hợp này, chúng tôi ngay lập tức trở về từ chức năng nếu chúng tôi tìm thấy bản ghi và quên đóng trình đọc.


7
"Đóng một trình đọc" có nghĩa là gì trong Entity Framework? Không có trình đọc hiển thị trong một truy vấn như var result = từ khách hàng trong myDb. Khách hàng nơi khách hàng.Id == customerId chọn khách hàng; trả về kết quả.FirstOrDefault ();
Anthony

@Anthony Như các câu trả lời khác nói, nếu bạn sử dụng EF để liệt kê truy vấn LINQ (IQueryable), DataReader bên dưới sẽ vẫn mở cho đến khi hàng cuối cùng được lặp lại. Nhưng mặc dù MARS là một tính năng quan trọng để kích hoạt trong chuỗi kết nối, vấn đề trong OP vẫn không được giải quyết chỉ với MARS. Vấn đề là cố gắng SaveChanges trong khi DataReader bên dưới vẫn đang mở.
Todd

1

Mã dưới đây hoạt động với tôi:

private pricecheckEntities _context = new pricecheckEntities();

...

private void resetpcheckedtoFalse()
{
    try
    {
        foreach (var product in _context.products)
        {
            product.pchecked = false;
            _context.products.Attach(product);
            _context.Entry(product).State = EntityState.Modified;
        }
        _context.SaveChanges();
    }
    catch (Exception extofException)
    {
        MessageBox.Show(extofException.ToString());

    }
    productsDataGrid.Items.Refresh();
}

2
Chào mừng đến với SO! Xem xét thêm một lời giải thích và / hoặc các liên kết mô tả lý do tại sao điều này làm việc cho bạn. Các câu trả lời chỉ có mã thường được coi là không có chất lượng tốt cho SO.
codeMagic

1

Trong trường hợp của tôi, sự cố đã xuất hiện khi tôi gọi Thủ tục lưu trữ qua EF và sau đó SaveChanges ném ngoại lệ này. Vấn đề là trong việc gọi thủ tục, điều tra viên đã không được xử lý. Tôi đã sửa mã theo cách sau:

public bool IsUserInRole(string username, string roleName, DataContext context)
{          
   var result = context.aspnet_UsersInRoles_IsUserInRoleEF("/", username, roleName);

   //using here solved the issue
   using (var en = result.GetEnumerator()) 
   {
     if (!en.MoveNext())
       throw new Exception("emty result of aspnet_UsersInRoles_IsUserInRoleEF");
     int? resultData = en.Current;

     return resultData == 1;//1 = success, see T-SQL for return codes
   }
}

0

Tôi đến bữa tiệc muộn nhưng hôm nay tôi cũng gặp phải lỗi tương tự và cách tôi giải quyết rất đơn giản. Kịch bản của tôi tương tự như mã đã cho này Tôi đang thực hiện các giao dịch DB bên trong các vòng lặp lồng nhau.

Vấn đề là vì một giao dịch DB đơn lẻ mất nhiều thời gian hơn một chút so với mỗi vòng lặp nên một khi giao dịch trước đó không hoàn thành thì lực kéo mới sẽ tạo ra một ngoại lệ, vì vậy giải pháp là tạo một đối tượng mới trong vòng lặp for-every nơi bạn đang thực hiện một giao dịch db.

Đối với các kịch bản đã đề cập ở trên, giải pháp sẽ như sau:

foreach (RivWorks.Model.Negotiation.AutoNegotiationDetails companyFeedDetail in companyFeedDetailList)
                {
private RivWorks.Model.Negotiation.RIV_Entities _dbRiv = RivWorks.Model.Stores.RivEntities(AppSettings.RivWorkEntities_connString);
                    if (companyFeedDetail.FeedSourceTable.ToUpper() == "AUTO")
                    {
                        var company = (from a in _dbRiv.Company.Include("Product") where a.CompanyId == companyFeedDetail.CompanyId select a).First();
                        foreach (RivWorks.Model.NegotiationAutos.Auto sourceProduct in client.Auto)
                        {
                            foreach (RivWorks.Model.Negotiation.Product targetProduct in company.Product)
                            {
                                if (targetProduct.alternateProductID == sourceProduct.AutoID)
                                {
                                    found = true;
                                    break;
                                }
                            }
                            if (!found)
                            {
                                var newProduct = new RivWorks.Model.Negotiation.Product();
                                newProduct.alternateProductID = sourceProduct.AutoID;
                                newProduct.isFromFeed = true;
                                newProduct.isDeleted = false;
                                newProduct.SKU = sourceProduct.StockNumber;
                                company.Product.Add(newProduct);
                            }
                        }
                        _dbRiv.SaveChanges();  // ### THIS BREAKS ### //
                    }
                }

0

Tôi hơi muộn một chút, nhưng tôi cũng có lỗi này. Tôi đã giải quyết vấn đề bằng cách kiểm tra xem giá trị mà cập nhật ở đâu.

Tôi phát hiện ra rằng truy vấn của tôi là sai và có hơn 250 bản chỉnh sửa đang chờ xử lý. Vì vậy, tôi đã sửa chữa truy vấn của mình và bây giờ nó hoạt động chính xác.

Vì vậy, trong tình huống của tôi: Kiểm tra lỗi truy vấn, bằng cách gỡ lỗi kết quả mà truy vấn trả về. Sau đó sửa câu hỏi.

Hy vọng điều này sẽ giúp giải quyết các vấn đề trong tương lai.

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.