2009-08-20 34 views
8

Giả sử tôi có biểu thức IQueryable<T> mà tôi muốn đóng gói định nghĩa, lưu trữ và sử dụng lại hoặc nhúng nó vào truy vấn lớn hơn sau đó. Ví dụ:Làm cách nào để duy trì hoạt động trì hoãn LINQ?

IQueryable<Foo> myQuery = 
    from foo in blah.Foos 
    where foo.Bar == bar 
    select foo; 

Bây giờ tôi tin rằng tôi chỉ có thể giữ đối tượng myQuery đó xung quanh và sử dụng nó như tôi đã mô tả. Nhưng một số điều tôi không chắc chắn về:

  1. Tốt nhất để tham số hóa nó? Ban đầu tôi đã xác định điều này trong một phương thức và sau đó trả lại IQueryable<T> là kết quả của phương thức. Bằng cách này tôi có thể xác định blahbar làm đối số phương pháp và tôi đoán nó chỉ tạo ra một IQueryable<T> mới mỗi lần. Đây có phải là cách tốt nhất để đóng gói logic của một IQueryable<T>? Có cách nào khác không?

  2. Điều gì sẽ xảy ra nếu truy vấn của tôi phân giải thành vô hướng thay vì IQueryable? Ví dụ: nếu tôi muốn truy vấn này chính xác như được hiển thị nhưng thêm .Any() để cho tôi biết nếu có bất kỳ kết quả nào khớp với? Nếu tôi thêm (...).Any() thì kết quả là bool và được thực hiện ngay lập tức, đúng không? Có cách nào để sử dụng các toán tử Queryable này (Any, SindleOrDefault, v.v.) mà không thực thi ngay lập tức không? LINQ-to-SQL xử lý điều này như thế nào?

Edit: Phần 2 thực sự thêm về cố gắng tìm hiểu sự khác biệt giới hạn giữa IQueryable<T>.Where(Expression<Func<T, bool>>) vs IQueryable<T>.Any(Expression<Func<T, bool>>) là gì. Có vẻ như sau này không linh hoạt khi tạo các truy vấn lớn hơn, nơi thực thi sẽ bị trì hoãn. Các Where() có thể được nối thêm và sau đó các cấu trúc khác có thể được thêm vào sau đó và cuối cùng được thực thi. Kể từ khi Any() trả về giá trị vô hướng, có vẻ như nó sẽ thực thi ngay lập tức trước khi phần còn lại của truy vấn có thể được xây dựng.

Trả lời

5
  1. Bạn phải thực sự cẩn thận về việc chuyển xung quanh IQueryables khi bạn đang sử dụng một DataContext, bởi vì một khi bối cảnh được của xử lý, bạn sẽ không thể thực thi trên đó IQueryable nữa. Nếu bạn không sử dụng ngữ cảnh thì bạn có thể ổn, nhưng hãy nhận biết điều đó.

  2. .Any() và .FirstOrDefault() là không phải là trì hoãn. Khi bạn gọi cho họ, họ sẽ gây ra việc thực hiện xảy ra. Tuy nhiên, điều này có thể không làm những gì bạn nghĩ rằng nó. Ví dụ, trong LINQ to SQL nếu bạn thực hiện một .Any() trên một IQueryable nó hoạt động như một IF EXIST (SQL HERE) về cơ bản.

Bạn có thể của chuỗi IQueryable cùng như thế này nếu bạn muốn:

var firstQuery = from f in context.Foos 
        where f.Bar == bar 
        select f; 

var secondQuery = from f in firstQuery 
        where f.Bar == anotherBar 
        orderby f.SomeDate 
        select f; 

if (secondQuery.Any()) //immediately executes IF EXISTS(second query in SQL) 
{ 
    //causes execution on second query 
    //and allows you to enumerate through the results 
    foreach (var foo in secondQuery) 
    { 
     //do something 
    } 

    //or 

    //immediately executes second query in SQL with a TOP 1 
    //or something like that 
    var foo = secondQuery.FirstOrDefault(); 
} 
+0

Nghe giống như # 1, có phương pháp về cơ bản xây dựng một 'IQueryable' mới mỗi lần là một điều tốt * vì cách này tôi sẽ không gặp vấn đề với việc xử lý. Trong # 2, tôi đã nhầm lẫn về cách LINQ-to-SQL có thể dịch toán tử 'Any', nhưng tôi không thể trì hoãn. Nếu tôi sử dụng toán tử 'Any' trong một truy vấn lớn hơn thì nó sẽ được thực thi ngay lập tức ở đó, hay nó là một phần của việc thực hiện truy vấn lớn hơn? – mckamey

+0

OK Tôi nghĩ tôi gần như ở đó. Nếu tôi đã nhúng một '.Any()' vào một mệnh đề 'where' thì nó sẽ không thực hiện điều đó trong một vòng lặp, đúng không? Nó sẽ biên dịch thành biểu thức SQL thích hợp và gửi nó xuống. Vì vậy, trong thực tế, nó không phải là '.Any()' ngăn cản việc thực hiện trì hoãn vì nó được sử dụng như thế nào. Về cơ bản nếu kết quả của một truy vấn * toàn bộ là một vô hướng thì các số liệu của trình biên dịch bạn cần kết quả bây giờ thay vì tiếp tục xây dựng một 'IQueryable '. – mckamey

+1

@McKAMEY đúng, ngay khi bạn sử dụng. Bất kỳ() trong một ngữ cảnh không bị trì hoãn thì nó sẽ thực thi. Trong trường hợp của .Where() nó đang tìm kiếm một biểu thức, đó là trì hoãn, vì vậy bạn đang ok. Trong trường hợp của var hoặc vòng lặp foreach, những nguyên nhân gây ra bởi vì họ không phải là deferrable. – Joseph

2

Một lựa chọn tốt hơn nhiều so với bộ nhớ đệm đối tượng IQueryable là bộ nhớ cache cây Expression. Tất cả các đối tượng IQueryable có một thuộc tính được gọi là Expression (tôi tin), đại diện cho cây biểu hiện hiện tại cho truy vấn đó.

Tại thời điểm sau, bạn có thể tạo lại truy vấn bằng cách gọi truy vấn.Provider.CreateQuery (biểu thức) hoặc trực tiếp trên bất kỳ nhà cung cấp nào (trong trường hợp của bạn là Ngữ cảnh dữ liệu Linq2Sql).

Tham số hóa các cây biểu thức này hơi khó hơn một chút, vì chúng sử dụng ConstantExpressions để tạo giá trị. Để tham số hóa các truy vấn này, bạn S have phải xây dựng lại truy vấn mỗi khi bạn muốn các tham số khác nhau.

+1

Tôi sẽ nói rằng tham số hóa (hoặc quan trọng hơn là đóng gói một đơn vị logic) là mục tiêu thực sự ở đây chứ không phải là bộ nhớ đệm. Xem xét rằng trình biên dịch C# đã chuyển đổi nó, tôi không nghĩ rằng một tương đương thời gian chạy sẽ được sử dụng nhiều/lợi ích hiệu suất (như là những gì ngụ ý bộ nhớ đệm). – mckamey

+0

Bạn có thể giải thích tại sao tùy chọn này tốt hơn không? Theo như tôi hiểu bạn chỉ cần unwrap 'Expression' để rewrap nó sau này: tại sao không giữ wrapper như là? – PPC

1

Any() sử dụng cách này được hoãn lại.

var q = dc.Customers.Where(c => c.Orders.Any()); 

Any() sử dụng cách này không được hoãn lại, nhưng vẫn được dịch sang SQL (toàn bộ bảng khách hàng không được tải vào bộ nhớ).

bool result = dc.Customers.Any(); 

Nếu bạn muốn có một trì hoãn Bất kỳ(), làm theo cách này:

public static class QueryableExtensions 
{ 
    public static Func<bool> DeferredAny<T>(this IQueryable<T> source) 
    { 
    return() => source.Any(); 
    } 
} 

nào được gọi là như thế này:

Func<bool> f = dc.Customers.DeferredAny(); 
bool result = f(); 

Nhược điểm là kỹ thuật này sẽ không cho phép truy vấn phụ.

+0

Khi bạn nói hoãn lại, nó có vẻ như bạn có nghĩa là tôi có thể xác định một biểu thức lambda (hoặc đại biểu) nhưng không ngay lập tức thực hiện nó. Tôi nghĩ rằng có một sự tinh tế đối với khái niệm "thực thi trì hoãn" của LINQ, cho phép hoạt động trở thành một phần của cây biểu hiện lớn hơn, có thể được nhà cung cấp giải thích, tuy nhiên nó muốn. Câu hỏi của tôi là cố gắng để có được sự khác biệt giới hạn giữa 'IQueryable . Where (Expression >)' vs.'IQueryable . Bất kỳ (Biểu thức >)' vì nó có vẻ như sau này không linh hoạt. – mckamey

+1

Tính không linh hoạt đó xuất phát từ sự khác biệt về các loại trả về. Loại có tên 'bool' có thể không có hành vi trì hoãn. –

+0

đề xuất này dường như không an toàn với chủ đề (xem xét DataContext) –

0

Tạo một ứng dụng phần truy vấn của bạn bên trong một biểu thức

Func[Bar,IQueryable[Blah],IQueryable[Foo]] queryMaker = 
(criteria, queryable) => from foo in queryable.Foos 
     where foo.Bar == criteria 
     select foo; 

và sau đó bạn có thể sử dụng nó bằng cách ...

IQueryable[Blah] blah = context.Blah; 
Bar someCriteria = new Bar(); 
IQueryable[Foo] someFoosQuery = queryMaker(blah, someCriteria); 

Truy vấn có thể được gói gọn trong một lớp học nếu bạn muốn làm cho nó di động hơn/tái sử dụng.

public class FooBarQuery 
{ 
    public Bar Criteria { get; set; } 

    public IQueryable[Foo] GetQuery(IQueryable[Blah] queryable) 
    { 
    return from foo in queryable.Foos 
     where foo.Bar == Criteria 
     select foo; 
    } 
} 
Các vấn đề liên quan