2011-11-28 10 views
10

Tôi sử dụng ((ObjectQuery)IQueryable).ToTraceString() để lấy và tinh chỉnh mã SQL sẽ được thực thi bởi LINQ.Cách lấy ToTraceString cho IQueryable.Count

Vấn đề của tôi là không giống như hầu hết các phương pháp IQueryable IQueryable.Count như đã định nghĩa như thế này:

public static int Count(this IQueryable source) { 
     return (int)source.Provider.Execute(
      Expression.Call(
       typeof(Queryable), "Count", 
       new Type[] { source.ElementType }, source.Expression)); 
    } 

thực hiện truy vấn mà không soạn thảo và gửi lại IQueryable. tôi muốn làm các việc lừa bởi một cái gì đó như thế này:

public static IQueryable CountCompile(this IQueryable source) { 
    return source.Provider.CreateQuery(
     Expression.Call(
      typeof(Queryable), "Count", 
      new Type[] { source.ElementType }, source.Expression)); 
} 

Nhưng sau đó CreateQuery mang lại cho tôi những ngoại lệ sau đây:

LINQ to Entities query expressions can only be constructed from instances that implement the IQueryable interface.

Trả lời

5

Đây là câu trả lời thực tế mà tôi đã đưa ra khi tôi cố gắng như vậy. Ngoại lệ cho biết "chỉ có thể được xây dựng từ các cá thể triển khai giao diện IQueryable", vì vậy câu trả lời có vẻ đơn giản: trả về một thứ có thể truy vấn. Điều đó có thể xảy ra khi trả lại .Count() không? Vâng!

public partial class YourObjectContext 
{ 
    private static MethodInfo GetMethodInfo(Expression<Action> expression) 
    { 
     return ((MethodCallExpression)expression.Body).Method; 
    } 
    public IQueryable<TResult> CreateScalarQuery<TResult>(Expression<Func<TResult>> expression) 
    { 
     return QueryProvider.CreateQuery<TResult>(
      Expression.Call(
       method: GetMethodInfo(() => Queryable.Select<int, TResult>(null, (Expression<Func<int, TResult>>)null)), 
       arg0: Expression.Call(
        method: GetMethodInfo(() => Queryable.AsQueryable<int>(null)), 
        arg0: Expression.NewArrayInit(typeof(int), Expression.Constant(1))), 
       arg1: Expression.Lambda(body: expression.Body, parameters: new[] { Expression.Parameter(typeof(int)) }))); 
    } 
} 

Để sử dụng nó:

var query = context.CreateScalarQuery(() => context.Entity.Count()); 
MessageBox.Show(((ObjectQuery)query).ToTraceString()); 

Về cơ bản, điều này không là quấn một truy vấn không IQueryable trong một subselect. Nó biến truy vấn thành

from dummy in new int[] { 1 }.AsQueryable() 
select context.Entity.Count() 

ngoại trừ cho phép QueryProvider xử lý truy vấn. SQL được tạo ra là khá nhiều những gì bạn sẽ mong đợi:

SELECT 
[GroupBy1].[A1] AS [C1] 
FROM (SELECT 
    COUNT(1) AS [A1] 
    FROM [dbo].[Entity] AS [Extent1] 
) AS [GroupBy1] 
+0

Tuyệt vời, bạn có biết làm thế nào để đạt được cùng trên DBContext, mà không cần sử dụng ObjectContext của nó? Khi tôi cố gắng sử dụng cách tiếp cận tương tự - nó ném ngoại lệ - Không thể tạo thể hiện {Entity tôi muốn đếm}, truy vấn chỉ có thể sử dụng các giá trị nguyên thủy –

+0

@PhilippMunin Khi tôi thử điều này trong một dự án sử dụng DbContext, nó hoạt động tốt. Bạn có thể bao gồm một số chi tiết khác cho biết cách làm cho nó thất bại không? (Dự án của tôi đang sử dụng một bản xây dựng EF6 khá gần đây nhưng vẫn còn tiền phát hành, nếu nó quan trọng.) – hvd

+0

Tôi đã tạo ra câu hỏi riêng cho điều đó: http://stackoverflow.com/questions/19385346/dbcontext-get-iqueryable-for- scalar-system-functions-count-any-sum-max Sẽ thực sự đánh giá cao nếu bạn tìm cách để làm điều đó –

2

Bạn không thể tạo một đối tượng truy vấn cho 'Đếm' vì nó là không trả về một IQueryable (có nghĩa là - nó trả về một giá trị duy nhất).

Bạn có hai lựa chọn:

  • (Recommended) Sử dụng eSQL:

    context.CreateQuery<YourEntity>("select count(1) from YourEntitySet").ToTraceString() 
    
  • Sử dụng Reflection để gọi một phương thức riêng mà không thực hiện việc kiểm tra IQueryable (điều này là sai cho lý do rõ ràng nhưng nếu bạn chỉ cần nó để gỡ lỗi, nó có thể hữu ích):

    public static IQueryable CountCompile(this IQueryable source) 
    { 
        // you should cache this MethodInfo 
        return (IQueryable)source.Provider.GetType().GetMethod("CreateQuery", BindingFlags.NonPublic | BindingFlags.Instance, null, 
                 new[] {typeof (Expression), typeof (Type)}, null) 
         .Invoke(source.Provider, new object[] 
                { 
                 Expression.Call(
                  typeof (Queryable), "Count", 
                  new[] {source.ElementType}, source.Expression), 
                 source.ElementType 
                }); 
    } 
    
+0

Tôi nghĩ về giải pháp đầu tiên, nhưng giải pháp thứ hai của bạn khá thú vị và là những gì tôi đang tìm kiếm. Tôi đã định nghĩa Giá trị để thực hiện Đếm thông qua giải pháp hoàn thành CountCompile: giá trị T tĩnh công khai (nguồn IQueryable này) { return (T) source.Provider.Execute (source.Expression); } để tôi có thể sử dụng truy vấn.CountCompile() Giá trị () thay vì Đếm(). – alpav

+0

Bạn nên lưu ý rằng giải pháp này dựa trên các chi tiết thực hiện nội bộ và có thể thất bại ở bất kỳ bản phát hành EF nào. –

+1

Ngoài ra, nếu bạn cần thao tác SQL được tạo ra bởi EF, bạn có thể muốn xem xét việc tạo một nhà cung cấp tùy chỉnh, mặc dù đây là một * lô * phức tạp hơn những gì bạn đang đề xuất. Hãy xem mẫu này: http://archive.msdn.microsoft.com/EFSampleProvider –