Compilación de consultas de Linq a SQL desde un IQueryable no trivial

.net expression-trees linq-to-sql performance

Pregunta

¿Hay alguna manera de usar el método CompiledQuery.Compile para compilar la expresión asociada con un IQueryable? Actualmente tengo un IQueryable con un gran árbol de Expresión detrás. El IQueryable se creó utilizando varios métodos que suministran componentes. Por ejemplo, dos métodos pueden devolver IQueryables que luego se unen en un tercero. Por esta razón, no puedo definir explícitamente la expresión completa dentro de la llamada al método compile ().

Esperaba pasar la expresión al método de compilación como someIQueryable.Expression, sin embargo, esta expresión no está en la forma requerida por el método de compilación. Si trato de evitar esto poniendo la consulta directamente en el método de compilación, por ejemplo:

    var foo = CompiledQuery.Compile<DataContext, IQueryable<User>>(dc => dc.getUsers());
    var bar = foo(this);

donde hago el formulario de llamada dentro de un contexto de datos, recibo un error que dice que "getUsers no se asigna como un procedimiento almacenado o una función definida por el usuario". De nuevo, no puedo copiar el contenido del método getUsers al lugar donde realizo la llamada de compilación, ya que a su vez utiliza otros métodos.

¿Hay alguna manera de pasar la Expresión en el IQueryable devuelto de "getUsers" al método Compilar?

Actualicé . Intenté forzar mi voluntad en el sistema con el siguiente código:

    var phony = Expression.Lambda<Func<DataContext, IQueryable<User>>>(
        getUsers().Expression, Expression.Parameter(typeof(DataContext), "dc"));

    Func<DataContext, IQueryable<User>> wishful = CompiledQuery.Compile<DataContext, IQueryable<User>>(phony);
    var foo = wishful(this);

foo termina siendo:

{System.Data.Linq.SqlClient.SqlProvider + OneTimeEnumerable`1 [Model.Entities.User]}

No tengo la opción de ver los resultados en foo, ya que en lugar de ofrecer expandir los resultados y ejecutar la consulta, solo veo el mensaje "La operación podría desestabilizar el tiempo de ejecución".

Solo necesito encontrar una forma para que la cadena sql solo se genere una vez y se use como un comando paramaterizado en solicitudes posteriores. Puedo hacerlo manualmente usando el método GetCommand en el contexto de datos, pero luego tengo que establecer explícitamente todos los parámetros. y hago el mapeo de objetos por mí mismo, que es unos pocos cientos de líneas de código dada la complejidad de esta consulta en particular.

Actualizado

John Rusk proporcionó la información más útil, por lo que le concedí la victoria en este caso. Sin embargo, se requerían algunos ajustes adicionales y hubo un par de otros problemas que encontré en el camino, así que pensé que me 'expandiría' en la respuesta. En primer lugar, el error 'La operación podría desestabalizar el tiempo de ejecución' no se debió a la compilación de la expresión, en realidad se debió a algunos lanzamientos profundos en el árbol de expresiones. En algunos lugares necesitaba llamar al .Cast<T>() para emitir formalmente elementos, incluso cuando eran del tipo correcto. Sin entrar en demasiados detalles, esto era básicamente necesario cuando varias expresiones se combinaban en un solo árbol y cada rama podía devolver un tipo diferente, cada uno de los cuales era el subtipo de una clase común.

Después de resolver el problema de desestabilización, volví al problema de compilación. La solución de expansión de John estaba casi allí. Buscó expresiones de llamada de método en el árbol e intentó resolverlas con la expresión subyacente que el método usualmente devolvería. Mis referencias a expresiones no fueron proporcionadas por las llamadas a métodos, sino por propiedades. Así que necesitaba modificar la expresión visitante que realiza la expansión para incluir estos tipos:

protected override Expression VisitMemberAccess(MemberExpression m) {
    if(m.Method.DeclaringType == typeof(ExpressionExtensions)) {
        return new ExpressionExpander().Visit((Expression)(((System.Reflection.PropertyInfo)m.Member).GetValue(null, null)));
    }
    return base.VisitMemberAccess(m);
}

Este método puede no ser apropiado en todos los casos, pero debería ayudar a cualquier persona que se encuentre en la misma situación.

Respuesta aceptada

Algo así funciona, al menos en mis pruebas:

Expression<Func<DataContext, IQueryable<User>> queryableExpression = GetUsers();
var expressionWithSomeAddedStuff = (DataContext dc) => from u in queryableExpression.Invoke(dc) where ....;
var expressionThatCanBeCompiled = expressionWithSomeAddedStuff.Expand();
var foo = CompiledQuery.Compile<DataContext, IQueryable<User>>(expressionThatCanBeCompiled);

Esto parece un poco detallado, y probablemente hay mejoras que puede hacer.

El punto clave es que utiliza los métodos Invocar y Expandir de LinqKit. Básicamente, le permiten crear una consulta a través de la composición y luego compilar el resultado final.



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