preorden ordenar metodo binario arbol c# linq lambda expression-trees

c# - ordenar - Mutar el árbol de expresión de un predicado para apuntar a otro tipo



metodo preorden c# (6)

Introducción

En la aplicación en la que estoy trabajando actualmente, hay dos tipos de objetos comerciales: el tipo "ActiveRecord" y el tipo "DataContract". Entonces, por ejemplo, habría:

namespace ActiveRecord { class Widget { public int Id { get; set; } } } namespace DataContract { class Widget { public int Id { get; set; } } }

La capa de acceso a la base de datos se encarga de traducir entre familias: puede indicarle que actualice un DataContract.Widget y mágicamente creará un ActiveRecord.Widget con los mismos valores de propiedad y lo guardará en su lugar.

El problema surgió al intentar refactorizar esta capa de acceso a la base de datos.

El problema

Deseo agregar métodos como los siguientes a la capa de acceso a la base de datos:

// Widget is DataContract.Widget interface IDbAccessLayer { IEnumerable<Widget> GetMany(Expression<Func<Widget, bool>> predicate); }

Lo anterior es un método simple de "obtener" de uso general con predicado personalizado. El único punto de interés es que paso un árbol de expresiones en lugar de un lambda porque dentro de IDbAccessLayer estoy consultando un IQueryable<ActiveRecord.Widget> ; para hacer eso de manera eficiente (piense en LINQ a SQL) Necesito pasar un árbol de expresiones para que este método me pregunte eso.

El inconveniente: el parámetro debe transformarse mágicamente de un Expression<Func<DataContract.Widget, bool>> a un Expression<Func<ActiveRecord.Widget, bool>> .

Intento de solución

Lo que me gustaría hacer dentro de GetMany es:

IEnumerable<DataContract.Widget> GetMany( Expression<Func<DataContract.Widget, bool>> predicate) { var lambda = Expression.Lambda<Func<ActiveRecord.Widget, bool>>( predicate.Body, predicate.Parameters); // use lambda to query ActiveRecord.Widget and return some value }

Esto no funcionará porque en un escenario típico, por ejemplo, si:

predicate == w => w.Id == 0;

... el árbol de expresiones contiene una instancia de MemberAccessExpression que tiene una propiedad de tipo MemberInfo que describe DataContract.Widget.Id . También hay instancias de ParameterExpression tanto en el árbol de expresiones como en su colección de parámetros ( predicate.Parameters ) que describen DataContract.Widget ; todo esto dará como resultado errores ya que el cuerpo consultable no contiene ese tipo de widget, sino ActiveRecord.Widget .

Después de buscar un poco, encontré System.Linq.Expressions.ExpressionVisitor (su fuente se puede encontrar here en el contexto de un instructivo), que ofrece una forma conveniente de modificar un árbol de expresiones. En .NET 4, esta clase se incluye de fábrica.

Armado con esto, implementé un visitante. Este visitante simple solo se ocupa de cambiar los tipos de acceso de miembros y expresiones de parámetros, pero eso es suficiente funcionalidad para trabajar con el predicado w => w.Id == 0 .

internal class Visitor : ExpressionVisitor { private readonly Func<Type, Type> typeConverter; public Visitor(Func<Type, Type> typeConverter) { this.typeConverter = typeConverter; } protected override Expression VisitMember(MemberExpression node) { var dataContractType = node.Member.ReflectedType; var activeRecordType = this.typeConverter(dataContractType); var converted = Expression.MakeMemberAccess( base.Visit(node.Expression), activeRecordType.GetProperty(node.Member.Name)); return converted; } protected override Expression VisitParameter(ParameterExpression node) { var dataContractType = node.Type; var activeRecordType = this.typeConverter(dataContractType); return Expression.Parameter(activeRecordType, node.Name); } }

Con este visitante, GetMany convierte en:

IEnumerable<DataContract.Widget> GetMany( Expression<Func<DataContract.Widget, bool>> predicate) { var visitor = new Visitor(...); var lambda = Expression.Lambda<Func<ActiveRecord.Widget, bool>>( visitor.Visit(predicate.Body), predicate.Parameters.Select(p => visitor.Visit(p)); var widgets = ActiveRecord.Widget.Repository().Where(lambda); // This is just for reference, see below Expression<Func<ActiveRecord.Widget, bool>> referenceLambda = w => w.Id == 0; // Here we ''d convert the widgets to instances of DataContract.Widget and // return them -- this has nothing to do with the question though. }

Resultados

La buena noticia es que lambda está bien construida. La mala noticia es que no está funcionando; me está explotando cuando trato de usarlo, y los mensajes de excepción realmente no son útiles en absoluto.

He examinado la lambda que produce mi código y una lambda codificada con la misma expresión; se ven exactamente lo mismo. Pasé horas en el depurador tratando de encontrar alguna diferencia, pero no puedo.

Cuando el predicado es w => w.Id == 0 , lambda ve exactamente como referenceLambda . Pero el último funciona con, por ejemplo, IQueryable<T>.Where , mientras que el primero no; Lo he intentado en la ventana inmediata del depurador.

También debería mencionar que cuando el predicado es w => true , todo funciona bien. Por lo tanto, estoy asumiendo que no estoy haciendo suficiente trabajo en el visitante, pero no puedo encontrar más pistas para seguir.

Solución final

Después de tener en cuenta las respuestas correctas al problema (dos de ellas a continuación, una breve, una con código) el problema se resolvió; Puse el código junto con algunas notas importantes en una respuesta separada para evitar que esta larga pregunta sea aún más larga.

Gracias a todos por sus respuestas y comentarios!


¿ExecuteTypedList no logra lo que quieres hacer? SubSonic poblará tus DTO / POCO. Del blog de Rob Connery:

ExecuteTypedList <> intenta hacer coincidir los nombres de las columnas devueltas con los nombres de las propiedades del tipo pasado. En este ejemplo, coinciden exactamente, y eso no es completamente real. Puede solucionar esto al aliar las columnas, de la misma forma que alias una llamada SQL:

return Northwind.DB.Select("ProductID as ''ID''", "ProductName as ''Name''", "UnitPrice as ''Price''") .From<Northwind.Product>().ExecuteTypedList<Product>();

Aquí está el enlace a Rob''s Writing desacoplado, código demostrable con SubSonic 2.1


Creo que Linq-To-Sql producirá el SQL deseable si haces tus consultas correctamente. En este caso, al usar la ejecución IQueryable y diferida, puede evitar devolver todos los registros ActiveRecord.Widget .

IEnumerable<DataContract.Widget> GetMany( Func<DataContract.Widget, bool> predicate) { // get Widgets IQueryable<DataContract.Widget> qry = dc.Widgets.Select(w => TODO: CONVERT_TO_DataContract.Widget); return qry.Where(predicate); }


Intenté la implementación simple (no completa) para mutar la expresión p => p.Id == 15 (el código está debajo). Hay una clase llamada "CrossMapping" que define el mapeo entre los tipos originales y "nuevos" y escribe los miembros.

Hay varios métodos llamados Mutate_XY_Expression para cada tipo de expresión, que Mutate_XY_Expression una nueva expresión mutada. Las entradas de método necesitan el expreso original ( MemberExpression originalExpression ) como modelo de expresión, la lista o expresión de parámetros ( IList<ParameterExpression> parameterExpressions ) que son parámetros definidos por expresión "parent" y deben ser utilizados por el cuerpo "parent''s", y el mapeo objeto ( CrossMapping mapping ) que define la asignación entre tipos y miembros.

Para una implementación completa tal vez necesites más información de la expresión de los padres que los parámetros. Pero el patrón debería ser el mismo.

La muestra no implementa el patrón Visitor, como usted sabe, es porque es simple. Pero no hay barrera para convertirlos.

Espero, será de ayuda.

El código (C # 4.0):

using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Linq.Expressions; namespace ConsoleApplication1 { public class Product1 { public int Id { get; set; } public string Name { get; set; } public decimal Weight { get; set; } } public class Product2 { public int Id { get; set; } public string Name { get; set; } public decimal Weight { get; set; } } class Program { static void Main( string[] args ) { // list of products typed as Product1 var lst1 = new List<Product1> { new Product1{ Id = 1, Name = "One" }, new Product1{ Id = 15, Name = "Fifteen" }, new Product1{ Id = 9, Name = "Nine" } }; // the expression for filtering products // typed as Product1 Expression<Func<Product1, bool>> q1; q1 = p => p.Id == 15; // list of products typed as Product2 var lst2 = new List<Product2> { new Product2{ Id = 1, Name = "One" }, new Product2{ Id = 15, Name = "Fifteen" }, new Product2{ Id = 9, Name = "Nine" } }; // type of Product1 var tp1 = typeof( Product1 ); // property info of "Id" property from type Product1 var tp1Id = tp1.GetProperty( "Id", BindingFlags.Public | BindingFlags.Instance ); // delegate type for predicating for Product1 var tp1FuncBool = typeof( Func<,> ).MakeGenericType( tp1, typeof( bool ) ); // type of Product2 var tp2 = typeof( Product2 ); // property info of "Id" property from type Product2 var tp21Id = tp2.GetProperty( "Id", BindingFlags.Public | BindingFlags.Instance ); // delegate type for predicating for Product2 var tp2FuncBool = typeof( Func<,> ).MakeGenericType( tp2, typeof( bool ) ); // mapping object for types and type members var cm1 = new CrossMapping { TypeMapping = { // Product1 -> Product2 { tp1, tp2 }, // Func<Product1, bool> -> Func<Product2, bool> { tp1FuncBool, tp2FuncBool } }, MemberMapping = { // Product1.Id -> Product2.Id { tp1Id, tp21Id } } }; // mutate express from Product1''s "enviroment" to Product2''s "enviroment" var cq1_2 = MutateExpression( q1, cm1 ); // compile lambda to delegate var dlg1_2 = ((LambdaExpression)cq1_2).Compile(); // executing delegate var rslt1_2 = lst2.Where( (Func<Product2, bool>)dlg1_2 ).ToList(); return; } class CrossMapping { public IDictionary<Type, Type> TypeMapping { get; private set; } public IDictionary<MemberInfo, MemberInfo> MemberMapping { get; private set; } public CrossMapping() { this.TypeMapping = new Dictionary<Type, Type>(); this.MemberMapping = new Dictionary<MemberInfo, MemberInfo>(); } } static Expression MutateExpression( Expression originalExpression, CrossMapping mapping ) { var ret = MutateExpression( originalExpression: originalExpression, parameterExpressions: null, mapping: mapping ); return ret; } static Expression MutateExpression( Expression originalExpression, IList<ParameterExpression> parameterExpressions, CrossMapping mapping ) { Expression ret; if ( null == originalExpression ) { ret = null; } else if ( originalExpression is LambdaExpression ) { ret = MutateLambdaExpression( (LambdaExpression)originalExpression, parameterExpressions, mapping ); } else if ( originalExpression is BinaryExpression ) { ret = MutateBinaryExpression( (BinaryExpression)originalExpression, parameterExpressions, mapping ); } else if ( originalExpression is ParameterExpression ) { ret = MutateParameterExpression( (ParameterExpression)originalExpression, parameterExpressions, mapping ); } else if ( originalExpression is MemberExpression ) { ret = MutateMemberExpression( (MemberExpression)originalExpression, parameterExpressions, mapping ); } else if ( originalExpression is ConstantExpression ) { ret = MutateConstantExpression( (ConstantExpression)originalExpression, parameterExpressions, mapping ); } else { throw new NotImplementedException(); } return ret; } static Type MutateType( Type originalType, IDictionary<Type, Type> typeMapping ) { if ( null == originalType ) { return null; } Type ret; typeMapping.TryGetValue( originalType, out ret ); if ( null == ret ) { ret = originalType; } return ret; } static MemberInfo MutateMember( MemberInfo originalMember, IDictionary<MemberInfo, MemberInfo> memberMapping ) { if ( null == originalMember ) { return null; } MemberInfo ret; memberMapping.TryGetValue( originalMember, out ret ); if ( null == ret ) { ret = originalMember; } return ret; } static LambdaExpression MutateLambdaExpression( LambdaExpression originalExpression, IList<ParameterExpression> parameterExpressions, CrossMapping mapping ) { if ( null == originalExpression ) { return null; } var newParameters = (from p in originalExpression.Parameters let np = MutateParameterExpression( p, parameterExpressions, mapping ) select np).ToArray(); var newBody = MutateExpression( originalExpression.Body, newParameters, mapping ); var newType = MutateType( originalExpression.Type, mapping.TypeMapping ); var ret = Expression.Lambda( delegateType: newType, body: newBody, name: originalExpression.Name, tailCall: originalExpression.TailCall, parameters: newParameters ); return ret; } static BinaryExpression MutateBinaryExpression( BinaryExpression originalExpression, IList<ParameterExpression> parameterExpressions, CrossMapping mapping ) { if ( null == originalExpression ) { return null; } var newExprConversion = MutateExpression( originalExpression.Conversion, parameterExpressions, mapping ); var newExprLambdaConversion = (LambdaExpression)newExprConversion; var newExprLeft = MutateExpression( originalExpression.Left, parameterExpressions, mapping ); var newExprRigth = MutateExpression( originalExpression.Right, parameterExpressions, mapping ); var newType = MutateType( originalExpression.Type, mapping.TypeMapping ); var newMember = MutateMember( originalExpression.Method, mapping.MemberMapping); var newMethod = (MethodInfo)newMember; var ret = Expression.MakeBinary( binaryType: originalExpression.NodeType, left: newExprLeft, right: newExprRigth, liftToNull: originalExpression.IsLiftedToNull, method: newMethod, conversion: newExprLambdaConversion ); return ret; } static ParameterExpression MutateParameterExpression( ParameterExpression originalExpresion, IList<ParameterExpression> parameterExpressions, CrossMapping mapping ) { if ( null == originalExpresion ) { return null; } ParameterExpression ret = null; if ( null != parameterExpressions ) { ret = (from p in parameterExpressions where p.Name == originalExpresion.Name select p).FirstOrDefault(); } if ( null == ret ) { var newType = MutateType( originalExpresion.Type, mapping.TypeMapping ); ret = Expression.Parameter( newType, originalExpresion.Name ); } return ret; } static MemberExpression MutateMemberExpression( MemberExpression originalExpression, IList<ParameterExpression> parameterExpressions, CrossMapping mapping ) { if ( null == originalExpression ) { return null; } var newExpression = MutateExpression( originalExpression.Expression, parameterExpressions, mapping ); var newMember = MutateMember( originalExpression.Member, mapping.MemberMapping ); var ret = Expression.MakeMemberAccess( expression: newExpression, member: newMember ); return ret; } static ConstantExpression MutateConstantExpression( ConstantExpression originalExpression, IList<ParameterExpression> parameterExpressions, CrossMapping mapping ) { if ( null == originalExpression ) { return null; } var newType = MutateType( originalExpression.Type, mapping.TypeMapping ); var newValue = originalExpression.Value; var ret = Expression.Constant( value: newValue, type: newType ); return ret; } } }


Parece que estás generando la expresión del parámetro dos veces, en VisitMember () aquí:

var converted = Expression.MakeMemberAccess( base.Visit(node.Expression), activeRecordType.GetProperty(node.Member.Name));

... desde base.Visit () terminará en VisitParameter I imagine, y en GetMany () sí mismo:

var lambda = Expression.Lambda<Func<ActiveRecord.Widget, bool>>( visitor.Visit(predicate.Body), predicate.Parameters.Select(p => visitor.Visit(p));

Si está utilizando una ParameterExpression en el cuerpo, tiene que ser la misma instancia (no solo el mismo tipo y nombre) que la declarada para el Lambda. He tenido problemas anteriormente con este tipo de escenario, aunque creo que el resultado fue que simplemente no pude crear la expresión, solo lanzaría una excepción. En cualquier caso, puede intentar reutilizar la instancia del parámetro para ver si ayuda.


Resultó que la parte difícil es simplemente que las instancias de ParameterExpression que existen en el árbol de expresiones de la nueva lambda deben ser las mismas instancias que se pasan en el IEnumerable<ParameterExpression> de Expression.Lambda .

Tenga en cuenta que dentro de TransformPredicateLambda estoy dando t => typeof(TNewTarget) como la función "convertidor de tipo"; eso es porque en este caso específico, podemos suponer que todos los parámetros y accesos de miembros serán de ese tipo específico. Escenarios más avanzados pueden necesitar lógica adicional allí.

El código:

internal class DbAccessLayer { private static Expression<Func<TNewTarget, bool>> TransformPredicateLambda<TOldTarget, TNewTarget>( Expression<Func<TOldTarget, bool>> predicate) { var lambda = (LambdaExpression) predicate; if (lambda == null) { throw new NotSupportedException(); } var mutator = new ExpressionTargetTypeMutator(t => typeof(TNewTarget)); var explorer = new ExpressionTreeExplorer(); var converted = mutator.Visit(predicate.Body); return Expression.Lambda<Func<TNewTarget, bool>>( converted, lambda.Name, lambda.TailCall, explorer.Explore(converted).OfType<ParameterExpression>()); } private class ExpressionTargetTypeMutator : ExpressionVisitor { private readonly Func<Type, Type> typeConverter; public ExpressionTargetTypeMutator(Func<Type, Type> typeConverter) { this.typeConverter = typeConverter; } protected override Expression VisitMember(MemberExpression node) { var dataContractType = node.Member.ReflectedType; var activeRecordType = this.typeConverter(dataContractType); var converted = Expression.MakeMemberAccess( base.Visit(node.Expression), activeRecordType.GetProperty(node.Member.Name)); return converted; } protected override Expression VisitParameter(ParameterExpression node) { var dataContractType = node.Type; var activeRecordType = this.typeConverter(dataContractType); return Expression.Parameter(activeRecordType, node.Name); } } } /// <summary> /// Utility class for the traversal of expression trees. /// </summary> public class ExpressionTreeExplorer { private readonly Visitor visitor = new Visitor(); /// <summary> /// Returns the enumerable collection of expressions that comprise /// the expression tree rooted at the specified node. /// </summary> /// <param name="node">The node.</param> /// <returns> /// The enumerable collection of expressions that comprise the expression tree. /// </returns> public IEnumerable<Expression> Explore(Expression node) { return this.visitor.Explore(node); } private class Visitor : ExpressionVisitor { private readonly List<Expression> expressions = new List<Expression>(); protected override Expression VisitBinary(BinaryExpression node) { this.expressions.Add(node); return base.VisitBinary(node); } protected override Expression VisitBlock(BlockExpression node) { this.expressions.Add(node); return base.VisitBlock(node); } protected override Expression VisitConditional(ConditionalExpression node) { this.expressions.Add(node); return base.VisitConditional(node); } protected override Expression VisitConstant(ConstantExpression node) { this.expressions.Add(node); return base.VisitConstant(node); } protected override Expression VisitDebugInfo(DebugInfoExpression node) { this.expressions.Add(node); return base.VisitDebugInfo(node); } protected override Expression VisitDefault(DefaultExpression node) { this.expressions.Add(node); return base.VisitDefault(node); } protected override Expression VisitDynamic(DynamicExpression node) { this.expressions.Add(node); return base.VisitDynamic(node); } protected override Expression VisitExtension(Expression node) { this.expressions.Add(node); return base.VisitExtension(node); } protected override Expression VisitGoto(GotoExpression node) { this.expressions.Add(node); return base.VisitGoto(node); } protected override Expression VisitIndex(IndexExpression node) { this.expressions.Add(node); return base.VisitIndex(node); } protected override Expression VisitInvocation(InvocationExpression node) { this.expressions.Add(node); return base.VisitInvocation(node); } protected override Expression VisitLabel(LabelExpression node) { this.expressions.Add(node); return base.VisitLabel(node); } protected override Expression VisitLambda<T>(Expression<T> node) { this.expressions.Add(node); return base.VisitLambda(node); } protected override Expression VisitListInit(ListInitExpression node) { this.expressions.Add(node); return base.VisitListInit(node); } protected override Expression VisitLoop(LoopExpression node) { this.expressions.Add(node); return base.VisitLoop(node); } protected override Expression VisitMember(MemberExpression node) { this.expressions.Add(node); return base.VisitMember(node); } protected override Expression VisitMemberInit(MemberInitExpression node) { this.expressions.Add(node); return base.VisitMemberInit(node); } protected override Expression VisitMethodCall(MethodCallExpression node) { this.expressions.Add(node); return base.VisitMethodCall(node); } protected override Expression VisitNew(NewExpression node) { this.expressions.Add(node); return base.VisitNew(node); } protected override Expression VisitNewArray(NewArrayExpression node) { this.expressions.Add(node); return base.VisitNewArray(node); } protected override Expression VisitParameter(ParameterExpression node) { this.expressions.Add(node); return base.VisitParameter(node); } protected override Expression VisitRuntimeVariables(RuntimeVariablesExpression node) { this.expressions.Add(node); return base.VisitRuntimeVariables(node); } protected override Expression VisitSwitch(SwitchExpression node) { this.expressions.Add(node); return base.VisitSwitch(node); } protected override Expression VisitTry(TryExpression node) { this.expressions.Add(node); return base.VisitTry(node); } protected override Expression VisitTypeBinary(TypeBinaryExpression node) { this.expressions.Add(node); return base.VisitTypeBinary(node); } protected override Expression VisitUnary(UnaryExpression node) { this.expressions.Add(node); return base.VisitUnary(node); } public IEnumerable<Expression> Explore(Expression node) { this.expressions.Clear(); this.Visit(node); return expressions.ToArray(); } } }


La respuesta de Jon anterior es excelente, así que la amplié para manejar llamadas a métodos, expresiones constantes, etc. de modo que ahora funciona también para expresiones como:

x => x.SubObjects .AsQueryable() .SelectMany(y => y.GrandChildObjects) .Any(z => z.Value == 3)

También eliminé ExpressionTreeExplorer ya que lo único que necesitamos son las ParameterExpressions.

Aquí está el código ( Actualización: borre la caché cuando termine de convertir )

public class ExpressionTargetTypeMutator : ExpressionVisitor { private readonly Func<Type, Type> typeConverter; private readonly Dictionary<Expression, Expression> _convertedExpressions = new Dictionary<Expression, Expression>(); public ExpressionTargetTypeMutator(Func<Type, Type> typeConverter) { this.typeConverter = typeConverter; } // Clear the ParameterExpression cache between calls to Visit. // Not thread safe, but you can probably fix it easily. public override Expression Visit(Expression node) { bool outermostCall = false; if (false == _isVisiting) { this._isVisiting = true; outermostCall = true; } try { return base.Visit(node); } finally { if (outermostCall) { this._isVisiting = false; _convertedExpressions.Clear(); } } } protected override Expression VisitMember(MemberExpression node) { var sourceType = node.Member.ReflectedType; var targetType = this.typeConverter(sourceType); var converted = Expression.MakeMemberAccess( base.Visit(node.Expression), targetType.GetProperty(node.Member.Name)); return converted; } protected override Expression VisitParameter(ParameterExpression node) { Expression converted; if (false == _convertedExpressions.TryGetValue(node, out converted)) { var sourceType = node.Type; var targetType = this.typeConverter(sourceType); converted = Expression.Parameter(targetType, node.Name); _convertedExpressions.Add(node, converted); } return converted; } protected override Expression VisitMethodCall(MethodCallExpression node) { if (node.Method.IsGenericMethod) { var convertedTypeArguments = node.Method.GetGenericArguments() .Select(this.typeConverter) .ToArray(); var genericMethodDefinition = node.Method.GetGenericMethodDefinition(); var newMethod = genericMethodDefinition.MakeGenericMethod(convertedTypeArguments); return Expression.Call(newMethod, node.Arguments.Select(this.Visit)); } return base.VisitMethodCall(node); } protected override Expression VisitConstant(ConstantExpression node) { var valueExpression = node.Value as Expression; if (null != valueExpression) { return Expression.Constant(this.Visit(valueExpression)); } return base.VisitConstant(node); } protected override Expression VisitLambda<T>(Expression<T> node) { return Expression.Lambda(this.Visit(node.Body), node.Name, node.TailCall, node.Parameters.Select(x => (ParameterExpression)this.VisitParameter(x))); } }