tamaños significado oreja numero expansores expansor expansiones cara 2mm c# lambda closures expression-trees linqkit

c# - significado - ¿Hay alguna razón particular por la que el expansor de LinqKit no pueda recoger expresiones de los campos?



expansores numero 4 (2)

Estoy usando la biblioteca LinqKit que permite combinar expresiones sobre la marcha.

Esta es una felicidad pura para escribir la capa de acceso a datos de Entity Framewok porque, opcionalmente, se pueden reutilizar y combinar varias expresiones, lo que permite un código legible y eficiente.

Considere la siguiente pieza de código:

private static readonly Expression<Func<Message, int, MessageView>> _selectMessageViewExpr = ( Message msg, int requestingUserId ) => new MessageView { MessageID = msg.ID, RequestingUserID = requestingUserId, Body = ( msg.RootMessage == null ) ? msg.Body : msg.RootMessage.Body, Title = ( ( msg.RootMessage == null ) ? msg.Title : msg.RootMessage.Title ) ?? string.Empty };

Declaramos una expresión que proyecta Message en MessageView ( MessageView los detalles para mayor claridad).

Ahora, el código de acceso a datos puede usar esta expresión para obtener un mensaje individual:

var query = CompiledQueryCache.Instance.GetCompiledQuery( "GetMessageView", () => CompiledQuery.Compile( _getMessagesExpr .Select( msg => _selectMessageViewExpr.Invoke( msg, userId ) ) // re-use the expression .FirstOrDefault( ( MessageView mv, int id ) => mv.MessageID == id ) .Expand() ) );

Esto es hermoso porque la misma expresión se puede reutilizar para obtener una lista de mensajes también:

var query = CompiledQueryCache.Instance.GetCompiledQuery( "GetMessageViewList", () => CompiledQuery.Compile( BuildFolderExpr( folder ) .Select( msg => _selectMessageViewExpr.Invoke( msg, userId ) ) .OrderBy( mv => mv.DateCreated, SortDirection.Descending ) .Paging() .Expand() ), folder );

Como puede ver, la expresión de proyección se almacena en _selectMessageViewExpr y se utiliza para crear varias consultas diferentes.

Sin embargo, pasé mucho tiempo rastreando un error extraño cuando este código se bloqueó en la llamada Expand() .
El error decía:

No se puede convertir el objeto de tipo System.Linq.Expressions.FieldExpression para escribir System.Linq.Expressions.LambdaExpression .

Solo después de un tiempo me di cuenta de que todo funciona cuando se hace referencia a una expresión en una variable local antes de Invoke en :

var selector = _selectMessageViewExpr; // reference the field var query = CompiledQueryCache.Instance.GetCompiledQuery( "GetMessageView", () => CompiledQuery.Compile( _getMessagesExpr .Select( msg => selector.Invoke( msg, userId ) ) // use the variable .FirstOrDefault( ( MessageView mv, int id ) => mv.MessageID == id ) .Expand() ) );

Este código funciona como se espera.

Mi pregunta es:

¿Hay alguna razón específica por la que LinqKit no reconozca Invoke en expresiones almacenadas en campos? ¿Es solo una omisión por parte del desarrollador, o hay alguna razón importante por la que las expresiones deben almacenarse primero en las variables locales?

Esta pregunta probablemente puede responderse mirando el código generado y verificando las fuentes de LinqKit. Sin embargo, pensé que tal vez alguien relacionado con el desarrollo de LinqKit podría responder esta pregunta.

Gracias.


Descargué el código fuente y traté de analizarlo. ExpressionExpander no permite hacer referencia a expresiones que están almacenadas en variables que no sean constantes. Espera la expresión de que se está Invoke método Invoke para que haga referencia al objeto representado por ConstantExpression , no a otro MemberExpression .

Por lo tanto, no podemos proporcionar nuestra expresión reutilizable como referencia a ningún miembro de la clase (incluso campos públicos, no propiedades). El acceso de miembros object.member1.member2 (como object.member1.member2 ... etc) tampoco es compatible.

Pero esto puede solucionarse atravesando la expresión inicial y extrayendo recíprocamente los valores de los subcampos.

He reemplazado el código del método TransformExpr de ExpressionExpander clase ExpressionExpander para

var lambda = Expression.Lambda(input); object value = lambda.Compile().DynamicInvoke(); if (value is Expression) return Visit((Expression)value); else return input;

y funciona ahora.

En esta solución, todo lo que mencioné anteriormente (recursivamente atravesando el árbol) se realiza por nosotros mediante el compilador de ExpressionTree :)


He creado una versión mejorada de respuesta de micrófono :

if (input == null) return input; var field = input.Member as FieldInfo; var prope = input.Member as PropertyInfo; if ((field != null && field.FieldType.IsSubclassOf(typeof(Expression))) || (prope != null && prope.PropertyType.IsSubclassOf(typeof(Expression)))) return Visit(Expression.Lambda<Func<Expression>>(input).Compile()()); return input;

La principal ventaja es la eliminación de DynamicInvoke que tiene grandes gastos generales y llamadas Invoke solo cuando realmente lo necesito.