internal ObjectExpression(Type objectType, Expression expression, Mappings maps, int parameterCount) { _objectType = objectType; _expression = expression; _maps = maps; _parameterCount = parameterCount; }
public Function(FunctionOperator op, Expression[] args) { if( op == FunctionOperator.Substring && args.Length == 2 ) { Expression[] newArgs = new Expression[3]; newArgs[0] = args[0]; newArgs[1] = args[1]; newArgs[2] = new Function(FunctionOperator.Len, new Expression[] {(Expression)args[0].Clone()}); args = newArgs; } this.Operator = op; this.Params = args; }
public OrderBy(Expression source, OrderByItemCollection orderByItems, OrderByJoinCollection orderByJoins) { this.Source = source; _items = orderByItems; _joins = orderByJoins; }
private bool ValidateNode(Expression node, object[] noargs) { node.Validate(); return true; }
internal static Type GetResultType(Expression leftExpr, Expression rightExpr, BinaryOperator op) { if( IsOperatorBoolean(op) ) { return typeof(bool); } if( op == BinaryOperator.Concatenation ) { return typeof(string); } // if we make it here, we are deal with an Arithmetic operator //V2: the code below is a gross simplification or the actual logic that should be here truly deal with all the scenarios // there is MUCH MUCH more to do here to fully implement this method. Type leftType = leftExpr.ValueType; Type rightType = rightExpr.ValueType; if( leftExpr.NodeType == NodeType.Parameter ) { leftType = rightType; //V2: use inferredType? } else if( rightExpr.NodeType == NodeType.Parameter ) { rightType = leftType; //V2: use inferredType? } // to simplify the logic convert char types to strings if( leftType == typeof(char) ) { leftType = typeof(string); } if( rightType == typeof(char) ) { rightType = typeof(string); } // a string combined with any other type equals a string if( leftType == typeof(string) || rightType == typeof(string) ) { return typeof(string); } // a datetime combined with any other type equals a datetime if( leftType == typeof(DateTime) || rightType == typeof(DateTime) ) { return typeof(DateTime); } // determine the precedence of the two types int leftIndex = GetPrecedenceIndex(leftType); int rightIndex = GetPrecedenceIndex(rightType); if( leftIndex == 0 || rightIndex == 0 ) { //note: we should probably be throwing an error here return null; } // return the type with the higher precedence return (leftIndex >= rightIndex) ? leftType : rightType; }
private bool ReplaceAxisCallback(Expression node, object[] noargs) // note: internal longhorn name found on www.winfx247.com { if( node.NodeType == NodeType.Axis ) { Axis axis = (Axis)node; // convert the axis into a filter (don't want any axis nodes in final tree) Expression filter = new Filter(axis); // see if we need to wrap the filter with an exist node // note: this is needed when: // 1) the source property of the axis is relational // 2) the parent of the axis is not already an exists (for future support for the exists keyword) // 3) the axis is not in the source of its parent filter if( axis.Owner.NodeType != NodeType.Unary || ((Unary)axis.Owner).Operator != UnaryOperator.Exists ) { // get the source property for this axis Property property; if( axis.Source.NodeType == NodeType.Property ) { property = (Property)axis.Source; } else if( axis.Source.NodeType == NodeType.Filter || axis.Source.NodeType == NodeType.Axis ) { property = (Property)(axis.Source as Filter).Source; } else // source not property, filter, or axis { throw new Exception("Axis source node type of '" + axis.Source.NodeType + "' was not expected."); } if( property.IsRelational ) { // find the containing filter for this axis (there has to be one) and track the child Expression child = axis; Expression parent = axis.Owner; while( parent != null && parent.NodeType != NodeType.Filter ) { child = parent; parent = parent.Owner; } if( parent == null ) { throw new Exception("Axis node is not contained in a Filter node. Assumption failed."); } if( ((Filter)parent).Source != child ) // axis not in source of filter { filter = new Unary(UnaryOperator.Exists, filter); } } } // do the replacement Expression.Replace(axis, filter); } return true; }
private bool ReplaceAddWithConcatenation(Expression node, object[] noargs) // note: internal longhorn name found on www.winfx247.com { if( node.NodeType == NodeType.Binary ) { Binary binary = (Binary)node; if( binary.Operator == BinaryOperator.Addition && binary.Left.ValueType == typeof(string) && binary.Right.ValueType == typeof(string) ) { binary.Operator = BinaryOperator.Concatenation; } } return true; }
public static void EnumNodes(Expression root, EnumNodesCallBack callback, EnumNodesCallBack postCallback, params object[] args) { switch( root.NodeType ) { case NodeType.Literal: case NodeType.Parameter: case NodeType.Context: { if( callback != null ) { callback(root, args); } if( postCallback != null ) { postCallback(root, args); } return; } case NodeType.Binary: { if( callback == null || callback(root, args) ) { Binary node = (Binary)root; Expression.EnumNodes(node.Left, callback, postCallback, args); Expression.EnumNodes(node.Right, callback, postCallback, args); if( postCallback != null ) { postCallback(root, args); } } return; } case NodeType.Unary: { if( callback == null || callback(root, args) ) { Unary node = (Unary)root; Expression.EnumNodes(node.Operand, callback, postCallback, args); if( postCallback != null ) { postCallback(root, args); } } return; } case NodeType.Axis: case NodeType.Filter: { if( callback == null || callback(root, args) ) { Filter node = (Filter)root; Expression.EnumNodes(node.Source, callback, postCallback, args); Expression.EnumNodes(node.Constraint, callback, postCallback, args); if( postCallback != null ) { postCallback(root, args); } } return; } case NodeType.Property: { if( callback == null || callback(root, args) ) { Property node = (Property)root; Expression.EnumNodes(node.Source, callback, postCallback, args); if( postCallback != null ) { postCallback(root, args); } } return; } case NodeType.Parent: { if( callback == null || callback(root, args) ) { Parent node = (Parent)root; Expression.EnumNodes(node.Source, callback, postCallback, args); if( postCallback != null ) { postCallback(root, args); } } return; } case NodeType.Function: { if( callback == null || callback(root, args) ) { Function node = (Function)root; for( int i = 0; i < node.Params.Length; i++ ) { Expression.EnumNodes(node.Params[i], callback, postCallback, args); } if( postCallback != null ) { postCallback(root, args); } } return; } case NodeType.TypeFilter: { if( callback == null || callback(root, args) ) { TypeFilter node = (TypeFilter)root; Expression.EnumNodes(node.Source, callback, postCallback, args); if( postCallback != null ) { postCallback(root, args); } } return; } case NodeType.Empty: { return; } case NodeType.OrderBy: { if( callback == null || callback(root, args) ) { OrderBy node = (OrderBy)root; Expression.EnumNodes(node.Source, callback, postCallback, args); if( postCallback != null ) { postCallback(root, args); } } return; } default: { throw new NotSupportedException("Expression type '" + root.GetType() + "' is not currently supported."); } } }
private bool RecomposeBinary(Expression node, object[] noargs) // note: internal longhorn name found on www.winfx247.com { if( node.NodeType == NodeType.Binary ) { Binary binary = (Binary)node; bool leftIsAxis = (binary.Left.NodeType == NodeType.Axis); bool rightIsAxis = (binary.Right.NodeType == NodeType.Axis); // nothing to do if neither branch contains an axis if( !leftIsAxis && !rightIsAxis ) { return true; } // don't consider moving the binary node if neither branch is constant (and joined by a logical operator) // the binary node should stay above an axis in this case (example: (AAA.BBB = X) && (CCC = Y)) if( !binary.Left.IsConst && !binary.Right.IsConst && Binary.IsOperatorLogical(binary.Operator) ) { return true; } // see if we have an axis on both sides if( leftIsAxis && rightIsAxis ) { throw new NotSupportedException("Relationship traversals on both sides of a binary operator is not currently supported."); // recompose the left and right to put both under one axis //V2: Recompose2Axis(binary); //return true; } else // axis on one side only { // get our root axis and find the last (leaf) axis in the chain // the leaf axis will hold the parent binary when moved // (this is to handle multiple dot expressions like: AAA.BBB.CCC = @Value) Axis axis = (leftIsAxis) ? (Axis)binary.Left : (Axis)binary.Right; Axis leafAxis = axis; int parentCount = 1; while( leafAxis.Constraint.NodeType == NodeType.Axis ) { leafAxis = (Axis)leafAxis.Constraint; parentCount += 1; } // now make the binary node the constraint of the the leaf axis // and move the existing constraint under the other branch of the binary if( leftIsAxis ) { Expression newRight = (Expression)binary.Right.Clone(); Expression.EnumNodes(newRight, new Expression.EnumNodesCallBack(this.IncreaseParentDepth), null, parentCount); leafAxis.Constraint = new Binary(binary.Operator, leafAxis.Constraint, newRight); } else // right is axis { Expression newLeft = (Expression)binary.Left.Clone(); Expression.EnumNodes(newLeft, new Expression.EnumNodesCallBack(this.IncreaseParentDepth), null, parentCount); leafAxis.Constraint = new Binary(binary.Operator, newLeft, leafAxis.Constraint); } Expression.Replace(binary, axis); } } return true; }
public Filter(Expression source, Expression constraint) { this.Source = source; this.Constraint = constraint; }
public Binary(BinaryOperator op, Expression left, Expression right) { this.Operator = op; this.Left = left; this.Right = right; }
private void WriteSqlQuery(TextWriter w, Expression expr) { switch( expr.NodeType ) { case NodeType.Property: { Property property = (Property)expr; EntityMap map = _maps[property.OwnerClass]; FieldMap field = map.GetFieldMap(property.Name); if( field == null ) { throw new Exception("Property '" + property.Name + "' could not be found for entity type '" + property.OwnerClass + "'."); } string alias; if( !field.IsLookup ) { alias = GetAlias(property); } else // lookup field { LookupMap lookup = (LookupMap)field; alias = (string)_lookupFieldToAliasMap[lookup.FieldAlias]; } if( alias == null ) { throw new Exception("Could not find table alias for property '" + property.Name + "' in entity '" + property.OwnerClass + "'."); } WriteSqlColumn(w, alias, field.Field); return; } case NodeType.Parameter: { Query.Parameter node = (Query.Parameter)expr; string name; if( !_provider.NoNamedParameters ) { name = _provider.ParameterPrefix + "P" + node.Ordinal; } else { name = "?"; } _parameterTable.Add( new OPathParameter(name, node.ValueType) ); w.Write(name); return; } case NodeType.Literal: { Literal node = (Literal)expr; WriteLiteral(w, node.Value); return; } case NodeType.Binary: { Binary node = (Binary)expr; bool isFormat; string keyword = GetSqlKeyword(node.Operator, out isFormat); if( isFormat ) { WriteFormat(w, keyword, node.Left, node.Right); } else { w.Write('('); WriteSqlQuery(w, node.Left); w.Write(' '); w.Write(keyword); w.Write(' '); WriteSqlQuery(w, node.Right); w.Write(')'); } return; } case NodeType.Unary: { Unary node = (Unary)expr; if( node.Operator == UnaryOperator.Exists ) { w.Write(GetSqlKeyword(node.Operator)); w.Write("\n(\n"); WriteSqlQuery(w, node.Operand); w.Write("\n)"); } else if( node.Operator == UnaryOperator.IsNull ) { w.Write('('); WriteSqlQuery(w, node.Operand); w.Write(' '); w.Write(GetSqlKeyword(node.Operator)); w.Write(')'); } else { w.Write(GetSqlKeyword(node.Operator)); w.Write('('); WriteSqlQuery(w, node.Operand); w.Write(')'); } return; } case NodeType.Filter: { Filter filter = (Filter)expr; // the only supported filters are the root and ones directly below an exists node if( filter.Owner != null && (filter.Owner.NodeType != NodeType.Unary || (filter.Owner as Unary).Operator != UnaryOperator.Exists) ) { throw new NotSupportedException("Filter with source type '" + filter.Source.NodeType + "' was not expected."); } EntityMap entity = _maps[filter.ValueType]; string entityAlias = GetNextAlias(); filter.Alias = entityAlias; bool whereStarted = false; if( filter.Source.NodeType == NodeType.TypeFilter ) // root filter/outer select { Join[] lookupJoins; WriteSelectClause(w, entity, entityAlias, out lookupJoins); Join[] joins; if( _orderByNode != null ) { Join[] orderByJoins = GetJoinsFromOrderBy(entity, entityAlias, _orderByNode); joins = new Join[lookupJoins.Length + orderByJoins.Length]; lookupJoins.CopyTo(joins, 0); orderByJoins.CopyTo(joins, lookupJoins.Length); } else { joins = lookupJoins; } WriteFromClause(w, entity.Table, entityAlias, joins); if( entity.BaseEntity != null ) { WriteSubEntityConstraint(w, entityAlias, entity.TypeField, entity.TypeValue, ref whereStarted); } WriteFilterConstraints(w, filter, whereStarted); // add the default sort order if the entity has one defined but the query is not ordered if( _orderByNode == null && entity.SortOrder != null && entity.SortOrder.Length > 0 ) { w.Write("\nORDER BY "); w.Write(entity.SortOrder); } } else if( filter.Source.NodeType == NodeType.Property || filter.Source.NodeType == NodeType.Filter ) // nested filter/subquery { Expression source = filter.Source; while( source.NodeType == NodeType.Filter ) { source = (source as Filter).Source; } if( source.NodeType != NodeType.Property ) throw new Exception("Could not find source property for filter."); Property relProperty = (Property)source; if( !relProperty.IsRelational ) throw new Exception("Expected source property for Filter node to be relational."); RelationMap relation = relProperty.RelationMap; EntityMap sourceEntity = _maps[relProperty.OwnerClass]; string sourceAlias = GetAlias(relProperty); string relationConstraint = ReplaceTableNameWithAlias(relation.Filter, sourceEntity.Table, sourceAlias); relationConstraint = ReplaceTableNameWithAlias(relationConstraint, entity.Table, entityAlias); switch( relation.Relationship ) { case Relationship.Parent: { w.Write("SELECT *"); WriteFromClause(w, entity.Table, entityAlias, null); if( entity.BaseEntity != null ) { WriteSubEntityConstraint(w, entityAlias, entity.TypeField, entity.TypeValue, ref whereStarted); } WriteJoinCondition(w, sourceAlias, relation.Fields, entityAlias, GetFieldNames(entity.KeyFields), relationConstraint, true, ref whereStarted); WriteFilterConstraints(w, filter, whereStarted); break; } case Relationship.Child: { w.Write("SELECT *"); WriteFromClause(w, entity.Table, entityAlias, null); if( entity.BaseEntity != null ) { WriteSubEntityConstraint(w, entityAlias, entity.TypeField, entity.TypeValue, ref whereStarted); } WriteJoinCondition(w, sourceAlias, GetFieldNames(sourceEntity.KeyFields), entityAlias, relation.Fields, relationConstraint, true, ref whereStarted); WriteFilterConstraints(w, filter, whereStarted); break; } case Relationship.Many: { ManyMap junctionMap = (ManyMap)relation; string junctionAlias = GetNextAlias(); relationConstraint = ReplaceTableNameWithAlias(relationConstraint, junctionMap.Table, junctionAlias); // write the junction and child table, inner joined together in one select w.Write("SELECT *"); WriteFromClause(w, junctionMap.Table, junctionAlias, null); w.Write(", "); WriteSqlTable(w, entity.Table, entityAlias); if( entity.BaseEntity != null ) { WriteSubEntityConstraint(w, entityAlias, entity.TypeField, entity.TypeValue, ref whereStarted); } WriteJoinCondition(w, sourceAlias, GetFieldNames(sourceEntity.KeyFields), junctionAlias, junctionMap.Source, null, true, ref whereStarted); WriteJoinCondition(w, junctionAlias, junctionMap.Dest, entityAlias, GetFieldNames(entity.KeyFields), relationConstraint, true, ref whereStarted); WriteFilterConstraints(w, filter, whereStarted); break; } default: { throw new NotSupportedException("Relationship type '" + relation.Relationship + "' is not supported."); } } } else { throw new NotImplementedException("Filter with source type '" + filter.Source.NodeType + "' was not expected."); } return; } case NodeType.Function: { Function node = (Function)expr; bool isFormat; string keyword = GetSqlKeyword(node.Operator, out isFormat); if( isFormat ) { WriteFormat(w, keyword, node.Params); } else { if( node.Operator != FunctionOperator.In ) { throw new NotSupportedException("Function operator '" + node.Operator + "' was not expected."); } w.Write('('); WriteSqlQuery(w, node.Params[0]); w.Write(' '); w.Write(keyword); w.Write(" ("); for( int i = 1; i < node.Params.Length; i++ ) { if( i > 1 ) w.Write(", "); WriteSqlQuery(w, node.Params[i]); } w.Write("))"); } return; } case NodeType.OrderBy: { OrderBy node = (OrderBy)expr; _orderByNode = node; Filter filter = (node.Source as Filter); if( filter == null ) { throw new Exception("Expected source of OrderBy node to be Filter."); } WriteSqlQuery(w, filter); w.Write("\nORDER BY "); for( int i = 0; i < node.OrderByItems.Count; i++ ) { OrderByItem item = node.OrderByItems[i]; FieldMap field = item.FieldInfo; if( field == null ) { throw new Exception("Field info does not exists for OrderByItem '" + item.Item + "'."); } string alias = (item.Join == null) ? filter.Alias : item.Join.Alias; if( i > 0 ) w.Write(", "); WriteSqlColumn(w, alias, field.Field); w.Write( (item.Ascending) ? " ASC" : " DESC"); } return; } case NodeType.Empty: { return; } default: { throw new NotSupportedException("Expression type '" + expr.GetType() + "' is not currently supported."); } } }
public Property(string name, Expression source) { this.Name = name; this.Source = source; }
private bool OleDbSetParameterOrderInFilter(Expression node, object[] args) { if( node.NodeType == NodeType.Filter ) { // prevent enumerator from traversing child filters return false; } else if( node.NodeType == NodeType.Parameter ) { int index = (node as Query.Parameter).Ordinal; int nextOrdinal = (int)args[0]; _parameterTable[index].Ordinal = nextOrdinal; args[0] = nextOrdinal + 1; } return true; }
private bool OleDbSetParameterOrder(Expression node, object[] args) { if( node.NodeType == NodeType.Filter ) { // the parameters in this filter are the next in sequence Filter filter = (Filter)node; Expression.EnumNodesCallBack callback = new Expression.EnumNodesCallBack(this.OleDbSetParameterOrderInFilter); Expression.EnumNodes(filter.Source, callback, null, args); Expression.EnumNodes(filter.Constraint, callback, null, args); } return true; }
public TypeFilter(Expression source, Type type, bool baseTypeOnly) { this.Source = source; this.Type = type; this.BaseTypeOnly = baseTypeOnly; }
public TypeFilter(Expression source, Type type) : this(source, type, false) { }
public override object Clone() { Expression[] args = new Expression[_args.Length]; for( int i = 0; i < _args.Length; i++ ) { args[i] = (Expression)_args[i].Clone(); } return new Function(_op, args); }
public static void EnumNodes(Expression root, EnumNodesCallBack callback) { EnumNodes(root, callback, null, null); }
private bool ValidateNoFilters(Expression node, object[] noargs) { if( node.NodeType == NodeType.Filter || node.NodeType == NodeType.Axis ) { throw new OPathException("Relationship traversal found in parameter for " + GetDisplayName(_op) + " function. Please move the traversal outside the function."); } return true; }
public static void Replace(Expression oldNode, Expression newNode) { if( oldNode == null ) throw new ArgumentNullException("oldNode"); if( newNode == null ) throw new ArgumentNullException("newNode"); Expression parent = oldNode.Owner; switch( parent.NodeType ) { case NodeType.Axis: { Axis axis = (Axis)parent; if( axis.Source == oldNode ) { axis.Source = newNode; } else if( axis.Constraint == oldNode ) { axis.Constraint = newNode; } break; } case NodeType.Binary: { Binary binary = (Binary)parent; if( binary.Left == oldNode ) { binary.Left = newNode; } else if( binary.Right == oldNode ) { binary.Right = newNode; } break; } case NodeType.Filter: { Filter filter = (Filter)parent; if( filter.Source == oldNode ) { filter.Source = newNode; } else if( filter.Constraint == oldNode ) { filter.Constraint = newNode; } break; } case NodeType.Function: { Function function = (Function)parent; for( int i = 0; i < function.Params.Length; i++ ) { if( function.Params[i] == oldNode ) { function.Params[i] = newNode; } } break; } case NodeType.Property: { Property property = (Property)parent; if( property.Source == oldNode ) { property.Source = newNode; } break; } case NodeType.TypeFilter: { TypeFilter filter = (TypeFilter)parent; if( filter.Source == oldNode ) { filter.Source = newNode; } break; } case NodeType.Unary: { Unary unary = (Unary)parent; if( unary.Operand == oldNode ) { unary.Operand = newNode; } break; } default: { throw new NotSupportedException("Node type '" + oldNode.Owner.NodeType + "' was not expected."); } } // remove old node's parent association oldNode.Parent = null; }
public Axis(Expression source, Expression constraint) : base(source, constraint) { this.IsDot = true; }
private bool IncreaseParentDepth(Expression node, object[] args) { if( node.NodeType == NodeType.Axis ) { Axis axis = (Axis)node; IncreaseParentDepth((Property)axis.Source, args); return false; } else if( node.NodeType == NodeType.Property ) { Property property = (Property)node; int parentCount = (int)args[0]; for( int i = 0; i < parentCount; i++ ) { property.Source = new Parent((Context)property.Source); } return false; } return true; }
public Axis(Expression source, Expression constraint, bool isDot) : this(source, constraint) { this.IsDot = isDot; }
private bool LinkPropertiesToMap(Expression node, object[] args) { if( node.NodeType == NodeType.Filter ) { Filter filter = (Filter)node; Expression.EnumNodesCallBack callback = new Expression.EnumNodesCallBack(this.LinkPropertiesToMap); if( args.Length > 0 ) { Expression.EnumNodes(filter.Source, callback, null, args); } // add the new filter to the font of the list object[] newArgs = new object[args.Length + 1]; Array.Copy(args, 0, newArgs, 1, args.Length); newArgs[0] = filter; Expression.EnumNodes(filter.Constraint, callback, null, newArgs); return false; } else if( node.NodeType == NodeType.Property ) { Property property = (Property)node; // determine the level of the filter to link int level = 0; if( property.Source.NodeType == NodeType.Parent ) { level = (property.Source as Parent).Level; // replace the parent source with a new context for linking property.Source = new Context(); } if( level >= args.Length ) { throw new OPathException("Property '" + property.Name + "' could not be associated to an entity."); } // set the context link to the source of the filter Context context = (Context)property.Source; context.Link = (args[level] as Filter).Source; // set the property info SetPropertyInfo(property); } return true; }
internal static Type GetResultType(Expression leftExpr, Expression rightExpr, BinaryOperator op) { throw new NotImplementedException(); }
private void Validate(Expression expr) { // validate every node in the tree Expression.EnumNodes(expr, new Expression.EnumNodesCallBack(this.ValidateNode)); }
public Unary(UnaryOperator op, Expression operand) { this.Operator = op; this.Operand = operand; }
private void FixTree(Expression expr) { //Debug.WriteLine("\n\nBefore FixTree:\n" + expr.ToXmlString()); //V2: This logic does not handle axis nodes in functions correctly. //V2: Need to crate a RecomposeFunction to move axis out from inside a function (or just don't let this happen - make them use []'s instead) //V2: Be sure to check that all properties in a function are of the same type. // Mixing relations in a function would be bad (e.g., LEN(BillAddress.Zip + ShipAddress.Zip) //V2: Expression.EnumNodes(expr, new Expression.EnumNodesCallBack(this.RecomposeFunction), null, (Function)null); //V2: Debug.WriteLine("\n\nAfter RecomposeFunction:\n" + expr.ToXmlString()); // note: using a post callback to handle binary node chains correctly (found this out the hard way) Expression.EnumNodes(expr, null, new Expression.EnumNodesCallBack(this.RecomposeBinary), null); //Debug.WriteLine("\n\nAfter RecomposeBinary:\n" + expr.ToXmlString()); Expression.EnumNodes(expr, new Expression.EnumNodesCallBack(this.ReplaceAxisCallback)); //Debug.WriteLine("\n\nAfter ReplaceAxisCallback:\n" + expr.ToXmlString()); Expression.EnumNodes(expr, new Expression.EnumNodesCallBack(this.LinkPropertiesToMap), null, new object[0]); //Debug.WriteLine("\n\nAfter LinkPropertiesToMap:\n" + expr.ToXmlString()); Expression.EnumNodes(expr, new Expression.EnumNodesCallBack(this.ReplaceAddWithConcatenation), null, new object[0]); //Debug.WriteLine("\n\nAfter ReplaceAddWithConcatenation:\n" + expr.ToXmlString()); //Debug.WriteLine("\n\nAfter FixTree:\n" + expr.ToXmlString()); }
// note: internal longhorn name found on www.winfx247.com private bool ReplaceAddWithConcatenation(Expression node, object[] noargs) { if( node.NodeType == NodeType.Binary ) { Binary binary = (Binary)node; if( binary.Operator == BinaryOperator.Addition && binary.Left.ValueType == typeof(string) && binary.Right.ValueType == typeof(string) ) { binary.Operator = BinaryOperator.Concatenation; } } return true; }