I have the following LINQ query:
var result = from person in dbContext.Person
select new
{
FirstName = person.FirstName,
LastName = person.LastName,
// I want to save this logic
JobCount = person.Jobs.Count(x => x.Completed)
};
}
To avoid repeating myself in other LINQ queries, I would like to make the JobCount lambda logic available for use in other queries.
I thought I might be able to use Func<Person, int>, like so:
public Func<Person, int> GetCompletedJobsForPerson = person => person.Jobs.Count(x => x.Completed);
var result = from person in dbContext.Person
select new
{
FirstName = person.FirstName,
LastName = person.LastName,
// Use Invoke to get amount
JobCount = GetCompletedJobsForPerson.Invoke(person)
};
}
PROBLEM STATEMENT: This fails because the method cannot be mapped to an SQL statement and causes a NotSupportedException
NotSupportedException was unhandled
The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.
How can I make the lambda reusable from multiple LINQ queries?
It can't be done in an easy way (and if it could be done easily, someone would have done it :-) )
What could be done is use the same trick of PredicateBuilder and create an AsExpandable that will replace some "tokens" (function calls) in your query with some other function calls. But I don't think it's worth the trouble. It is some hundred lines of code to do it "correctly".
The other problem is that the query would then need this special method to be called:
var result = (from person in dbContext.Person
select new
{
FirstName = person.FirstName,
LastName = person.LastName,
// Use Invoke to get amount
JobCount = GetCompletedJobsForPerson(person)
}).FixMethodCalls();
Ok... it was difficult, but doable:
// v0.11 Codename: Handle with Care+ (+ == Plus)
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property)]
public class ExpandableAttribute : Attribute
{
// Just to know the suffix to use :-)
public static readonly string ExpandableSuffix = "Expression";
}
// Replaces method and properties calls to "special" method calls that
// are Expression(s). These method/property calls can be used anywhere in
// the query (Select, Where, GroupBy, ...)
// Remember to use .AsExpandable2() somewhere in your query (it must be a
// "top level" part of the query):
// OK:
// var res1 = (from x in table select x).AsExpandable2();
// var res2 = table.AsExpandable().Where(x => true);
// var res3 = table.Where(x => true).AsExpandable2();
// var res4 = table.Where(x => true).AsExpandable2().Select(x => x);
// var res5 = table.Where(x => true).AsExpandable2().Select(x => x).AsExpandable2();
// Not OK:
// var res1 = table.Select(x => x.subtable.AsExpandable2());
// **Method calls**
// The methods to be expanded can be static or instance. There must be
// a corresponding **static* method with same name and suffix
// "Expression", that doesn't have parameters and returns an Expression
// with a certain signature.
// Static:
// var res2 = table.AsExpandable2().Select(x => MyClass.StaticMethod(1, x, 2, 3));
// There must be in the class MyClass
// public/private/protected static Expression<Func<int, MyClass, int, int, returnType(StaticMethod)>> StaticMethodExpression()
// Instance:
// var res1 = table.AsExpandable2().Select(x => x.InstanceMethod(1, 2, 3));
// There must be in the class x.GetType()
// public/private/protected static Expression<Func<x.GetType(), int, int, int, returnType(InstanceMethod)>> InstanceMethodExpression()
// Note that multiple "tables" can be passed as parameters:
// Static:
// var res3 = (from x in table1 from y in table2 select new { x, y }).AsExpandable2().Select(z => MyClass.StaticMethod(1, z.x, z.y, 2, 3));
// There must be in the class MyClass
// public/private/protected/internal static Expression<Func<int, x.GetType(), y.GetType(), int, int, returnType(StaticMethod)>> StaticMethodExpression()
// Instance:
// var res4 = (from x in table1 from y in table2 select new { x, y }).AsExpandable2().Select(z => z.x.StaticMethod(1, z.y, 2, 3));
// There must be in the class x.GetType()
// public/private/protected/internal static Expression<Func<x.GetType(), int, y.GetType(), int, int, returnType(StaticMethod)>> InstanceMethodExpression()
// **Properties**
// Same as with method calls, but with properties :-)
// (useful for things like FullName, where
// FullName = Name + ' ' + Surname)
// Remember that the *Expression property must be **static**!
// Static (not very useful :-) ):
// var res1 = table.AsExpandable2().Select(x => MyClass.StaticProperty);
// There must be in the class MyClass
// public/private/protected/internal static Expression<Func<MyClass.StaticProperty.GetType()>> StaticPropertyExpression { get; }
// Instance:
// var res2 = table.AsExpandable2().Select(x => x.InstanceProperty);
// There must be in the class x.GetType()
// public/private/protected/internal static Expression<Func<x.GetType(), x.InstanceProperty.GetType())>> InstancePropertyExpression { get; }
public static class MethodsPropertiesExpander
{
// Because AsExpandable() is already used by http://www.albahari.com/nutshell/linqkit.aspx
public static IQueryable<T> AsExpandable2<T>(this IQueryable<T> source)
{
if (source is MethodsPropertiesExpander<T>)
{
return source;
}
return new MethodsPropertiesExpander<T>(source);
}
}
public interface IMethodsPropertiesExpander
{
}
public class MethodsPropertiesExpander<T> : IOrderedQueryable<T>, IQueryProvider, IMethodsPropertiesExpander
{
public readonly IQueryable<T> Query;
public MethodsPropertiesExpander(IQueryable<T> query)
{
if (!(query is IMethodsPropertiesExpander))
{
Expression expression = MethodsPropertiesReplacer.Default.Visit(query.Expression);
Query = expression == query.Expression ? query : query.Provider.CreateQuery<T>(expression);
}
else
{
Query = query;
}
}
/* IQueryable<T> */
public IEnumerator<T> GetEnumerator()
{
return Query.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public Type ElementType
{
get { return Query.ElementType; }
}
public Expression Expression
{
get { return Query.Expression; }
}
public IQueryProvider Provider
{
get { return this; }
}
/* IQueryProvider */
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return new MethodsPropertiesExpander<TElement>(Query.Provider.CreateQuery<TElement>(expression));
}
public IQueryable CreateQuery(Expression expression)
{
Type iqueryableArgument = GetIQueryableTypeArgument(expression.Type);
MethodInfo createQueryImplMethod = typeof(MethodsPropertiesExpander<T>)
.GetMethod("CreateQuery", BindingFlags.Instance | BindingFlags.NonPublic)
.MakeGenericMethod(iqueryableArgument);
return (IQueryable)createQueryImplMethod.Invoke(this, new[] { expression });
}
public TResult Execute<TResult>(Expression expression)
{
if (!(Query.Provider is IMethodsPropertiesExpander))
{
// We want to expand it only once :-)
expression = MethodsPropertiesReplacer.Default.Visit(expression);
}
return Query.Provider.Execute<TResult>(expression);
}
public object Execute(Expression expression)
{
if (!(Query.Provider is IMethodsPropertiesExpander))
{
// We want to expand it only once :-)
expression = MethodsPropertiesReplacer.Default.Visit(expression);
}
return Query.Provider.Execute(expression);
}
/* Implementation methods */
/// <summary>
/// Gets the T of IQueryablelt;T>
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
protected static Type GetIQueryableTypeArgument(Type type)
{
IEnumerable<Type> interfaces = type.IsInterface ?
new[] { type }.Concat(type.GetInterfaces()) :
type.GetInterfaces();
Type argument = (from x in interfaces
where x.IsGenericType
let gt = x.GetGenericTypeDefinition()
where gt == typeof(IQueryable<>)
select x.GetGenericArguments()[0]).FirstOrDefault();
return argument;
}
/* Utility classes */
protected sealed class MethodsPropertiesReplacer : ExpressionVisitor
{
// Single instance is enough!
public static readonly MethodsPropertiesReplacer Default = new MethodsPropertiesReplacer();
private MethodsPropertiesReplacer()
{
}
protected override Expression VisitMember(MemberExpression node)
{
PropertyInfo property = node.Member as PropertyInfo;
MethodInfo getter;
// We handle only properties (that aren't indexers) that have
// a get
if (property != null && property.GetIndexParameters().Length == 0 && (getter = property.GetGetMethod(true)) != null)
{
// We work only on methods marked as [ExpandableAttribute]
var attribute = property.GetCustomAttributes(typeof(ExpandableAttribute), false).FirstOrDefault();
if (attribute != null)
{
string name = property.Name + ExpandableAttribute.ExpandableSuffix;
var property2 = property.DeclaringType.GetProperty(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, null, Type.EmptyTypes, null);
if (property2 == null || property2.GetGetMethod(true) == null)
{
if (property2 == null)
{
if (property.DeclaringType.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, null, Type.EmptyTypes, null) != null)
{
throw new NotSupportedException(string.Format("{0}.{1} isn't static!", property.DeclaringType.FullName, name));
}
throw new NotSupportedException(string.Format("{0}.{1} not found!", property.DeclaringType.FullName, name));
}
// property2.GetGetMethod(true) == null
throw new NotSupportedException(string.Format("{0}.{1} doesn't have a getter!", property.DeclaringType.FullName, name));
}
// Instance Parameters have the additional
// "parameter" of the declaring type
var argumentsPlusReturnTypes = getter.IsStatic ?
new[] { node.Type } :
new[] { property.DeclaringType, node.Type };
var funcType = typeof(Func<>).Assembly.GetType(string.Format("System.Func`{0}", argumentsPlusReturnTypes.Length));
var returnType = typeof(Expression<>).MakeGenericType(funcType.MakeGenericType(argumentsPlusReturnTypes));
if (property2.PropertyType != returnType)
{
throw new NotSupportedException(string.Format("{0}.{1} has wrong return type!", property.DeclaringType.FullName, name));
}
var expression = (LambdaExpression)property2.GetValue(null, null);
// Instance Members have the additional "parameter"
// of the declaring type
var arguments2 = getter.IsStatic ? new Expression[0] : new[] { node.Expression };
var replacer = new SimpleExpressionReplacer(expression.Parameters, arguments2);
var body = replacer.Visit(expression.Body);
return this.Visit(body);
}
}
return base.VisitMember(node);
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
MethodInfo method = node.Method;
// We work only on methods marked as [ExpandableAttribute]
var attribute = method.GetCustomAttributes(typeof(ExpandableAttribute), false).FirstOrDefault();
if (attribute != null)
{
string name = method.Name + ExpandableAttribute.ExpandableSuffix;
var method2 = method.DeclaringType.GetMethod(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
if (method2 == null)
{
if (method.DeclaringType.GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null) != null)
{
throw new NotSupportedException(string.Format("{0}.{1} isn't static!", method.DeclaringType.FullName, name));
}
throw new NotSupportedException(string.Format("{0}.{1} not found!", method.DeclaringType.FullName, name));
}
// Instance methods have the additional "parameter" of
// the declaring type
var argumentsPlusReturnTypes = method.IsStatic ?
node.Arguments.Select(x => x.Type).Concat(new[] { node.Type }).ToArray() :
new[] { method.DeclaringType }.Concat(node.Arguments.Select(x => x.Type)).Concat(new[] { node.Type }).ToArray();
var funcType = typeof(Func<>).Assembly.GetType(string.Format("System.Func`{0}", argumentsPlusReturnTypes.Length));
var returnType = typeof(Expression<>).MakeGenericType(funcType.MakeGenericType(argumentsPlusReturnTypes));
if (method2.ReturnType != returnType)
{
throw new NotSupportedException(string.Format("{0}.{1} has wrong return type!", method.DeclaringType.FullName, name));
}
var expression = (LambdaExpression)method2.Invoke(null, null);
// Instance methods have the additional "parameter" of
// the declaring type
var arguments2 = method.IsStatic ? node.Arguments : new[] { node.Object }.Concat(node.Arguments);
var replacer = new SimpleExpressionReplacer(expression.Parameters, arguments2);
var body = replacer.Visit(expression.Body);
return this.Visit(body);
}
return base.VisitMethodCall(node);
}
}
}
// A simple expression visitor to replace some nodes of an expression
// with some other nodes
public class SimpleExpressionReplacer : ExpressionVisitor
{
public readonly Dictionary<Expression, Expression> Replaces;
public SimpleExpressionReplacer(Dictionary<Expression, Expression> replaces)
{
Replaces = replaces;
}
public SimpleExpressionReplacer(IEnumerable<Expression> from, IEnumerable<Expression> to)
{
Replaces = new Dictionary<Expression, Expression>();
using (var enu1 = from.GetEnumerator())
using (var enu2 = to.GetEnumerator())
{
while (true)
{
bool res1 = enu1.MoveNext();
bool res2 = enu2.MoveNext();
if (!res1 || !res2)
{
if (!res1 && !res2)
{
break;
}
if (!res1)
{
throw new ArgumentException("from shorter");
}
throw new ArgumentException("to shorter");
}
Replaces.Add(enu1.Current, enu2.Current);
}
}
}
public SimpleExpressionReplacer(Expression from, Expression to)
{
Replaces = new Dictionary<Expression, Expression> { { from, to } };
}
public override Expression Visit(Expression node)
{
Expression to;
if (node != null && Replaces.TryGetValue(node, out to))
{
return base.Visit(to);
}
return base.Visit(node);
}
}
I've added a bonus: you can even "expand" special properties. The instructions on how to use it are in the big comment at the beginning. Now I'll give you some examples:
// Generated by EF
public partial class MyClass
{
public int ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public ICollection<MyInnerClass> MyInnerClass;
}
// Written by you (remember the partial!)
public partial class MyClass
{
[Expandable]
public int CountMyInnerClass()
{
// Not necessary to implement, unless you want to use it C#-side
throw new NotImplementedException();
}
[Expandable]
public int CountMyInnerClassPlus(int num)
{
// Not necessary to implement, unless you want to use it C#-side
throw new NotImplementedException();
}
[Expandable]
public int CountMyInnerClassProperty
{
get
{
// Not necessary to implement, unless you want to use it C#-side
throw new NotImplementedException();
}
}
[Expandable]
public string FullName
{
get
{
// Not necessary to implement, unless you want to use it C#-side
return Name + " " + Surname;
}
}
protected static Expression<Func<MyClass, int>> CountMyInnerClassExpression()
{
return x => x.MyInnerClass.Count();
}
protected static Expression<Func<MyClass, int, int>> CountMyInnerClassPlusExpression()
{
return (x, num) => x.MyInnerClass.Count() + num;
}
protected static Expression<Func<MyClass, int>> CountMyInnerClassPropertyExpression
{
get
{
return x => x.MyInnerClass.Count();
}
}
protected static Expression<Func<MyClass, string>> FullNameExpression
{
get
{
return x => x.Name + " " + x.Surname;
}
}
}
and then, in some other class class (perhaps the ones of the query):
[Expandable]
public static int LocalCountMyInnerClassPlus(MyClass x, int num)
{
// Not necessary to implement, unless you want to use it C#-side
throw new NotImplementedException();
}
public static Expression<Func<MyClass, int, int>> LocalCountMyInnerClassPlusExpression()
{
return (x, num) => x.MyInnerClass.Count() + num;
}
and then
var query = (from x in db.MyClasses
select new
{
x.ID,
x.FullName,
Count1 = x.CountMyInnerClass(),
Count2 = x.CountMyInnerClassPlus(5),
Count3 = x.CountMyInnerClassProperty,
Count4 = LocalCountMyInnerClassPlus(x, 10),
}).AsExpandable2().ToList();
and it just works :-)
Related
I found this interesting article Reflection Performance - Create Delegate (Properties C#)
the described approach works great for properties. So I tried to to make it work for Methods, too, but without success.
Classes / Properties / Methods
public class bmecatContent
{
private bmecatHeader header;
private bmecatCatalog catalog;
private List<bmecatFieldValue> fieldValueList;
public bmecatContent()
{
header = new bmecatHeader();
catalog = new bmecatCatalog();
}
public string DeclarationVersion { get; set; }
public string DeclarationEncoding { get; set; }
public string BmecatVersion { get; set; }
public bmecatHeader Header
{ get { return header; } }
public bmecatCatalog Catalog
{ get { return catalog; } }
}
public class bmecatCatalog
{
private List<bmecatCatalogGroupSystem> catalogGroupSystem;
private List<bmecatClassificationSystem> classificationSystem;
private List<bmecatProduct> products;
private List<bmecatProductToCataloggroupMap> productToCataloggroupMap;
public bmecatCatalog()
{
catalogGroupSystem = new List<bmecatCatalogGroupSystem>();
classificationSystem = new List<bmecatClassificationSystem>();
products = new List<bmecatProduct>();
productToCataloggroupMap = new List<bmecatProductToCataloggroupMap>();
}
public List<bmecatClassificationSystem> Classification_System
{ get { return classificationSystem; } }
public List<bmecatCatalogGroupSystem> Catalog_Group_System
{ get { return catalogGroupSystem; } }
public List<bmecatProduct> Products
{ get { return products; } }
public List<bmecatProductToCataloggroupMap> Product_To_Cataloggroup_Map
{ get { return productToCataloggroupMap; } }
public bmecatProduct GetProductByInernationalPid(string Pid)
{
// linq
var query = from prodItem in products
from innerList in prodItem.Product_Details.International_PID
where innerList.PID == Pid
select prodItem;
return query.FirstOrDefault();
}
}
my current Approach looks like:
// Properties
public static Func<object, object> BuildGetAccessor(MethodInfo method)
{
var obj = Expression.Parameter(typeof(object), "o");
Expression<Func<object, object>> expr =
Expression.Lambda<Func<object, object>>(
Expression.Convert(
Expression.Call(
Expression.Convert(obj, method.DeclaringType),
method),
typeof(object)),
obj);
return expr.Compile();
}
// Methods (with string Parameter)
public static Func<object, string, object> BuildMethodAccessor(MethodInfo method)
{
var obj = Expression.Parameter(typeof(object), "o");
var strParam = Expression.Parameter(typeof(string), "strParam");
//var param = method.GetParameters().Select(p => Expression.Parameter(p.ParameterType, p.Name)).FirstOrDefault();
var param = Expression.Convert(strParam, method.GetParameters().First().ParameterType);
Expression<Func<object, string, object>> expr =
Expression.Lambda<Func<object, string, object>>(
Expression.Convert(Expression.Call(Expression.Convert(obj, method.DeclaringType), method, param),
typeof(object)),
obj);
return expr.Compile();
}
this code generates messages, that for the lambda-declaration a wrong number of Parameters was used.
thx a lot for your help!
// Update
this is my "work in progress" part when it Comes to creating & using the delegates:
bmecatParser parser = new bmecatParser();
// parser contains Property BmecatContent of type bmecatContent
// BmecatContent contains all properties and Methods I Need to Access at runtime
// e.g. BmecatContent.Catalog, BmecatContent.Catalog.GetProductByInernationalPid(string Pid)
// gets instance of main-class
var property = parser.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Single(obj => obj.Name == "BmecatContent");
var access = Extensions.BuildGetAccessor(property.GetGetMethod());
var resultBmecatContent = access(parser);
// gets instance of class that holds method
property = resultBmecatContent.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Single(obj => obj.Name == "Catalog");
access = Extensions.BuildGetAccessor(property.GetGetMethod());
var resultCatalog = access(resultBmecatContent);
// here I try to get value from method that has 1 Parameter (string)
var method = resultCatalog.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance).Single(obj => obj.Name == "GetProductByInernationalPid");
var accessProd = Extensions.BuildMethodAccessor(method);
var resultProduct = accessProd(resultCatalog, "4317784548366");
the idea behind this is to parse given classes + properties structure, where user provides propertynames / methodnames within mappinginstructions.
Check this fiddle for the error: https://dotnetfiddle.net/tlz4Qg
I have two classes like this:
public class ParentType{
private ParentType(){}
public int Id { get; protected set; }
public SubType Sub { get; protected set; }
}
public class SubType{
private SubType(){}
public int Id { get; protected set; }
}
I am going to transform a multilevel anonymous expression to a multilevel non-anonymous expression. To achieve this I have an expression like the below-mentioned one:
x => new
{
x.Id,
Sub = new
{
x.Sub.Id
}
}
To achieve that goal, I have transformed it to an expression like this:
x => new ParentType()
{
Id = x.Id,
Sub = new SubType()
{
Id = x.Sub.Id
},
}
But when I call Compile() method, I get the following error:
Variable 'x.Sub' of type 'SubType' referenced from scope '' but it is not defined
Here is my visitor class:
public class ReturnTypeVisitor<TIn, TOut> : ExpressionVisitor
{
private readonly Type funcToReplace;
private ParameterExpression currentParameter;
private ParameterExpression defaultParameter;
private Type currentType;
public ReturnTypeVisitor() => funcToReplace = typeof(Func<,>).MakeGenericType(typeof(TIn), typeof(object));
protected override Expression VisitNew(NewExpression node)
{
if (!node.Type.IsAnonymousType())
return base.VisitNew(node);
if (currentType == null)
currentType = typeof(TOut);
var ctor = currentType.GetPrivateConstructor();
if (ctor == null)
return base.VisitNew(node);
NewExpression expr = Expression.New(ctor);
IEnumerable<MemberBinding> bindings = node.Members.Select(x =>
{
var mi = currentType.GetProperty(x.Name);
//if the type is anonymous then I need to transform its body
if (((PropertyInfo)x).PropertyType.IsAnonymousType())
{
//This section is became unnecessary complex!
//
var property = (PropertyInfo)x;
var parentType = currentType;
var parentParameter = currentParameter;
currentType = currentType.GetProperty(property.Name).PropertyType;
currentParameter = Expression.Parameter(currentType, currentParameter.Name + "." + property.Name);
//I pass the inner anonymous expression to VisitNew and make the non-anonymous expression from it
var xOriginal = VisitNew(node.Arguments.FirstOrDefault(a => a.Type == property.PropertyType) as NewExpression);
currentType = parentType;
currentParameter = parentParameter;
return (MemberBinding)Expression.Bind(mi, xOriginal);
}
else//if type is not anonymous then simple find the property and make the memberbinding
{
var xOriginal = Expression.PropertyOrField(currentParameter, x.Name);
return (MemberBinding)Expression.Bind(mi, xOriginal);
}
});
return Expression.MemberInit(expr, bindings);
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
if (typeof(T) != funcToReplace)
return base.VisitLambda(node);
defaultParameter = node.Parameters.First();
currentParameter = defaultParameter;
var body = Visit(node.Body);
return Expression.Lambda<Func<TIn, TOut>>(body, currentParameter);
}
}
And use it like this:
public static Expression<Func<Tin, Tout>> Transform<Tin, Tout>(this Expression<Func<Tin, object>> source)
{
var visitor = new ReturnTypeVisitor<Tin, Tout>();
var result = (Expression<Func<Tin, Tout>>)visitor.Visit(source);
return result;// result.Compile() throw the aforementioned error
}
Here is the extension methods used inside my Visitor class:
public static ConstructorInfo GetPrivateConstructor(this Type type) =>
type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
// this hack taken from https://stackoverflow.com/a/2483054/4685428
// and https://stackoverflow.com/a/1650895/4685428
public static bool IsAnonymousType(this Type type)
{
var markedWithAttribute = type.GetCustomAttributes(typeof(CompilerGeneratedAttribute), inherit: false).Any();
var typeName = type.Name;
return markedWithAttribute
&& (typeName.StartsWith("<>") || type.Name.StartsWith("VB$"))
&& typeName.Contains("AnonymousType");
}
Update
Here is the .Net Fiddle link for the problem: https://dotnetfiddle.net/tlz4Qg
Update
I have removed the extra codes that seems to be out of the problem scope.
The cause of the problem in question is the line
currentParameter = Expression.Parameter(currentType, currentParameter.Name + "." + property.Name);
inside VisitNew method.
With your sample, it creates a new parameter called "x.Sub", so if we mark the parameters with {}, the actual result is
Sub = new SubType()
{
Id = {x.Sub}.Id
},
rather than expected
Sub = new SubType()
{
Id = {x}.Sub.Id
},
In general you should not create new ParameterExpressions except when remapping lambda expressions. And all newly created parameters should be passed to Expression.Lambda call, otherwise they will be considered "not defined".
Also please note that the visitor code has some assumptions which doesn't hold in general. For instance
var xOriginal = Expression.PropertyOrField(currentParameter, x.Name);
won't work inside nested new, because there you need access to a member of the x parameter like x.Sub.Id rather than x.Id. Which is basically the corersonding expression from NewExpression.Arguments.
Processing nested lambda expressions or collection type members and LINQ methods with expression visitors requires much more state control. While converting simple nested anonymous new expression like in the sample does not even need a ExpressionVisitor, because it could easily be achieved with simple recursive method like this:
public static Expression<Func<Tin, Tout>> Transform<Tin, Tout>(this Expression<Func<Tin, object>> source)
{
return Expression.Lambda<Func<Tin, Tout>>(
Transform(source.Body, typeof(Tout)),
source.Parameters);
}
static Expression Transform(Expression source, Type type)
{
if (source.Type != type && source is NewExpression newExpr && newExpr.Members.Count > 0)
{
return Expression.MemberInit(Expression.New(type), newExpr.Members
.Select(m => type.GetProperty(m.Name))
.Zip(newExpr.Arguments, (m, e) => Expression.Bind(m, Transform(e, m.PropertyType))));
}
return source;
}
I'm trying to add an additional method call to my expression tree, but I'm slightly confused how to implement it. Here is what I'm currently working with:
private static Action<object, object> CreateSetter(SetterInfo info)
{
var propertyInfo = info.Type.GetProperty(info.Name, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
if (propertyInfo == null)
return (s, v) => { };
var objParameter = Expression.Parameter(typeof(object));
var valueParameter = Expression.Parameter(typeof(object));
//This is the method call I'm trying to add
if (info.Name[0] == 'G' && info.Type.Name == TaxDataConstant.ParcelFeat)
{
var convertParcelFeatCall = Expression.Call(ConvertParcelFeatMethod, valueParameter, Expression.Constant(info.Name));
}
var changeTypeCall = Expression.Call(ChangeTypeMethod, valueParameter, Expression.Constant(propertyInfo.PropertyType));
var objCast = Expression.Convert(objParameter, info.Type);
var valueCast = Expression.Convert(changeTypeCall, propertyInfo.PropertyType);
var property = Expression.Property(objCast, propertyInfo);
var assignment = Expression.Assign(property, valueCast);
var lambda = Expression.Lambda<Action<object, object>>(assignment, objParameter, valueParameter);
return lambda.Compile();
}
What I want to happen is:
1) If the name of the type in my SetterInfo object is ParcelFeat and the Properties name begins with 'G' I want to call ConvertParcelFeat on valueParameter and then call ChangeType on the return.
2) If the name of the type is anything other than ParcelFeat call Changetype as normal with out the additional steps
What I'm confused is how to build the conditional. I'm assuming the way I'm doing it in the above code is wrong and I need to use something like Expression.IfThen() to to build the conditional. I'm also unsure how I can chain the method calls like I want.
You do not need in Expression.IfThen because for each specific SetterInfo you combine exactly one specific lambda instance.
Just plug in convertParcelFeatCall in proper place of your ExpressionTree and all should work just fine.
So your code might look like:
class Program
{
static void Main(string[] args)
{
var program = new Program();
var weightLambda = program.DoInternal("Weight").ToString()
== "(Param_0, Param_1) => (Convert(Param_0).Weight = Convert(ChangeType(Param_1, System.Object)))";
var goodiesLambda = program.DoInternal("Goodies").ToString()
== "(Param_0, Param_1) => (Convert(Param_0).Goodies = Convert(ChangeType(Param_1, ConvertParcelFeat(Param_1, \"Goodies\"))))";
Console.WriteLine("WeightLambda is Ok: {0}\nGoodiesLambda is Ok: {1}", weightLambda, goodiesLambda);
}
public Action<Object, Object> Do(string name)
{
return DoInternal(name).Compile();
}
public Expression<Action<object, object>> DoInternal(string name)
{
var info = new {Name = name, Type = typeof(Program)};
var propertyInfo = info.Type.GetProperty(info.Name, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
var objParameter = Expression.Parameter(typeof(object));
var valueParameter = Expression.Parameter(typeof(object));
//This is the method call I'm trying to add
Expression toBeTypeChanged;
if (info.Name[0] == 'G' && info.Type.Name == "Program")
{
toBeTypeChanged = Expression.Call(ConvertParcelFeatMethod, valueParameter, Expression.Constant(info.Name));
}
else
{
toBeTypeChanged = Expression.Constant(propertyInfo.PropertyType);
}
var changeTypeCall = Expression.Call(ChangeTypeMethod, valueParameter, toBeTypeChanged);
var objCast = Expression.Convert(objParameter, info.Type);
var valueCast = Expression.Convert(changeTypeCall, propertyInfo.PropertyType);
var property = Expression.Property(objCast, propertyInfo);
var assignment = Expression.Assign(property, valueCast);
return Expression.Lambda<Action<object, object>>(assignment, objParameter, valueParameter);
}
public object Weight { get; set; }
public object Goodies { get; set; }
public static object ChangeType(object valueParameter, object constant)
{
return null;
}
public static object ConvertParcelFeat(object valueParameter, object constant)
{
return null;
}
public MethodInfo ConvertParcelFeatMethod
{
get { return typeof(Program).GetMethod("ConvertParcelFeat"); }
}
public MethodInfo ChangeTypeMethod
{
get { return typeof(Program).GetMethod("ChangeType"); }
}
}
I was looking at another question that stated how Expression can be significantly faster than reflection since it can be precompiled to IL.
I'm not really sure how to use it though. Here is some code used in a base class for a Value Oject (in the DDD sense) where the basic idea is to use the values of all public properties to determine equality, which it gets via reflection. By using this base class, you needn't implement equality for subclasses that have Value Object.
protected virtual bool HasSameObjectSignatureAs(BaseObject compareTo)
{
var signatureProperties = GetType().GetProperties();
foreach (var property in signatureProperties)
{
var valueOfThisObject = property.GetValue(this, null);
var valueOfCompareTo = property.GetValue(compareTo, null);
if (valueOfThisObject == null && valueOfCompareTo == null) {
continue;
}
if ((valueOfThisObject == null ^ valueOfCompareTo == null) ||
(!valueOfThisObject.Equals(valueOfCompareTo))) {
return false;
}
}
How would this code be re-written using Expression?
Cheers,
Berryl
You can do this by building up a nested And expression for each property you want to compare:
protected Expression<Func<BaseObject, bool>> CreatePropertiesEqualExpression(BaseObject other)
{
if (! other.GetType().IsSubclassOf(this.GetType())) throw new ArgumentException();
var properties = this.GetType().GetProperties();
Expression trueExpr = Expression.Constant(true);
Expression thisExpr = Expression.Constant(this);
ParameterExpression paramExpr = Expression.Parameter(typeof(BaseObject), "compareTo");
Expression downCastExpr = Expression.Convert(paramExpr, other.GetType());
MethodInfo eqMethod = typeof(object).GetMethod("Equals", BindingFlags.Public | BindingFlags.Static);
Expression propCompExpr = properties.Aggregate(trueExpr, (expr, prop) =>
{
Expression thisPropExpr = Expression.Property(thisExpr, prop);
Expression compPropExpr = Expression.Property(downCastExpr, prop);
Expression eqExpr = Expression.Call(null, eqMethod, Expression.Convert(thisPropExpr, typeof(object)), Expression.Convert(compPropExpr, typeof(object)));
return Expression.And(expr, eqExpr);
});
return Expression.Lambda<Func<BaseObject, bool>>(propCompExpr, paramExpr);
}
You can then use it like this:
public class SubObject : BaseObject
{
public int Id { get; set; }
public string Name { get; set; }
private Func<BaseObject, bool> eqFunc;
public bool IsEqualTo(SubObject other)
{
if(this.eqFunc == null)
{
var compExpr = this.CreatePropertiesEqualExpression(other);
this.eqFunc = compExpr.Compile();
}
return this.eqFunc(other);
}
}
I'm looking for a way to do following dynamically:
var q = context.Subscription
.Include("Client")
.Include("Invoices")
Where(s=>s.Client.Invoices.Count(i=>i.InvoiceID == SomeInt) > 0);
I would like to build expression dynamically for the left side:
Expression left = s => s.Client.Invoices.Count(i => i.InvoiceID == iSomeVar); //!
Expression right = Expression.Constant(0);
var binary = Expression.GreaterThan(left, right);
Thanks!
UPDATED NOTES:
Please note: The end result must be
Expression<Func<T, bool>>
Simple version:
// To give clear idea, all what I want to achieve is to determine
// whether specific record exists in reference table using known Path.
// Ultimately I want to extend following function (which works great by
// the way, but for simple operations)
static Expression CreateExpression<T>(string propertyPath,
object propertyValue,
ParameterExpression parameterExpression)
{
PropertyInfo property = typeof(T).GetProperty(propertyName);
MemberExpression left = Expression.Property(parameterExpression, property);
ConstantExpression right = Expression.Constant(0);
BinaryExpression binary = Expression.GreaterThan(left, right);
return binary;
}
// And I want to call this function and get result exactly as shown below:
Expression result =
CreateExpression<Subscription>("Client.Invoices.InvoiceID",
theID,
valueSelector.Parameters.Single());
// Where result will be:
// t => t.Client.Invoices.Count(i => i.InvoiceID == theID) > 0;
Extended version:
// 1) I'm using Silverlight 4, EF, RIA.
// 2) At the server side I have a function GetSubscriptionsByCriteria
// that looks about it:
public IQueryable<Subscription> GetSubscriptionsByCriteria(...)
{
var query = this.ObjectContext.Subscriptions.Include("Client")
.Include("Client.Invoices");
var criteria = BuildCriteria(...);
return query.Where(criteria)
}
// 3) BuildCriteria(...) function gathers Expressions and
// aggregates it into the single Expression with different
// AND/OR conditions, something like that:
public Expression<Func<Subscription, bool>> BuildCriteria(
List<SearchFilter> filters,
Expression<Func<Subscription, bool>> valueSelector)
{
List<Expression> filterExpressions = new List<Expression>();
...
Expression expr = CreateExpression<Subscription>(
sfItem.DBPropertyName,
sfItem.DBPropertyValue,
paramExpression,
sf.SearchCondition);
filterExpressions.Add(expr);
...
var filterBody =
filterExpressions.Aggregate<Expression>(
(accumulate, equal) => Expression.And(accumulate, equal));
return Expression
.Lambda<Func<Subscription, bool>>(filterBody, paramExpression);
}
// 4) Here is the simplified version of CreateExpression function:
static Expression CreateExpression<T>(string propertyName,
object propertyValue,
ParameterExpression paramExpression)
{
PropertyInfo property = typeof(T).GetProperty(propertyName);
ConstantExpression right = Expression.Constant(0);
MemberExpression left = Expression.Property(paramExpression, property);
return binary = Expression.Equals(left, right);
}
So, I hope it's clear now why do I need Expression for the left side in my original post. Trying to make this as DRY as possible.
P.S. Not to make it too confusing here is why I think I need to do ёExpression.Call(...)ё:
When I run following code and break it to see DebugView I notice this:
Expression<Func<Subscription, bool>> predicate =
t => t.Client.Invoices.Count(i => i.InvoiceID == 5) > 0;
BinaryExpression eq = (BinaryExpression)predicate.Body;
var left = eq.Left; // <-- See DEBUG VIEW
var right = eq.Right;
// DEBUG VIEW:
// Arguments: Count = 2
// [0] = {t.Client.Invoices}
// [1] = {i => (i.InvoiceID == 5)}
// DebugView: ".Call System.Linq.Enumerable.Count(
// ($t.Client).ClientInvoices,
// .Lambda#Lambda1<System.Func`2[SLApp.Web.Invoice,System.Boolean]>)
// .Lambda#Lambda1<System.Func`2[SLApp.Web.Invoice,System.Boolean]>
// (SLApp.Web.ClientInvoice $i){ $i.ClientInvoiceID == 5 }"
Here's a working program that does what I think you'd like. It defines a function that takes a path to an integer property inside a collection, and an integer value. It then checks whether or not that collection has Count > 0 of that value.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
using System.Collections;
namespace Test_Console
{
public class Subscription
{
public int Id { get; set; }
public Client Client { get; set; }
}
public class Client
{
public ICollection<Invoice> Invoices { get; set; }
}
public class Invoice
{
public int Id { get; set; }
}
class Program
{
static void Main(string[] args)
{
var subscriptions = new[]
{
new Subscription { Id = 1, Client = new Client { Invoices = new [] {
new Invoice { Id = 1 },
new Invoice { Id = 2 },
new Invoice { Id = 5 }
} } },
new Subscription { Id = 2, Client = new Client { Invoices = new [] {
new Invoice { Id = 4 },
new Invoice { Id = 5 },
new Invoice { Id = 5 }
} } },
new Subscription { Id = 3, Client = new Client { Invoices = new Invoice[] {
} } },
};
var propertyPath = "Client.Invoices.Id";
Console.WriteLine("What Id would you like to check " + propertyPath + " for?");
var propertyValue = int.Parse(Console.ReadLine());
var whereNumberOne = makeWhere<Subscription>(propertyPath, propertyValue);
Console.WriteLine("The following Subscription objects match:");
foreach (var s in subscriptions.Where(whereNumberOne).ToList())
{
Console.WriteLine("Id: " + s.Id);
}
}
private static Func<T, bool> makeWhere<T>(string propertyPath, int propertyValue)
{
string[] navigateProperties = propertyPath.Split('.');
var currentType = typeof(T);
var functoidChain = new List<Func<object, object>>();
functoidChain.Add(x => x); // identity function starts the chain
foreach (var nextProperty in navigateProperties)
{
// must be inside loop so the closer on the functoids works properly
PropertyInfo nextPropertyInfo;
if (currentType.IsGenericType
&& currentType.GetGenericTypeDefinition().GetInterfaces().Contains(typeof(IEnumerable)))
{
nextPropertyInfo = currentType.GetGenericArguments()[0].GetProperty(nextProperty);
functoidChain.Add(x =>
((IEnumerable<object>)x)
.Count(y => (int)nextPropertyInfo.GetValue(y, null) == propertyValue)
);
}
else
{
nextPropertyInfo = currentType.GetProperty(nextProperty);
functoidChain.Add(x => nextPropertyInfo.GetValue(x, null));
}
currentType = nextPropertyInfo.PropertyType;
}
// compose the functions together
var composedFunctoidChain = functoidChain.Aggregate((f, g) => x => g(f(x)));
var leftSide = new Func<T, int>(x => (int)composedFunctoidChain(x));
return new Func<T, bool>(r => leftSide(r) > 0);
}
}
}
I think this should get you closer to what you're going for:
static Expression<Func<T, bool>> CreateAnyExpression<T, T2>(string propertyPath,
Expression<Func<T2, bool>> matchExpression)
{
var type = typeof(T);
var parameterExpression = Expression.Parameter(type, "s");
var propertyNames = propertyPath.Split('.');
Expression propBase = parameterExpression;
foreach(var propertyName in propertyNames)
{
PropertyInfo property = type.GetProperty(propertyName);
propBase = Expression.Property(propBase, property);
type = propBase.Type;
}
var itemType = type.GetGenericArguments()[0];
// .Any(...) is better than .Count(...) > 0
var anyMethod = typeof(Enumerable).GetMethods()
.Single(m => m.Name == "Any" && m.GetParameters().Length == 2)
.MakeGenericMethod(itemType);
var callToAny = Expression.Call(anyMethod, propBase, matchExpression);
return Expression.Lambda<Func<T, bool>>(callToAny, parameterExpression);
}
Calling it like this:
CreateAnyExpression<Subscription, Invoice>("Client.Invoices", i => i.InvoiceID == 1)
... yields the following Expression<Func<Subscription,bool>>:
s => s.Client.Invoices.Any(i => (i.InvoiceID == 1))
Here's a working program building Linq Expression
{(x.Children.Count(y => y.SomeID == SomeVar) > 0)}
using System;
using System.Linq;
using System.Linq.Expressions;
namespace ExpressionTree
{
class Program
{
static void Main(string[] args)
{
ParameterExpression foundX = Expression.Parameter(typeof(Parent), "x");
Guid[] guids = new Guid[1] { Guid.NewGuid() };
Expression expression = GetCountWithPredicateExpression(guids, foundX);
}
private static Expression GetCountWithPredicateExpression(Guid[] idsToFilter, ParameterExpression foundX)
{
System.Reflection.PropertyInfo childIDPropertyInfo = typeof(Child).GetProperty(nameof(Child.SomeID));
ParameterExpression foundY = Expression.Parameter(typeof(Child), "y");
Expression childIDLeft = Expression.Property(foundY, childIDPropertyInfo);
Expression conditionExpression = Expression.Constant(false, typeof(bool));
foreach (Guid id in idsToFilter)
conditionExpression = Expression.Or(conditionExpression, Expression.Equal(childIDLeft, Expression.Constant(id)));
Expression<Func<Child, bool>> idLambda = Expression.Lambda<Func<Child, bool>>(conditionExpression, foundY);
var countMethod = typeof(Enumerable).GetMethods()
.First(method => method.Name == "Count" && method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(Child));
System.Reflection.PropertyInfo childrenPropertyInfo = typeof(Parent).GetProperty("Children");
Expression childrenLeft = Expression.Property(foundX, childrenPropertyInfo);
Expression ret = Expression.GreaterThan(Expression.Call(countMethod, childrenLeft, idLambda), Expression.Constant(0));
return ret;
}
}
public class Parent
{
public Child[] Children { get; set; }
}
public class Child
{
public int ID { get; set; }
public Guid SomeID { get; set; }
}
}