Cómo obtener ToTraceString para IQueryable.Count

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

Pregunta

Uso ((ObjectQuery)IQueryable).ToTraceString() para obtener y modificar el código SQL que va a ejecutar LINQ.

Mi problema es que, a diferencia de la mayoría de los métodos de IQueryable, IQueryable.Count se define así:

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

Ejecuta la consulta sin compilar y devolver IQueryable. Quería hacer el truco por algo como esto:

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

Pero entonces CreateQuery me da la siguiente excepción:

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

Respuesta aceptada

Aquí hay una respuesta de trabajo real que se me ocurrió cuando traté de hacer lo mismo. La excepción dice que "solo se puede construir a partir de instancias que implementan la interfaz IQueryable", por lo que la respuesta parece simple: devolver algo cuestionable. ¿Es eso posible al devolver 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)) })));
    }
}

Para usarlo:

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

Básicamente, lo que esto hace es envolver una consulta no IQueryable en una subselección. Transforma la consulta en.

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

excepto permite que el QueryProvider del contexto maneje la consulta. El SQL generado es más o menos lo que debe esperar:

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

Respuesta popular

No puede crear un objeto de consulta para 'Recuento' ya que no devuelve un IQueryable (lo que tiene sentido, devuelve un solo valor).

Tienes dos opciones:

  • (Recomendado) Use eSQL:

    context.CreateQuery<YourEntity>("select count(1) from YourEntitySet").ToTraceString()
    
  • Use Reflection para llamar a un método privado que no realice la verificación IQueryable (esto es incorrecto por razones obvias, pero si solo lo necesita para la depuración, puede ser útil):

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


Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow