2011-11-28 34 views
10

我使用((ObjectQuery)IQueryable).ToTraceString()來獲取和調整將由LINQ執行的SQL代碼。如何獲得IQueryable.Count的ToTraceString

我的問題是,不像大多數的IQueryable方法IQueryable.Count因爲這樣定義:

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

無需編譯和返回IQueryable的執行查詢。 我希望通過這樣的事情做的伎倆:

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

但隨後的createQuery給了我以下異常:

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

回答

5

下面是我在嘗試如此相同時提出的實際工作答案。例外說「只能從實現IQueryable接口的實例構造」,所以答案看起來很簡單:返回一個可查詢的東西。這是可能的時候返回.Count()?是!

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)) }))); 
    } 
} 

要使用它:

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

基本上,這確實是子查詢包裹非IQueryable的查詢。它將查詢轉換爲

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

除了讓上下文的QueryProvider處理查詢。生成的SQL幾乎是你應該預期的:

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

太棒了,你知道如何在不使用ObjectContext的情況下在DBContext上實現相同的功能嗎?當我嘗試使用相同的方法 - 它會拋出異常 - 無法創建實例{我想要計算的實體},查詢只能使用原始值 –

+0

@PhilippMunin當我在使用DbContext的項目中嘗試此操作時,它工作得很好。你能否提供更多的細節來展示如何讓它失敗? (我的項目使用了一個相當新的,但仍然是預發佈EF6版本,如果它很重要。) – hvd

+0

我已經創建了單獨的問題:http://stackoverflow.com/questions/19385346/dbcontext-get-iqueryable-for-標量系統函數計數任意總和最大 真的很感激,如果你找到一種方法來做到這一點 –

2

不能爲「計數」創建一個查詢對象,因爲它是不返回IQueryable(這是有道理的 - 它返回一個值)。

你有兩個選擇:

  • (推薦)使用ESQL:

    context.CreateQuery<YourEntity>("select count(1) from YourEntitySet").ToTraceString() 
    
  • 使用反射來調用不執行IQueryable的檢查(這是不對的私有方法顯然的原因,但如果你只是需要它進行調試,它可能會得心應手):

    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

我想到了第一個解決方案,但你的第二個解決方案非常有趣,我正在尋找。我通過CountCompile定義值來使Count完整的解決方案:public static T Value (this IQueryable source){ return(T)source.Provider.Execute(source.Expression); } 以便我可以使用query.CountCompile()。值()而不是Count()。 – alpav

+0

您應該意識到此解決方案依賴於內部實現細節,並且可能會在任何版本的EF上失敗。 –

+1

另外,如果您需要操作EF生成的SQL,您可能需要考慮創建自定義提供程序,雖然這比您建議的要複雜得多。看看這個示例:http://archive.msdn.microsoft。com/EFSampleProvider –