Come ottenere ToTraceString per IQueryable.Count

c# expression-trees lambda linq linq-to-entities

Domanda

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

Il mio problema è che diversamente dalla maggior parte dei metodi IQueryable IQueryable.Count come definito 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 la query senza compilare e restituire IQueryable. Volevo fare il trucco con qualcosa del genere:

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 accettata

Ecco una risposta effettiva che mi è venuta quando ho provato a 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 si restituisce 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)) })));
    }
}

Per usarlo:

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

Fondamentalmente, ciò che fa è racchiudere una query non IQueryable in una sottoselezione. 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 ciò che dovresti aspettarti:

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

Risposta popolare

Non è possibile creare un oggetto query per "Conteggio" poiché non restituisce un IQueryable (che ha senso, restituisce un singolo valore).

Hai due opzioni:

  • (Consigliato) Usa eSQL:

    context.CreateQuery<YourEntity>("select count(1) from YourEntitySet").ToTraceString()
    
  • Usa Reflection per chiamare un metodo privato che non esegue il controllo IQueryable (questo è sbagliato per ovvi motivi, ma se hai solo bisogno di eseguire 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
                                         });
    }
    


Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché