2017-07-03 17 views
5

Tôi đã có tình huống sau đây:Lấy số lượng lớn các bản ghi dưới nhiều hạn chế, mà không gây ra một ngoại lệ out-of-bộ nhớ

  1. Có hai loại liên quan. Đối với câu hỏi này, tôi sẽ sử dụng các loại đơn giản sau đây:

    public class Person 
    { 
        public Guid Id {get; set;} 
        public int status {get; set;} 
    } 
    
    public class Account 
    { 
        public Guid AccountId {get; set;} 
        public decimal Amount { get; set; } 
        public Guid PersonId { get; set; } 
    } 
    

    Vì vậy mà một Person có thể có nhiều Account s (ví dụ, nhiều Account s sẽ tham khảo cùng PersonId).

  2. Trong cơ sở dữ liệu của chúng tôi, có hàng chục nghìn người và mỗi trung bình có 5-10 tài khoản.

  3. Tôi cần truy xuất tài khoản của từng người, giả sử họ thực hiện các yêu cầu nhất định. Sau đó, tôi cần phải xem liệu tất cả các tài khoản của người này, cùng nhau, có hoàn thành một điều kiện khác không.

    Trong ví dụ này, chúng ta hãy nói rằng tôi cần mỗi tài khoản với amount < 100, và rằng sau khi lấy tài khoản của một người, tôi cần phải kiểm tra xem số tiền của họ lớn hơn 1000.

  4. Sử dụng một truy vấn LINQ là mong muốn, nhưng không thể thực hiện bằng cách sử dụng các từ khóa group-by-into, bởi vì Nhà cung cấp LINQ (LINQ-to-CRM) không hỗ trợ nó.

  5. Bên cạnh đó, thực hiện các truy vấn LINQ đơn giản sau đây để thực hiện niêm yết 3 yêu cầu này cũng là không thể (vui lòng đọc comment inlined):

    var query = from p in personList 
          join a in accountList on p.Id equals a.PersonId 
          where a.Amount < 100 
          select a; 
    var groups = query.GroupBy(a => a.PersonId); 
    // and now, run in bulks on x groups 
    // (let x be the groups amount that won't cause an out-of-memory exception) 
    

    Nó không phải là có thể cho 2 lý do:

    a. Nhà cung cấp LINQ buộc phải gọi tới ToList() trước khi sử dụng GroupBy().

    b. Cố gắng thực sự gọi ToList() trước khi sử dụng GroupBy() dẫn đến một ngoại lệ ngoài bộ nhớ - vì có hàng chục nghìn tài khoản.

  6. Vì lý do hiệu quả, tôi không muốn làm những điều sau đây, vì nó có nghĩa là hàng chục ngàn khả năng tìm lại:

    a. Truy xuất tất cả mọi người.

    b. Lặp lại chúng và truy xuất tài khoản của mỗi người trên mỗi lần lặp.

Sẽ rất vui vì những ý tưởng hiệu quả.

+1

Cũng như một FYI: điểm 4 và 5.A là như nhau, và 5.B là loại ngụ ý. Bạn có lẽ cũng sẽ nhận được một OOM với một GroupBy anyway –

Trả lời

5

tôi sẽ đề nghị ra lệnh truy vấn bằng cách PersonId, chuyển sang LINQ to Objects qua AsEnumerable() (do đó thực hiện nó, nhưng mà không thể hóa toàn bộ tập hợp kết quả trong bộ nhớ như ToList() cuộc gọi), và sau đó sử dụng phương pháp GroupAdjacent từ MoreLINQ gói:

Phương pháp này được triển khai bằng cách sử dụng thực thi hoãn lại và truyền các nhóm. Tuy nhiên, các phần tử nhóm được đệm. Do đó, mỗi nhóm được tạo ra ngay khi nó được hoàn thành và trước khi nhóm tiếp theo xảy ra.

var query = from p in personList 
      join a in accountList on p.Id equals a.PersonId 
      where a.Amount < 100 
      orderby a.PersonId 
      select a; 
var groups = query.AsEnumerable() 
    .GroupAdjacent(a => a.PersonId) 
    .Where(g => g.Sum(a => a.Amount) > 1000); 

Bí quyết AsEnumerable() hoạt động tốt với nhà cung cấp truy vấn EF cho chắc chắn. Cho dù nó hoạt động với nhà cung cấp LINQ to CRM thực sự phụ thuộc vào cách nhà cung cấp thực hiện phương thức GetEnumerator() - nếu nó cố gắng đệm toàn bộ kết quả truy vấn, thì bạn sẽ không gặp may.

+0

Làm thế nào mà AsEnumerable thực hiện truy vấn nhưng không hiện thực hóa các đối tượng trong bộ nhớ? Điều đó có hiệu quả với các nhà cung cấp LINQ khác như EF không? –

+0

@CamiloTerevinto Tuyệt đối - đó là cách ưu tiên để chuyển từ LINQ sang Thực thể thành LINQ thành Đối tượng. Bằng cách thực hiện ý tôi, nó sẽ được thực thi khi truy vấn L2O cuối cùng được thực hiện, nhưng sẽ được xử lý tuần tự trong bộ nhớ (từng cái một) như thường lệ 'IEnumerable '. Sự khác biệt điển hình giữa phương thức trả về 'IEnumerable ' (streaming) và 'List ' (đệm). –

+0

Thú vị, tôi ngạc nhiên vì tôi chưa bao giờ đọc về điều đó trong quá nhiều thời gian sử dụng EF và LINQ. Tôi sẽ phải cung cấp cho một SQL Server Profiler thử :) cảm ơn! –

1

tôi sẽ ra lệnh bằng GUID và sau đó quá trình trong các khối:

var basep = (from p in personList select p.Id).OrderBy(id => id); 

int basepCount = personList.Count(); 
int blocksize = 1000; 
int numblocks = (basepCount/blocksize) + (basepCount % blocksize == 0 ? 0 : 1); 
for (var block = 0; block < numblocks; ++block) { 
    var firstPersonId = basep.Skip(block * blocksize).First(); 
    var lastPersonId = basep.Skip(Math.Min(basepCount-1, block*blocksize+blocksize-1)).First(); 

    var query = from p in personList.Where(ps => firstPersonId.CompareTo(ps.Id) <= 0 && ps.Id.CompareTo(lastPersonId) <= 0) 
       join a in accountList on p.Id equals a.PersonId 
       where a.Amount < 100 
       select a; 
    var groups = query.GroupBy(a => a.PersonId); 
    // work on groups 
} 
Các vấn đề liên quan