Làm cách nào để xóa các thực thể được theo dõi trong khung thực thể


81

Tôi đang chạy một số mã sửa chữa chạy trên một đống lớn các thực thể, khi tốc độ tiến triển của nó giảm xuống, đó là do số lượng các thực thể được theo dõi trong ngữ cảnh tăng lên với mỗi lần lặp, có thể mất nhiều thời gian nên tôi sẽ lưu các thay đổi vào cuối của mỗi lần lặp. Mỗi lần lặp là độc lập và không thay đổi các thực thể được tải trước.

Tôi biết tôi có thể tắt theo dõi thay đổi nhưng tôi không muốn, bởi vì nó không phải là một mã chèn hàng loạt, mà là tải các thực thể và tính toán một số thứ và nếu các số không chính xác, hãy đặt các số mới và cập nhật / xóa / tạo một số thực thể bổ sung. Tôi biết mình có thể tạo một DbContext mới cho mỗi lần lặp và có lẽ điều đó sẽ chạy nhanh hơn so với làm tất cả trong cùng một trường hợp, nhưng tôi đang nghĩ rằng có thể có một cách tốt hơn.

Vì vậy, câu hỏi là; Có cách nào để xóa các thực thể đã được tải trước đó trong ngữ cảnh db không?


5
Bạn chỉ có thể gọi context.Entry(entity).State = EntityState.Detachedvà nó sẽ ngừng theo dõi thực thể cụ thể đó.
Ben Robinson

2
Tại sao bạn không tạo một Ngữ cảnh mới? Thực sự không có chi phí lớn trừ khi bạn cần mã được tối ưu hóa.
Adrian Nasui

khung thực thể truy cập máy chủ cơ sở dữ liệu chỉ cho các thực thể đã thay đổi, bạn không phải lo lắng về hiệu suất về điều đó. nhưng bạn có thể tạo một ngữ cảnh mới chỉ bao gồm các bảng bạn làm việc để làm cho nó nhanh hơn.
İsmet Alkan

1
@IsThat Vì vậy, việc phát hiện các thay đổi cần có thời gian, tôi không lo lắng về DbPerformance.
hazimdikenli

bạn đã thực sự gỡ lỗi và theo dõi tắc nghẽn hiệu suất hay chỉ giả sử điều này?
İsmet Alkan

Câu trả lời:


117

Bạn có thể thêm một phương thức vào của bạn DbContexthoặc một phương thức tiện ích mở rộng sử dụng ChangeTracker để tách tất cả các thực thể Đã thêm, Đã sửa đổi và Đã xóa:

public void DetachAllEntities()
{
    var changedEntriesCopy = this.ChangeTracker.Entries()
        .Where(e => e.State == EntityState.Added ||
                    e.State == EntityState.Modified ||
                    e.State == EntityState.Deleted)
        .ToList();

    foreach (var entry in changedEntriesCopy)
        entry.State = EntityState.Detached;
}

5
Đảm bảo gọi 'ToList' sau 'Where'. Nếu không, nó ném ra một System.InvalidOperationException: 'Bộ sưu tập đã được sửa đổi; hoạt động liệt kê có thể không thực hiện. '
mabead

6
Trong bài kiểm tra đơn vị của tôi, trạng thái mục nhập là "Chưa sửa đổi", có thể xảy ra bởi vì tôi sử dụng một giao dịch mà tôi quay lại ở cuối phương pháp kiểm tra. Điều này có nghĩa là tôi phải đặt trạng thái mục nhập được theo dõi thành "Đã tách rời" mà không kiểm tra trạng thái hiện tại, để các bài kiểm tra của tôi chạy chính xác cùng một lúc. Tôi gọi mã trên ngay sau khi quay lại giao dịch, nhưng tôi đã hiểu, cuộn lại chắc chắn có nghĩa là trạng thái Chưa sửa đổi.
barbara.post

2
(và cũng var entityphải thực sự var entrynhư vậy vì nó là mục nhập không phải là Thực thể thực sự)
nestoda

2
@DavidSherret Nghĩ rằng đó có thể là trường hợp! Tôi hiểu được điều này bởi vì trong một ứng dụng thử nghiệm của tôi, chạy qua 1000 mục và đánh dấu là Đã tách rời mất khoảng 6000 mili giây với mã hiện có. Khoảng 15ms với tính năng mới :)
nestoda

3
Bạn cũng không nên sử dụng e.State == EntityState.Unchanged? Mặc dù thực thể không thay đổi, nó vẫn được theo dõi trong ngữ cảnh và là một phần của tập hợp các thực thể được xem xét trong DetectChanges. Ví dụ: bạn thêm thực thể mới (nó ở trạng thái Đã thêm), gọi SaveChanges và thực thể được thêm bây giờ có trạng thái Không thay đổi (nó đi ngược lại với mẫu UnitOfWork, nhưng op đã hỏi: Tôi đang lưu các thay đổi vào cuối mỗi lần lặp ).
jahav

25

1. Khả năng: tách mục nhập

dbContext.Entry(entity).State = EntityState.Detached;

Khi bạn tách mục nhập, trình theo dõi thay đổi sẽ ngừng theo dõi nó (và sẽ dẫn đến hiệu suất tốt hơn)

Xem: http://msdn.microsoft.com/de-de/library/system.data.entitystate(v=vs.110).aspx

2. Khả năng: làm việc với Statuslĩnh vực của riêng bạn + bối cảnh không kết nối

Có thể bạn muốn kiểm soát trạng thái của thực thể của mình một cách độc lập để bạn có thể sử dụng các biểu đồ bị ngắt kết nối. Thêm thuộc tính cho trạng thái thực thể và chuyển trạng thái này thành dbContext.Entry(entity).Statekhi thực hiện các hoạt động (sử dụng kho lưu trữ để thực hiện việc này)

public class Foo
{
    public EntityStatus EntityStatus { get; set; }
}

public enum EntityStatus
{
    Unmodified,
    Modified,
    Added
}

Xem liên kết sau để biết ví dụ: https://www.safaribooksonline.com/library/view/programming-entity-framework/9781449331825/ch04s06.html


Tôi nghĩ rằng việc thêm một phương thức mở rộng và chạy trên tất cả các thực thể trong ChangeTracker và tách chúng ra sẽ hoạt động.
hazimdikenli

15

Tôi đang chạy một dịch vụ windows cập nhật các giá trị mỗi phút và tôi đã gặp phải vấn đề tương tự. Tôi đã thử chạy giải pháp @DavidSherrets nhưng sau một vài giờ, điều này cũng chậm. Giải pháp của tôi chỉ đơn giản là tạo một bối cảnh mới như thế này cho mỗi lần chạy mới. Đơn giản nhưng nó hoạt động.

_dbContext = new DbContext();


5
Đây không phải là giải pháp 'Đơn giản nhưng hiệu quả' cho mục tiêu của bạn. Nó là một trong những chính xác duy nhất. Ngữ cảnh nên sống càng ít càng tốt, 1 ngữ cảnh trên 1 giao dịch là lựa chọn tốt nhất.
Yegor Androsov

2
Đồng ý với @pwrigshihanomoronimo, bối cảnh tuân theo mẫu thiết kế UnitOfWork. Theo định nghĩa của Martin Fowler:> Duy trì danh sách các đối tượng bị ảnh hưởng bởi giao dịch kinh doanh và> điều phối việc ghi ra các thay đổi và giải quyết các vấn đề đồng thời>.
Michel

Điều này dường như làm một mẹo cho tôi. Tôi đang đồng bộ hóa dữ liệu, có khoảng nửa triệu giao dịch (chèn và cập nhật) trong các bảng có vài triệu hàng. Vì vậy, tôi đã phải vật lộn với OutOfMemoryException sau một thời gian (hoặc một số thao tác). Điều này đã giải quyết khi tôi tạo một DbContext mới cho mỗi X số vòng lặp, đây là một nơi tự nhiên để thể hiện lại ngữ cảnh. Điều đó có lẽ đã kích hoạt GC theo cách tốt hơn, không phải nghĩ đến khả năng rò rỉ bộ nhớ trong EF và các hoạt động chạy lâu. Cảm ơn bạn!
Mats Magnem

4

Tôi vừa gặp phải vấn đề này và cuối cùng tình cờ tìm ra giải pháp tốt hơn cho những người sử dụng phương pháp tiêm phụ thuộc .NET Core điển hình. Bạn có thể sử dụng DbContext theo phạm vi cho mỗi thao tác. Điều đó sẽ đặt lại DbContext.ChangeTrackerđể SaveChangesAsync()không bị sa lầy vào việc kiểm tra các thực thể từ các lần lặp trước. Đây là một ví dụ về phương pháp ASP.NET Core Controller:

    /// <summary>
    /// An endpoint that processes a batch of records.
    /// </summary>
    /// <param name="provider">The service provider to create scoped DbContexts.
    /// This is injected by DI per the FromServices attribute.</param>
    /// <param name="records">The batch of records.</param>
    public async Task<IActionResult> PostRecords(
        [FromServices] IServiceProvider provider,
        Record[] records)
    {
        // The service scope factory is used to create a scope per iteration
        var serviceScopeFactory =
            provider.GetRequiredService<IServiceScopeFactory>();

        foreach (var record in records)
        {
            // At the end of the using block, scope.Dispose() will be called,
            // release the DbContext so it can be disposed/reset
            using (var scope = serviceScopeFactory.CreateScope())
            {
                var context = scope.ServiceProvider.GetService<MainDbContext>();

                // Query and modify database records as needed

                await context.SaveChangesAsync();
            }
        }

        return Ok();
    }

Do các dự án ASP.NET Core thường sử dụng DbContextPool, điều này thậm chí không tạo / hủy các đối tượng DbContext. (Trong trường hợp bạn quan tâm, DbContextPool thực sự gọi DbContext.ResetState()DbContext.Resurrect(), nhưng tôi không khuyên bạn nên gọi chúng trực tiếp từ mã của bạn, vì chúng có thể sẽ thay đổi trong các bản phát hành trong tương lai.) Https://github.com/aspnet/EntityFrameworkCore/blob/v2 .2.1 / src / EFCore / Internal / DbContextPool.cs # L157


0

Từ EF Core 3.0 có một API nội bộ có thể đặt lại ChangeTracker. Không sử dụng điều này trong mã sản xuất, tôi đề cập đến nó vì nó có thể giúp ai đó trong việc thử nghiệm tùy thuộc vào tình huống.

using Microsoft.EntityFrameworkCore.Internal;

_context.GetDependencies().StateManager.ResetState();

Như nhận xét trên mã cho biết;

Đây là một API nội bộ hỗ trợ cơ sở hạ tầng Entity Framework Core và không tuân theo các tiêu chuẩn tương thích giống như các API công khai. Nó có thể được thay đổi hoặc loại bỏ mà không cần thông báo trong bất kỳ bản phát hành nào. Bạn chỉ nên sử dụng nó trực tiếp trong mã của mình một cách hết sức thận trọng và biết rằng làm như vậy có thể dẫn đến lỗi ứng dụng khi cập nhật lên bản phát hành Entity Framework Core mới.


-3

Ý kiến ​​của tôi là, theo kinh nghiệm của tôi, EF, hoặc bất kỳ orm nào, không hoạt động tốt dưới quá nhiều áp lực hoặc mô hình phức tạp.

Nếu bạn không muốn theo dõi, thực sự tôi sẽ nói tại sao thậm chí làm orm?

Nếu tốc độ là lực lượng chính, không có gì đánh bại các thủ tục được lưu trữ và lập chỉ mục tốt.

Và xa hơn nữa, nếu các truy vấn của bạn luôn là mỗi id, hãy cân nhắc sử dụng nosql hoặc có thể là sql chỉ với key và json. Điều này sẽ tránh được vấn đề trở kháng giữa các lớp và bảng.

Đối với trường hợp của bạn, tải mọi thứ trong các đối tượng theo cách đó có vẻ rất chậm đối với tôi. Thực sự trong trường hợp của bạn, các thủ tục được lưu trữ tốt hơn vì bạn tránh được việc truyền dữ liệu qua mạng và sql là cách nhanh hơn và được tối ưu hóa để quản lý tổng hợp và những thứ tương tự.

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.