2011-11-28 6 views
10

Io uso ((ObjectQuery)IQueryable).ToTraceString() per ottenere e modificare il codice SQL che verrà eseguito da LINQ.Come ottenere ToTraceString per IQueryable.Count

mio problema è che a differenza di molti metodi IQueryable IQueryable.Count come definiti in questo modo:

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

esegue query senza la compilazione e la restituzione IQueryable. Volevo fare il trucco da qualcosa di simile:

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

Ma poi createQuery mi dà la seguente eccezione:

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

risposta

5

Ecco una risposta di lavoro effettiva che ho trovato quando ho cercato di fare lo stesso. L'eccezione dice "può essere costruita solo da istanze che implementano l'interfaccia IQueryable", quindi la risposta sembra semplice: restituire un oggetto interrogabile. È possibile quando restituisci un .Count()? Sì!

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

usarlo:

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

In sostanza, ciò che fa è avvolgere una query non IQueryable in una selezione secondaria. Trasforma la query in

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

eccetto che il QueryProvider del contesto gestisce la query. L'SQL generato è praticamente quello che dovresti aspettarti:

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

Grande, sai come ottenere lo stesso su DBContext, senza utilizzare il suo ObjectContext? Quando provo a utilizzare lo stesso approccio - genera eccezione - Impossibile creare l'istanza {Entità che voglio contare}, la query può utilizzare solo i valori primitivi –

+0

@PhilippMunin Quando provo in un progetto usando DbContext, funziona perfettamente. Puoi includere altri dettagli che mostrano come farlo fallire? (Il mio progetto utilizza una build EF6 abbastanza recente ma ancora pre-release, se è importante.) – hvd

+0

Ho creato una domanda separata per questo: http://stackoverflow.com/questions/19385346/dbcontext-get-query-for- scalar-system-functions-count-any-sum-max Apprezzeremmo davvero se trovate un modo per farlo –

2

Non è possibile creare un oggetto query per 'conte' dal momento che è non restituisce un IQueryable (che ha senso, restituisce un singolo valore).

si hanno due opzioni:

  • (consigliata) Utilizzo ESQL:

    context.CreateQuery<YourEntity>("select count(1) from YourEntitySet").ToTraceString() 
    
  • utilizzare la reflection per chiamare un metodo privato che non esegue il controllo IQueryable (questo è sbagliato per ovvi motivi, ma se ne hai solo bisogno per il debug, può essere utile):

    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

Ho pensato alla prima soluzione, ma la tua seconda soluzione è piuttosto interessante ed è ciò che stavo cercando. Ho definito il valore per effettuare il conteggio tramite CountCompile soluzione completa: valore T statico pubblico (questa origine IQueryable) { return (T) source.Provider.Execute (source.Expression); } in modo che sia possibile utilizzare query.CountCompile(). Valore () anziché Count(). – alpav

+0

È necessario essere consapevoli del fatto che questa soluzione si basa su dettagli di implementazione interni e potrebbe non riuscire in nessuna versione di EF. –

+1

Inoltre, se è necessario manipolare l'SQL generato da EF, è possibile esaminare la creazione di un fornitore personalizzato, sebbene questo * lotto * sia più complesso di quello che si suggerisce. Dai un'occhiata a questo esempio: http://archive.msdn.microsoft.com/EFSampleProvider –