7

Hãy xem xét các đối tượng những thực thể giả tạo:Loại bỏ chọn N + 1 mà không .Include

public class Consumer 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 
    public bool NeedsProcessed { get; set; } 
    public virtual IList<Purchase> Purchases { get; set; } //virtual so EF can lazy-load 
} 

public class Purchase 
{ 
    public int Id { get; set; } 
    public decimal TotalCost { get; set; } 
    public int ConsumerId { get; set; } 
} 

Bây giờ chúng ta hãy nói rằng tôi muốn chạy mã này:

var consumers = Consumers.Where(consumer => consumer.NeedsProcessed); 

//assume that ProcessConsumers accesses the Consumer.Purchases property 
SomeExternalServiceICannotModify.ProcessConsumers(consumers); 

Theo mặc định này sẽ bị ảnh hưởng từ Chọn N + 1 bên trong phương thức ProcessConsumers. Nó sẽ kích hoạt một truy vấn khi nó liệt kê người tiêu dùng, sau đó nó sẽ lấy từng mua bộ sưu tập 1 bằng 1. Các giải pháp tiêu chuẩn cho vấn đề này sẽ có thêm một bao gồm:

var consumers = Consumers.Include("Purchases").Where(consumer => consumer.NeedsProcessed); 

//assume that ProcessConsumers accesses the Consumer.Purchases property 
SomeExternalServiceICannotModify.ProcessConsumers(consumers); 

Đó hoạt động tốt trong nhiều trường hợp, nhưng trong một số trường hợp phức tạp, bao gồm hoàn toàn có thể phá hủy hiệu suất theo đơn đặt hàng của cường độ. Là nó có thể làm điều gì đó như thế này:.

  1. Lấy người tiêu dùng, người tiêu dùng var của tôi = _entityContext.Consumers.Where (...) ToList()
  2. Lấy mua hàng của tôi, var mua = _entityContext.Purchases. Ở đâu (...). ToList()
  3. Hydrate người tiêu dùng. Mua bộ sưu tập theo cách thủ công từ các giao dịch mua tôi đã nạp vào bộ nhớ. Sau đó, khi tôi chuyển nó cho ProcessConsumers, nó sẽ không kích hoạt thêm các truy vấn db.

Tôi không chắc chắn cách thực hiện # 3. Nếu bạn cố gắng truy cập bất kỳ bộ sưu tập người tiêu dùng nào. Mua hàng sẽ kích hoạt tải chậm (và do đó, hãy chọn N + 1). Có lẽ tôi cần phải đưa Người tiêu dùng vào loại thích hợp (thay vì loại proxy của EF) và sau đó tải bộ sưu tập? Một cái gì đó như thế này:

foreach (var consumer in Consumers) 
{ 
    //since the EF proxy overrides the Purchases property, this doesn't really work, I'm trying to figure out what would 
    ((Consumer)consumer).Purchases = purchases.Where(x => x.ConsumerId = consumer.ConsumerId).ToList(); 
} 

EDIT: Tôi đã viết lại ví dụ một chút để hy vọng tiết lộ vấn đề này rõ ràng hơn.

+1

IIRC EF sẽ tự động hydrate các bộ sưu tập, vì vậy # 3 không phải được thực hiện thủ công. – jeroenh

+1

Truy vấn đầu tiên của bạn nên thực hiện như một câu lệnh SQL đơn. Bạn có thấy nhiều cuộc gọi db không? –

+0

@Nicholas, bạn nói đúng, tôi đã cập nhật ví dụ để làm cho nó Chọn N + 1. Đây là một ví dụ rất đơn giản, đọc toàn bộ câu hỏi và cố gắng hiểu những gì tôi thực sự yêu cầu. Ví dụ thực tế ở đâu. Bao gồm không đủ phức tạp hơn đáng kể và không hợp lý để đặt bên trong câu hỏi SO. – manu08

Trả lời

0

EF sẽ cư consumer.Purchases bộ sưu tập cho bạn, nếu bạn sử dụng bối cảnh tương tự để lấy cả bộ sưu tập:

List<Consumer> consumers = null; 
using (var ctx = new XXXEntities()) 
{ 
    consumers = ctx.Consumers.Where(...).ToList(); 

    // EF will populate consumers.Purchases when it loads these objects 
    ctx.Purchases.Where(...).ToList(); 
} 

// the Purchase objects are now in the consumer.Purchases collections 
var sum = consumers.Sum(c => c.Purchases.Sum(p => p.TotalCost)); 

EDIT:

Kết quả chỉ trong 2 cuộc gọi db: 1 để có được bộ sưu tập của Consumers và 1 để nhận bộ sưu tập Purchases.

EF sẽ xem từng bản ghi Purchase được trả lại và tra cứu bản ghi Consumer tương ứng từ Purchase.ConsumerId. Sau đó, nó sẽ thêm đối tượng Purchase vào bộ sưu tập Consumer.Purchases cho bạn.


Phương án 2:

Nếu có một số lý do bạn muốn lấy hai danh sách từ ngữ cảnh khác nhau và sau đó liên kết chúng, tôi sẽ thêm tài sản khác đến lớp Consumer:

partial class Consumer 
{ 
    public List<Purchase> UI_Purchases { get; set; } 
} 

Sau đó, bạn có thể đặt thuộc tính này từ bộ sưu tập Purchases và sử dụng nó trong giao diện người dùng của mình.

+0

Tùy chọn 1 là sự phục hồi những gì tôi có ở trên, phải không? Nó vẫn sẽ chọn n + 1. Tùy chọn 2 là hợp lý, nhưng nó sẽ không thực sự làm việc cho trường hợp của tôi. Tôi sẽ cập nhật câu hỏi ban đầu của mình để xây dựng. – manu08

+0

Không, kết quả chỉ bằng 2 cuộc gọi db. Tôi đã thêm giải thích thêm cho câu trả lời của tôi. –

0

Lấy tiêu dùng của tôi

var consumers = _entityContext.Consumers 
           .Where(consumer => consumer.Id > 1000) 
           .ToList(); 

Lấy mua hàng của tôi

var purchases = consumers.Select(x => new { 
             Id = x.Id, 
             IList<Purchases> Purchases = x.Purchases   
             }) 
         .ToList() 
         .GroupBy(x => x.Id) 
         .Select(x => x.Aggregate((merged, next) => merged.Merge(next))) 
         .ToList(); 

Hydrat người tiêu dùng. Mua bộ sưu tập theo cách thủ công từ các giao dịch mua tôi đã được nạp vào bộ nhớ.

for(int i = 0; i < costumers.Lenght; i++) 
    costumers[i].Purchases = purchases[i]; 
+0

Tôi tin rằng nửa bên trái của, người tiêu dùng [i]. Mua hàng = mua hàng [i], sẽ kích hoạt EF để cố gắng tải Mua hàng cho bạn. Vì vậy, bạn sẽ kích hoạt tải lười biếng sau đó ghi đè lên nó. – manu08

+0

Bạn có thể tách biểu đồ đối tượng ra khỏi ngữ cảnh hoặc tắt khả năng tải chậm, trước khi thực hiện bước 3. –

+0

@MortenMertner Trong trường hợp của tôi, tôi không thể vô hiệu hóa việc tải chậm vì tôi cần những thứ khác để tải chậm sau này. Tôi sẽ chơi xung quanh với việc tách đối tượng, nạp theo cách thủ công, sau đó gắn lại đối tượng. Cảm ơn ý tưởng. – manu08

0

có nó không thể cho bạn để làm việc xung quanh nhiều roundtrips-hay-không hiệu quả truy vấn hệ vấn đề bằng cách thực hiện công việc trên cơ sở dữ liệu - về cơ bản bằng cách quay một chiếu thay vì một đặc biệt thực thể, như được trình bày bên dưới:

var query = from c in db.Consumers 
      where c.Id > 1000 
      select new { Consumer = c, Total = c.Purchases.Sum(p => p.TotalCost) }; 
var total = query.Sum(cp => cp.Total); 

Tôi không phải là chuyên gia về EF bằng bất kỳ phương tiện nào, vì vậy hãy tha thứ cho tôi nếu kỹ thuật này không phù hợp.

+0

Kỹ thuật đó là tốt cho ví dụ giả tạo, nhưng tôi yêu cầu bạn giả sử nó phức tạp hơn nhiều (ví dụ: bạn cần phải làm nhiều thứ khác với Người tiêu dùng và Giao dịch mua của họ, vì vậy bạn muốn toàn bộ đồ thị đối tượng trong bộ nhớ). – manu08

+0

Giả sử rằng ví dụ của tôi không kém phần giả tạo, trong đó tôi chỉ trả về Tổng số từ các giao dịch mua. Bạn có thể dễ dàng trả lại bộ sưu tập các đối tượng liên quan được chọn thủ công cho những thứ bạn cần làm. Chọn dữ liệu vào lớp chiếu tùy chỉnh (ví dụ: ConsumerPurchasesForYearlyReportData hoặc một số loại như vậy) và sử dụng làm hạt giống cho công việc sau đây. ORM nói chung (và EF nói riêng) không phải là tuyệt vời khi trả lại các kết hợp được lọc. –

1

Nếu tôi hiểu chính xác, bạn muốn tải cả hai tập hợp con đã lọc của Người tiêu dùng mỗi tập hợp con đã lọc của các Giao dịch mua của họ trong 1 truy vấn. Nếu điều đó không chính xác, hãy tha thứ cho sự hiểu biết của tôi về ý định của bạn. Nếu điều đó là đúng, bạn có thể làm một cái gì đó như:

var consumersAndPurchases = db.Consumers.Where(...) 
    .Select(c => new { 
     Consumer = c, 
     RelevantPurchases = c.Purchases.Where(...) 
    }) 
    .AsNoTracking() 
    .ToList(); // loads in 1 query 

// this should be OK because we did AsNoTracking() 
consumersAndPurchases.ForEach(t => t.Consumer.Purchases = t.RelevantPurchases); 

CannotModify.Process(consumersAndPurchases.Select(t => t.Consumer)); 

Lưu ý rằng điều này sẽ không hoạt động nếu chức năng Process dự kiến ​​để sửa đổi các đối tượng tiêu dùng và sau đó cam kết những thay đổi về cơ sở dữ liệu.

+0

Điều này sẽ ngăn chặn theo dõi thay đổi, nhưng tôi tin t.Consumer.Purchases sẽ vẫn kích hoạt tải chậm, trước khi lưu t.RelevantPurchases qua nó. – manu08

+0

Tôi ngạc nhiên rằng điều đó sẽ xảy ra. Tuy nhiên, nếu điều đó xảy ra, bạn có thể thêm (sau cuộc gọi ToList()): .Select (t => new Consumer {/ * sao chép tất cả các trường có liên quan từ t.Consumer * /, Purchases = t.RelevantPurchases}).(). Điều đó có hiệu quả sẽ ánh xạ mọi thứ vào các đối tượng không phải là EF-proxy mà không nên tải xuống bất kỳ thứ gì. Một tùy chọn khác là làm cho thuộc tính Mua không phải là ảo, mặc dù điều này cũng sẽ ngăn không cho tải xuống ở nơi khác. – ChaseMedallion

Các vấn đề liên quan