I have a list of products against which I need to create expression trees that can be persisted and later retrieved and executed. This is for a client-side calculation builder.
I am new to Expressions and although I have read a reasonable amount of documentation, the learning curve is a little steep here. What I want is to be able to accumulate PropertyExpression and Operand pairs to start with. Here is what I have so far and am not sure if I have structured it correctly.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace ConsoleApplication1
{
public enum ProductType { Perishable, Fixed, Miscellaneous }
public enum OperandType { Addition, Sunbtraction, Multiplication, Division }
public class Product
{
public string Name { get; set; }
public ProductType Type { get; set; }
public float Price { get; set; }
}
public class Configuration
{
public Dictionary<string, float> Dictionary { get; set; }
}
public class Operand
{
public OperandType Type { get; set; }
}
public class CalculationPair<TEntityType, TProperty>
where TEntityType: class
where TProperty: struct
{
public ValueTypeProperty<TEntityType, TProperty> Left { get; set; }
public Operand Operand { get; set; }
public ValueTypeProperty<TEntityType, TProperty> Right { get; set; }
// How to specify TResult as an [out] parameter?
public TResult Calculate<TResult> ()
where TResult: struct
{
TResult result = default(TResult);
if (this.Operand.Type == OperandType.Multiplication)
{
// How to execute the expression?
//result = this.Left * this.Right;
}
return (result);
}
}
public class ValueTypeProperty<TEntityType, TProperty>
where TEntityType: class
where TProperty: struct
{
public string Name { get; set; }
public Expression<Func<TEntityType, TProperty>> PropertyExpression { get; set; }
}
public class ProductPriceProperty:
ValueTypeProperty<Product, float>
{
}
public static class Program
{
public static void Main ()
{
Configuration config = new Configuration();
List<Product> products = new List<Product>();
config.Dictionary.Add("ExportFactor", 80);
config.Dictionary.Add("ChannelMargin", 100);
products.Add(new Product() { Name = "1", Type = ProductType.Fixed, Price = 10 });
products.Add(new Product() { Name = "2", Type = ProductType.Miscellaneous, Price = 20 });
products.Add(new Product() { Name = "3", Type = ProductType.Perishable, Price = 30 });
foreach (var product in products)
{
if (product.Type == ProductType.Fixed)
{
CalculationPair<Product, float> calculation = new CalculationPair<Product, float>()
{
Left = new ProductPriceProperty() { Name = "Product Price", PropertyExpression = (entity => entity.Price) },
Operand = new Operand() { Type = OperandType.Multiplication },
Right = new ProductPriceProperty() { Name = "ExportFactor", PropertyExpression = (entity => config.Dictionary ["ExportFactor"]) },
};
// Calculation needs to be persisted to be retrieved later.
// ???!
// Once calculation has been reconstruction from the persistence layer, it needs to be executed.
product.Price = calculation.Calculate<float>();
}
}
}
}
}
UPDATE: Here is what I am struggling with in order of priority:
How to execute the expression in the CalculationPair.Calculate<TReult>() function?
How to specify TResult as an [out] parameter in the CalculationPair.Calculate<TReult>() function?
How to persist the calculation Expression and retrieve it later?
As Jon said, you can use usual expression tress, or you can make closure anonymous method like in this code:
public class CalculationPair<TEntityType, TProperty>
where TEntityType : class
where TProperty : struct
{
// not sure that first three properties are needed here
public ValueTypeProperty<TEntityType, TProperty> Left { get; set; }
public Operand Operand { get; set; }
public ValueTypeProperty<TEntityType, TProperty> Right { get; set; }
// closure method
public Func<TEntityType, TProperty> Calculator { get; set; }
}
And here is part of Main method that uses it:
foreach (var product in products)
{
if (product.Type == ProductType.Fixed)
{
CalculationPair<Product, float> calculation = new CalculationPair<Product, float>()
{
Left = new ProductPriceProperty() { Name = "Product Price", PropertyExpression = (entity => entity.Price) },
Operand = new Operand() { Type = OperandType.Multiplication },
Right = new ProductPriceProperty() { Name = "ExportFactor", PropertyExpression = (entity => config.Dictionary["ExportFactor"]) },
// only this property is needed, and it will handle reference to config object in the closure
Calculator = (entity) => entity.Price * config.Dictionary["ExportFactor"]
};
// Once calculation has been reconstruction from the persistence layer, it needs to be executed.
product.Price = calculation.Calculator(product);
}
}
In that sample there is no Expression trees, just usual closure method.
UPDATE1
The problem with your expressions for Left and Right nodes, is that each this expression is linked to own entity parameter instead of ParameterExpression that we create and that will point to real entity object, so we need to rewrite old one to new one it with ExpressionVisitor . It used for parsing and rewriting needs.
Here is code of that rewriter:
public class ParameterRewriter : ExpressionVisitor
{
private readonly ParameterExpression _expToRewrite;
public ParameterRewriter(ParameterExpression expToRewrite)
{
this._expToRewrite = expToRewrite;
}
protected override Expression VisitParameter(ParameterExpression node)
{
// we just use type checking to understand that it's our parameter, and we replace it with new one
if (node.Type == this._expToRewrite.Type) return this._expToRewrite;
return base.VisitParameter(node);
}
}
And here is CalculationPair class:
public class CalculationPair<TEntityType, TProperty>
where TEntityType : class
where TProperty : struct
{
public ValueTypeProperty<TEntityType, TProperty> Left { get; set; }
public Operand Operand { get; set; }
public ValueTypeProperty<TEntityType, TProperty> Right { get; set; }
public TResult Calculate<TResult>(TEntityType entity)
where TResult : struct
{
TResult result = default(TResult);
var prop = Expression.Parameter(typeof(TEntityType), "param");
var visitor = new ParameterRewriter(prop);
var leftExp = visitor.Visit(Left.PropertyExpression.Body);
var rightExp = visitor.Visit(Right.PropertyExpression.Body);
Expression body;
switch (this.Operand.Type)
{
case OperandType.Multiplication:
body = Expression.Multiply(leftExp, rightExp);
break;
case OperandType.Addition:
body = Expression.Add(leftExp, rightExp);
break;
case OperandType.Division:
body = Expression.Divide(leftExp, rightExp);
break;
case OperandType.Sunbtraction:
body = Expression.Subtract(leftExp, rightExp);
break;
default:
throw new Exception("Unknown operand type");
}
var lambda = Expression.Lambda<Func<TEntityType, TResult>>(body, prop);
// compilation is long operation, so you might need to store this Func as property and don't compile it each time
var func = lambda.Compile();
result = func(entity);
return (result);
}
}
And usage is the same
product.Price = calculation.Calculate<float>(product);
Related
I am trying to modify MemberBinding expression using Expression visitor. But I am getting an error when I try to compile the modified expression which says:
'variable 'source' of type 'EFTest.Views.TestView' referenced from scope '', but it is not defined'
I would also like to mention that i do not get any error if I return MemberBinding as it is in VisitMemberBinding function. I have also made sure that the modified Expression is exactly same as the orignal one.
Final purpose of this work will be to block a specific column in all linq queries in EntityFramework Core. Please let me know if you have suggestions regarding that.
Sample codes are below
class Expre
{
static void Main(string[] args)
{
DataContext context = new DataContext();
var rs = context.Query<TestView>().Select(m => new MyModel()
{
Id = m.DocumentId,
});
Visitor visitor = new Visitor();
var ss = visitor.Modify(rs.Expression);
var CompiledLambdaExpression = Expression.Lambda(ss).Compile();
EntityQueryable<MyModel> queryAble = (EntityQueryable<MyModel>)CompiledLambdaExpression.DynamicInvoke();
var rss = Enumerable.ToList(queryAble);
}
}
public class Visitor : ExpressionVisitor
{
public MemberBinding MemberBinding;
public Expression Modify(Expression expression)
{
var r = Visit(expression);
return r;
}
protected override MemberBinding VisitMemberBinding(MemberBinding node)
{
Type targetType = typeof(MyModel);
Type sourceType = typeof(TestView);
PropertyInfo SourceProperty = sourceType.GetProperty("DocumentId");
PropertyInfo TargetProperty = targetType.GetProperty("Id");
ParameterExpression parameterExpression = Expression.Parameter(sourceType, "m");
MemberExpression propertyExpression = Expression.Property(parameterExpression, SourceProperty);
MemberBinding memberBinding = Expression.Bind(TargetProperty, propertyExpression);
return base.VisitMemberBinding(memberBinding);
}
protected override Expression VisitParameter(ParameterExpression node)
{
return base.VisitParameter(node);
}
}
public class MyModel
{
public Int32 Id { get; set; }
public string Name { get; set; }
}
public class TestView
{
public Int32 Code { get; set; }
public string LanguageCode { get; set; }
public Int32 DocumentId { get; set; }
}
I'm trying to create a method that wraps a LINQ Where call (on an IQueryable) for filtering on a specific field in a collection and am at a loss for how to make it work.
For example, I have a collection of Job objects similar to the following:
public class Job
{
public int Id { get; set; }
public int StatusId { get; set; }
}
public class StatusItem
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsAvailable { get; set; }
public static readonly StatusItem Canceled = new StatusItem() { Id = (int)StatusEnum.Canceled, Name = StatusEnum.Canceled.ToString(), IsAvailable = true };
public static readonly StatusItem Created = new StatusItem() { Id = (int)StatusEnum.Created, Name = StatusEnum.Created.ToString(), IsAvailable = true };
public static readonly StatusItem Open = new StatusItem() { Id = (int)StatusEnum.Open, Name = StatusEnum.Open.ToString(), IsAvailable = true };
public static readonly StatusItem Assigned = new StatusItem() { Id = (int)StatusEnum.Assigned, Name = StatusEnum.Assigned.ToString(), IsAvailable = false };
}
I'm hoping to have a service method that enforces filtering using only the system defined statuses, something like this:
IEnumerable<Job> GetAll(Expression<Func<StatusItem, bool>> statusFilter)
{
// Jobs is IQueryable<job>. How do I apply statusFilter to Job.StatusId?
return jobs.Where(/* some magic here? */);
}
With a call similar to:
return JobService.GetAll(s => s > StatusItem.Open && s < StatusItem.Assigned);
Edit: Been staring too long at this. Brain now mush. Attempted to fix previous errors
The simplest way to do it is to use Expression<Func<Job, bool>> instead of Expression<Func<StatusEnum, bool>>, which would let you write something like this:
IEnumerable<Job> GetAll(Expression<Func<Job, bool>> jobFilter)
{
return jobs.Where(jobFilter);
}
It also has the benefit of being more flexible in case you want to filter by something other than the status.
If you really want to use Expression<Func<StatusEnum, bool>>, it becomes more complex, because you need to rewrite the expression to make a Expression<Func<Job, bool>> from the Expression<Func<StatusEnum, bool>>. Here's a way to do it:
IEnumerable<Job> GetAll(Expression<Func<StatusEnum, bool>> statusFilter)
{
var job = Expression.Parameter(typeof(Job), "job");
var visitor = new ParameterReplacementVisitor(
statusFilter.Parameters[0],
Expression.Property(job, nameof(Job.StatusId)));
Expression<Func<Job, bool>> jobFilter =
Expression.Lambda<Func<Job, bool>>(
visitor.Visit(statusFilter.Body),
job);
return jobs.Where(jobFilter);
}
class ParameterReplacementVisitor : ExpressionVisitor
{
private readonly ParameterExpression _parameter;
private readonly Expression _replacement;
public ParameterReplacementVisitor(ParameterExpression parameter, Expression replacement)
{
_parameter = parameter;
_replacement = replacement;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (node == _parameter)
return _replacement;
return node;
}
}
I don't know if this is what you need, but check this:
jobs.Where(x => statusFilter.Compile().Invoke((StatusEnum)x.StatusId));
Also consider changing property StatusId to StatusEnum. Properties should also be public.
class Job
{
public int Id { get; set; }
public StatusEnum StatusId { get; set; }
}
With this kind of declaration casting to StatusEnum is not required:
jobs.Where(x => statusFilter.Compile().Invoke(x.StatusId));
I'm trying to dynamically convert this type of expression :
myDbSet.Select(x => new MyClass
{
IsSelected = x.ChildList.Any()
})
into :
myDbSet.Select(x => new
{
Item = x
IsSelected = x.ChildList.Any()
})
I would write an extension method that would take the strongly typed version and transform into the anonymous then create a new expression in order to do something like this :
myDbSet.Select(anonymousTransformedExpression).ToList().
.Select(newGeneratedExpression)
I would want this newGeneratedExpression to be:
myDbSet.Select(x => new
{
x.Item.IsSelected = x.IsSelected
return x.Item;
})
So basically tranform the return back into a strongly typed but with the "IsSelected" value applied.
Problem is I cant really find a starting point on how to do so..
EDIT
Alright I realised that the question wasn't so clear and I've come a little closer to the solution with one main problem so far. Here's what I got :
public static IEnumerable<TModel> SelectWithUnmapped<TModel> (this IQueryable<TModel> source, Expression<Func<TModel, object>> assigner) where TModel : class, new()
{
var anonymousType = typeof(AnonymousType<TModel>);
var expression = Expression.New(anonymousType);
var parameter = Expression.Parameter(typeof(TModel), "x");
//this part is hard coded to only take binding at position 0.. eventually will become dynamic
var originalBinding = ((MemberAssignment) ((MemberInitExpression) assigner.Body).Bindings[0]);
var originalExpression = originalBinding.Expression;
Expression conversion = Expression.Convert(originalExpression, typeof(object));
var bindings = new[]
{
Expression.Bind(anonymousType.GetProperty("Item"), parameter),
//this is hardcoded test
Expression.Bind(anonymousType.GetProperty("Property1"), conversion)
};
var body = Expression.MemberInit(expression, bindings);
var lambda = Expression.Lambda<Func<TModel, AnonymousType<TModel>>>(body, parameter);
var test = source.Select(lambda).ToList();
return source;
}
class AnonymousType<TModel>
{
public TModel Item { get; set; }
public object Property1 { get; set; }
public object Property2 { get; set; }
public object Property3 { get; set; }
public object Property4 { get; set; }
public object Property5 { get; set; }
public object Property6 { get; set; }
public object Property7 { get; set; }
public object Property8 { get; set; }
public object Property9 { get; set; }
public object Property10 { get; set; }
public object Property11 { get; set; }
public object Property12 { get; set; }
public object Property13 { get; set; }
public object Property14 { get; set; }
public object Property15 { get; set; }
public object Property16 { get; set; }
public object Property17 { get; set; }
public object Property18 { get; set; }
public object Property19 { get; set; }
public object Property20 { get; set; }
}
}
When I try to assign test, I get the following error : The parameter 'x' was not bound in the specified LINQ to Entities query expression.
This is due to my second binding using the original expression's binding expression. However both use the same parameter name of "x". How could I make sure my new binding actually know that x is the parameter from the new expression?
So basically so far I'm trying to take in an expression that looks like :
x => new Test
{
PropertyTest = x.Blah.Count()
}
into :
x => new AnonymousType<Test>(){
Item = x,
Property1 = x.Blah.Count()
}
I finally finished it up. I guess it just took patience and trial and error a lot. To help anyone who would like to do the same. I had to limit myself to a certain property count.. which can be added easily.
Here's the code :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
public static class IQueryableExtensions
{
public static IEnumerable<TModel> SelectAndAssign<TModel>(this IQueryable<TModel> source, Expression<Func<TModel, object>> assigner) where TModel : class, new()
{
//get typed body of original expression
var originalBody = (MemberInitExpression)assigner.Body;
//list to store the new bindings we're creating for new expression
var newExpressionBindings = new List<MemberBinding>();
var newExpressionReturnType = typeof(AnonymousType<TModel>);
//new param
var parameter = Expression.Parameter(typeof(TModel), "x");
//base binding
newExpressionBindings.Add(Expression.Bind(newExpressionReturnType.GetProperty("Item"), parameter));
//go through all the original expression's bindings
for (var i = 0; i < originalBody.Bindings.Count; i++)
{
var originalBinding = (MemberAssignment)originalBody.Bindings[i];
var originalExpression = originalBinding.Expression;
var memberType = originalBinding.Expression.Type;
//create delegate based on the member type
var originalLambdaDelegate = typeof(Func<,>).MakeGenericType(typeof(TModel), memberType);
//create lambda from that delegate
var originalLambda = Expression.Lambda(originalLambdaDelegate, originalExpression, assigner.Parameters[0]);
//create a AnonymousVar<MemberType> from the type of the member ( to please EF unable to assign bool to object directly )
//start with getting the generic type
var genericMemberType = typeof(AnonymousVar<>).MakeGenericType(memberType);
//then create teh delegate
var genericMemberTypeDelegate = typeof(Func<>).MakeGenericType(genericMemberType);
//Now create an expression with a binding for that object to assign its Property ( strongly typed now from the generic declaration )
var genericInstantiationExpression = Expression.New(genericMemberType);
//the binding.. using the original expression expression
var genericInstantiationBinding = Expression.Bind(genericMemberType.GetProperty("Property"), originalLambda.Body);
// create the body
var genericInstantiationBody = Expression.MemberInit(genericInstantiationExpression, genericInstantiationBinding);
//now we need to recreate a lambda for this
var newBindingExpression = Expression.Lambda(genericMemberTypeDelegate, genericInstantiationBody);
//Create the binding and add it to the new expression bindings
newExpressionBindings.Add(Expression.Bind(newExpressionReturnType.GetProperty("Property" + (i + 1)), newBindingExpression.Body));
}
//start creating the new expression
var expression = Expression.New(newExpressionReturnType);
//create new expression body with bindings
var body = Expression.MemberInit(expression, newExpressionBindings);
//The actual new expression lambda
var newLambda = Expression.Lambda<Func<TModel, AnonymousType<TModel>>>(body, parameter);
// replace old lambda param with new one
var replacer = new ParameterReplacer(assigner.Parameters[0], newLambda.Parameters[0]); // replace old lambda param with new
//new lambda with fixed params
newLambda = Expression.Lambda<Func<TModel, AnonymousType<TModel>>>(replacer.Visit(newLambda.Body), newLambda.Parameters[0]);
//now that we have all we need form the server, we materialize the list
var materialized = source.Select(newLambda).ToList();
//typed return parameter
var typedReturnParameter = Expression.Parameter(typeof(AnonymousType<TModel>), "x");
//Lets assign all those custom properties back into the original object type
var expressionLines = new List<Expression>();
for (var i = 0; i < originalBody.Bindings.Count; i++)
{
var originalBinding = (MemberAssignment)originalBody.Bindings[i];
var itemPropertyExpression = Expression.Property(typedReturnParameter, "Item");
var bindingPropertyExpression = Expression.Property(itemPropertyExpression, originalBinding.Member.Name);
var memberType = originalBinding.Expression.Type;
var valuePropertyExpression = Expression.Convert(Expression.Property(typedReturnParameter, "Property" + (i + 1)), typeof(AnonymousVar<>).MakeGenericType(memberType));
var memberValuePropertyExpression = Expression.Property(valuePropertyExpression, "Property");
var equalExpression = Expression.Assign(bindingPropertyExpression, memberValuePropertyExpression);
expressionLines.Add(equalExpression);
}
var returnTarget = Expression.Label(typeof(TModel));
expressionLines.Add(Expression.Return(returnTarget, Expression.Property(typedReturnParameter, "Item")));
expressionLines.Add(Expression.Label(returnTarget, Expression.Constant(null, typeof(TModel))));
var finalExpression = Expression.Block(expressionLines);
var typedReturnLambda = Expression.Lambda<Func<AnonymousType<TModel>, TModel>>(finalExpression, typedReturnParameter).Compile();
return materialized.Select(typedReturnLambda);
}
class AnonymousVar<TModel>
{
public TModel Property { get; set; }
}
class AnonymousType<TModel>
{
public TModel Item { get; set; }
public object Property1 { get; set; }
public object Property2 { get; set; }
public object Property3 { get; set; }
public object Property4 { get; set; }
public object Property5 { get; set; }
public object Property6 { get; set; }
public object Property7 { get; set; }
public object Property8 { get; set; }
public object Property9 { get; set; }
public object Property10 { get; set; }
public object Property11 { get; set; }
public object Property12 { get; set; }
public object Property13 { get; set; }
public object Property14 { get; set; }
public object Property15 { get; set; }
public object Property16 { get; set; }
public object Property17 { get; set; }
public object Property18 { get; set; }
public object Property19 { get; set; }
public object Property20 { get; set; }
}
class ParameterReplacer : ExpressionVisitor
{
private ParameterExpression from;
private ParameterExpression to;
public ParameterReplacer(ParameterExpression from, ParameterExpression to)
{
this.from = from;
this.to = to;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return base.VisitParameter(node == this.from ? this.to : node);
}
}
}
I'm trying to build expressions dynamically for a rules engine and things were going very well until I tried to allow nested types and properties to be specified as operands. Sample:
ExpressionBuilder
public Expression BuildExpression<T>(string propertyName, Enums.Operator ruleOperator, object value, ParameterExpression parameterExpression)
{
ExpressionType expressionType = new ExpressionType();
Expression body = parameterExpression;
foreach (var member in propertyName.Split('.'))
{
body = MemberExpression.Property(body, member);
}
var leftOperand = MemberExpression.PropertyOrField(body, propertyName);
var rightOperand = Expression.Constant(Convert.ChangeType(value, value.GetType()));
FieldInfo fieldInfo = expressionType.GetType().GetField(Enum.GetName(typeof(Enums.Operator), ruleOperator));
var expressionTypeValue = (ExpressionType)fieldInfo.GetValue(ruleOperator);
return CastBuildExpression(expressionTypeValue, value, leftOperand, rightOperand);
}
RuleEngine
public Func<T, bool>[] CombineRules<T>(Criterion[] criteria)
{
List<Func<T, bool>> list = new List<Func<T, bool>>();
foreach (var criterion in criteria)
{
ExpressionBuilder expressionBuilder = new ExpressionBuilder();
var param = Expression.Parameter(typeof (T));
Expression expression = expressionBuilder.BuildExpression<T>(criterion.PropertyName,
criterion.Operator_, criterion.Value, param);
Func<T, bool> func = Expression.Lambda<Func<T, bool>>(expression, param).Compile();
list.Add(func);
}
return list.ToArray();
}
Criterion
public class Criterion
{
private bool propertySet;
public string PropertyName { get; set; }
public Enums.Operator Operator_ { get; set; }
public object Value { get; set; }
MemberModel
public class MemberModel
{
public string UserName{ get; set; }
public PersonalDetailsModel PersonalDetails {get; set;}
}
PersonalDetailsModel
public class PersonalDetailsModel
{
public int PersonalDetailsId { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
public string Middlename { get; set; }
public string DateOfBirth { get; set; }
public string GenderType { get; set; }
public string SalutationType { get; set; }
}
The problem arises when I try to pass in nested properties as a left operand, i.e. PropertyName="PersonalDetails.FirstName" into ruleEngine.CombineRules(criteria.ToArray());
I get "'PersonalDetails.Firstname' not a member of type 'System.String'", despite it clearly being so. I've been stuck on this for a while now, any idea what might be causing it?
Any help would be greatly appreciated.
Here
Expression body = parameterExpression;
foreach (var member in propertyName.Split('.'))
{
body = MemberExpression.Property(body, member);
}
you already processed the property path, so this
var leftOperand = MemberExpression.PropertyOrField(body, propertyName);
make no sense and is the source of the exception. In your example, body contains something like p.PersonalDetails.FirstName (String) and the above line is trying to build something like p.PersonalDetails.FirstName.PersonalDetails.FirstName.
Use var leftOperand = body; instead.
You can shorten the whole property path processing by using simple
var leftOperand = propertyName.Split('.')
.Aggregate((Expression)parameterExpression, Expression.PropertyOrField);
I'm trying to dynamically combin lambda expressions. The code below will explain what I want. This is NOT a case of combining a=>b and b=>c to a=>c. Instead, I want to prevent code duplication by reusing conversion-expressions:
class User // convert from...
{
public string FirstName {get;set;}
public string LastName {get;set;}
}
class Person // convert to...
{
public string Name
}
public class UserTransaction
{
User FromUser {get;set;}
User ToUser {get;set;}
decimal Amount {get;set;}
}
public class PersonTransaction
{
Person FromPerson {get;set;}
Person ToPerson {get;set;}
bool IsPositive;
}
Expression<Func<User, Person>> ToPerson = u => new Person {Name = u.FirstName + " " + u.LastName};
Expression<Func<UserTransaction, PersonTransaction>> PersonTransaction = ut => new PersonTransaction {
FromPerson = FromUser.Compile()(ut.FromUser), // Actually, I do not want to compile
ToPerson = ToUser.Compile()(ut.FromUser), // (or double code)
IsPositive = ut.Amount > 0
}
In the example above, I already have an expression to convert a user to a person. I do not want to duplicate this code or compile it. I've tried using stripping out the "compile"-calls by manually editing the expression tree. I did not succeed. Has anybody tried something similar and succeed?
You can do some voodoo with ExpressionVisitor to rewrite your existing code to be fully inline; this:
detects the invoke
locates the Compile
resolves the originating lambda
swaps in all the parameter values directly
rebuilds the expression tree accordingly
Have fun!
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Reflection;
public class User // convert from...
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Person // convert to...
{
public string Name { get; set; }
}
public class UserTransaction
{
public User FromUser { get; set; }
public User ToUser { get; set; }
public decimal Amount { get; set; }
}
public class PersonTransaction
{
public Person FromPerson { get; set; }
public Person ToPerson { get; set; }
public bool IsPositive { get; set; }
}
static class Program
{
static void Main()
{
Expression<Func<User, Person>> ToPerson = u => new Person { Name = u.FirstName + " " + u.LastName };
Expression<Func<UserTransaction, PersonTransaction>> PersonTransaction = ut => new PersonTransaction
{
FromPerson = ToPerson.Compile()(ut.FromUser), // Actually, I do not want to compile
ToPerson = ToPerson.Compile()(ut.ToUser), // (or double code)
IsPositive = ut.Amount > 0
};
var visitor = new RemoveCompilationsExpressionVisitor();
var inlined = (Expression<Func<UserTransaction, PersonTransaction>>)visitor.Visit(PersonTransaction);
}
class ParameterSwapExpressionVisitor :ExpressionVisitor
{
private readonly Dictionary<ParameterExpression, Expression> swaps;
public ParameterSwapExpressionVisitor(Dictionary<ParameterExpression, Expression> swaps)
{
this.swaps = swaps;
}
protected override Expression VisitParameter(ParameterExpression node)
{
Expression result;
return swaps.TryGetValue(node, out result) ? result : base.VisitParameter(node);
}
}
class RemoveCompilationsExpressionVisitor : ExpressionVisitor
{
protected override Expression VisitInvocation(InvocationExpression node)
{
var lambda = TryGetInnerLambda(node.Expression);
if(lambda != null)
{
// this would be a partial solution, but we want to go further!
// return Expression.Invoke(lambda, node.Arguments);
var swaps = new Dictionary<ParameterExpression, Expression>();
for(int i = 0; i < lambda.Parameters.Count; i++)
{
swaps.Add(lambda.Parameters[i], node.Arguments[i]);
}
var visitor = new ParameterSwapExpressionVisitor(swaps);
return visitor.Visit(lambda.Body);
}
return base.VisitInvocation(node);
}
LambdaExpression TryGetInnerLambda(Expression node)
{
try
{
if(node.NodeType == ExpressionType.Call)
{
var mce = (MethodCallExpression)node;
var method = mce.Method;
if (method.Name == "Compile" && method.DeclaringType.IsGenericType && method.DeclaringType.GetGenericTypeDefinition()
== typeof(Expression<>))
{
object target;
if (TryGetLiteral(mce.Object, out target))
{
return (LambdaExpression)target;
}
}
}
}
catch (Exception ex)
{
/* best effort only */
Debug.WriteLine(ex);
}
return null;
}
static bool TryGetLiteral(Expression node, out object value)
{
value = null;
if (node == null) return false;
switch(node.NodeType)
{
case ExpressionType.Constant:
value = ((ConstantExpression)node).Value;
return true;
case ExpressionType.MemberAccess:
var me = (MemberExpression)node;
object target;
if (TryGetLiteral(me.Expression, out target))
{
switch (me.Member.MemberType)
{
case System.Reflection.MemberTypes.Field:
value = ((FieldInfo)me.Member).GetValue(target);
return true;
case MemberTypes.Property:
value = ((PropertyInfo)me.Member).GetValue(target, null);
return true;
}
}
break;
}
return false;
}
}
}