2011-11-28 4 views
10

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

Mi problema es que a diferencia de la mayoría de los métodos IQueryable IQueryable.Count tal como se definen 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 consulta sin compilar y volviendo IQueryable. que 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

5

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

utilizarlo:

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 debería esperar:

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

Genial, ¿sabes cómo lograr lo mismo en DBContext, sin usar su ObjectContext? Cuando trato de usar el mismo enfoque - arroja una excepción - No puedo crear la instancia {Entidad que deseo contar}, la consulta solo puede usar valores primitivos –

+0

@PhilippMunin Cuando intento esto en un proyecto que usa DbContext, funciona bien. ¿Puedes incluir más detalles que muestren cómo hacer que falle? (Mi proyecto está usando una compilación de EF6 bastante reciente pero aún prelanzada, si es que importa.) – hvd

+0

He creado una pregunta separada para eso: http://stackoverflow.com/questions/19385346/dbcontext-get-iqueryable-for- scalar-system-functions-count-any-sum-max Realmente apreciaría si encuentra una forma de hacerlo –

2

no se puede crear un objeto de consulta para 'contar', ya que es no devuelve un IQueryable (lo cual tiene sentido, devuelve un solo valor).

tiene dos opciones:

  • (recomendado) Uso esql:

    context.CreateQuery<YourEntity>("select count(1) from YourEntitySet").ToTraceString() 
    
  • utilizar la reflexión para llamar a un método privado que no realiza la comprobación IQueryable (esto está mal para 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 
                }); 
    } 
    
+0

Pensé en la primera solución, pero su segunda solución es bastante interesante y es lo que estaba buscando. Definí el valor para realizar la cuenta a través de la solución completa CountCompile: público estático Valor T (esta fuente IQueryable) { return (T) source.Provider.Execute (source.Expression); } para que pueda usar query.CountCompile(). Valor () en lugar de Count(). – alpav

+0

Debe tener en cuenta que esta solución se basa en detalles de implementación interna y podría fallar en cualquier versión de EF. –

+1

Además, si necesita manipular el SQL generado por EF, puede considerar crear un proveedor personalizado, aunque esto es * mucho * más complejo que lo que está sugiriendo. Eche un vistazo a esta muestra: http://archive.msdn.microsoft.com/EFSampleProvider –

Cuestiones relacionadas