Entity Framework Core truyền dữ liệu blob lớn mà không bị tràn bộ nhớ, thực hành tốt nhất


8

Tôi đang viết mã đi qua một lượng lớn dữ liệu hình ảnh, chuẩn bị một khối delta lớn chứa tất cả được nén để gửi.

Đây là một ví dụ về cách dữ liệu này có thể

[MessagePackObject]
public class Blob : VersionEntity
{
    [Key(2)]
    public Guid Id { get; set; }
    [Key(3)]
    public DateTime CreatedAt { get; set; }
    [Key(4)]
    public string Mediatype { get; set; }
    [Key(5)]
    public string Filename { get; set; }
    [Key(6)]
    public string Comment { get; set; }
    [Key(7)]
    public byte[] Data { get; set; }
    [Key(8)]
    public bool IsTemporarySmall { get; set; }
}

public class BlobDbContext : DbContext
{
    public DbSet<Blob> Blob { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blob>().HasKey(o => o.Id);
    }
}

Khi làm việc với điều này, tôi xử lý mọi thứ thành một đoạn phim và tôi muốn lưu giữ ít nhất có thể trong bộ nhớ tại bất kỳ thời điểm nào.

Có đủ để làm điều đó như thế này?

foreach(var b in context.Where(o => somefilters).AsNoTracking())
    MessagePackSerializer.Serialize(stream, b);

Điều này sẽ vẫn lấp đầy bộ nhớ với tất cả các bản ghi blob, hoặc chúng sẽ được xử lý từng cái một khi tôi lặp lại trên bảng liệt kê. Nó không sử dụng bất kỳ ToList nào, chỉ có điều tra viên, vì vậy Entity Framework sẽ có thể xử lý nó khi đang di chuyển, nhưng tôi không chắc đó có phải là điều không.

Bất kỳ chuyên gia Khung thực thể nào ở đây, những người có thể đưa ra một số hướng dẫn về cách xử lý việc này đúng cách.


Tôi không chắc chắn 100% nhưng tôi nghĩ rằng điều này sẽ dẫn đến một truy vấn duy nhất được gửi đến cơ sở dữ liệu, tuy nhiên nó xử lý nó trên c # side 1 by 1. (bạn có thể kiểm tra điều này với sql profiler) bạn có thể thay đổi vòng lặp của mình và sử dụng bỏ qua và thực hiện để đảm bảo bạn nhận được một mục duy nhất tuy nhiên đây không phải là mục đích của ef vì vậy tôi không chắc liệu bạn có tìm được cách thực hành tốt nhất không.
Joost K

Nếu tôi hiểu chính xác, SqlDataReader sẽ tạo kết nối đến cơ sở dữ liệu và tìm nạp các phần trong khi bạn đang lặp lại Read (). Nếu điều tra viên làm việc theo cách tương tự ở đây, nó sẽ ổn thôi. Nhưng nếu nó đệm tất cả, và sau đó lặp đi lặp lại, chúng ta có một vấn đề. Bất cứ ai ở đây có thể xác nhận làm thế nào điều này hoạt động? Tôi muốn nó thực thi một truy vấn duy nhất, nhưng có kết nối luồng đến cơ sở dữ liệu và hoạt động khi bạn đi với dữ liệu, xử lý và giải phóng một thực thể tại một thời điểm.
Atle S

Tại sao bạn không nhớ hồ sơ mã của bạn? Chúng tôi không thể làm điều đó cho bạn. Ngoài ra, câu hỏi rất rộng / không rõ ràng (và sẽ được giữ như vậy nếu nó không dành cho tiền thưởng) vì các thành phần không xác định và mã xung quanh. (Giống như, từ đâu streamđến?). Cuối cùng, việc xử lý dữ liệu filestream của SQL Server nhanh và phát trực tuyến đòi hỏi một cách tiếp cận khác ngoài phạm vi EF.
Gert Arnold

Câu trả lời:


1

Nói chung khi bạn tạo bộ lọc LINQ trên Thực thể, nó giống như viết một câu lệnh SQL ở dạng mã. Nó trả về một IQueryablecái mà chưa thực sự chống lại cơ sở dữ liệu. Khi bạn lặp lại IQueryablebằng một foreachhoặc gọi ToList()thì sql được thực thi và tất cả các kết quả được trả về và được lưu trong bộ nhớ.

https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/ef/lingu-reference/query-execut

Mặc dù EF có thể không phải là lựa chọn tốt nhất cho hiệu năng thuần túy, có một cách tương đối đơn giản để xử lý việc này mà không phải lo lắng quá nhiều về việc sử dụng bộ nhớ:

Hãy xem xét những điều sau đây

var filteredIds = BlobDbContext.Blobs
                      .Where(b => b.SomeProperty == "SomeValue")
                      .Select(x => x.Id)
                      .ToList();

Bây giờ bạn đã lọc các Blobs theo yêu cầu của bạn và thực hiện điều này đối với cơ sở dữ liệu, nhưng chỉ trả về các giá trị Id trong bộ nhớ.

Sau đó

foreach (var id in filteredIds)
{
    var blob = BlobDbContext.Blobs.AsNoTracking().Single(x => x.Id == id);
    // Do your work here against a single in-memory blob
}

Các blob lớn nên có sẵn để thu gom rác sau khi bạn hoàn thành nó và bạn không nên hết bộ nhớ.

Rõ ràng bạn có thể kiểm tra ý nghĩa số lượng bản ghi trong danh sách id hoặc bạn có thể thêm siêu dữ liệu vào truy vấn đầu tiên để giúp bạn quyết định cách xử lý nếu bạn muốn tinh chỉnh ý tưởng.


1
Điều này không trả lời câu hỏi của tôi. Tôi muốn biết nếu EF xử lý việc tìm nạp từ truy vấn trong một vấn đề liên tiếp khi đi ngang qua trình liệt kê, cách mà SqlDataReader thực hiện với Next. Điều đó là có thể, và đó cũng là cách ưa thích thay vì tìm nạp từng cái một. Câu trả lời gần nhất tôi từng trả lời ở đây là những gì Smit Patel nói trong câu trả lời ở đây: github.com/aspnet/EntityFrameworkCore/issues/14640 Ông nói "Điều đó có nghĩa là gì, chúng tôi sẽ không cần đệm trong nội bộ. trong trường hợp, truy vấn không theo dõi sẽ không nhận / lưu trữ nhiều dữ liệu hơn hàng kết quả hiện tại là gì. ".
Atle S

Nếu bạn có thể xác nhận 100% rằng EF tìm nạp mọi thứ trước khi liệt kê, đó sẽ là một phần của câu trả lời, nếu bạn cũng cung cấp cách sử dụng SqlDataReader để thực hiện theo cách phù hợp. Hoặc nếu EF thực sự làm điều này đúng, một xác nhận về điều đó sẽ là một câu trả lời. Dù sao, việc này bắt đầu mất nhiều thời gian hơn tôi sẽ phải gỡ lỗi cho EF để xác nhận;)
Atle S

Xin lỗi - tôi đã đào một ít nhưng không đi đến tận cùng của nó. Tôi sẽ đề nghị rằng nếu bạn lo lắng về hiệu suất thuần túy, thì EF không phải là hướng đi, nếu bạn muốn giữ mô hình của EF, thì câu trả lời của tôi đảm bảo rằng bạn sẽ không hết bộ nhớ. Giả sử rằng Idcó một chỉ mục được nhóm, thì hiệu năng của rất nhiều truy vấn tuần tự có thể không tệ như bạn nghĩ.
ste-fu
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.