Problem evaluation conditional Lambdaexpressions - c#

I have a problem evaluation conditional lambdaexpressions, using System.Linq.Expressions.
Here the full example Code:
using System;
using System.Linq.Expressions;
using System.Reflection;
namespace ExpressionTree
{
class Program<TType>
{
public TType Data { get; set; }
public void RegisterLambda<TProp>(Expression<Func<TType, TProp>> expression)
{
if (expression.Body.NodeType == ExpressionType.Conditional)
{
ConditionalExpression condExp = (ConditionalExpression)expression.Body;
var condTest = condExp.Test;
var paramExpression = Expression.Parameter(typeof(TType), "x");
var lambda = Expression.Lambda(condTest, paramExpression);
var deleg = lambda.Compile(); // this will raise an InvalidOperationException: variable 'x' of type 'ExpressionTree.DataObject'
// referenced from scope '', but it is not defined
var testResult = deleg.DynamicInvoke(Data);
}
if (expression.Body.NodeType == ExpressionType.MemberAccess)
{
var pi = expression.GetPropertyInfo();
Console.WriteLine(pi.GetValue(Data));
}
if (expression.Body.NodeType == ExpressionType.Constant)
{
ConstantExpression ce = (ConstantExpression)expression.Body;
Console.WriteLine(ce.Value);
}
}
}
class Start
{
static void Main(string[] args)
{
Program<DataObject> P = new Program<DataObject>();
P.Data = new DataObject();
P.Data.DisplayName = "1234567890";
P.RegisterLambda(x => x.DisplayName);
P.RegisterLambda(x => x.DisplayName.Length <= 3 ? "Foobar" : x.DisplayName);
P.RegisterLambda<string>(something => "abc");
}
}
class DataObject
{
public string DisplayName { get; set; }
}
static class Extensions
{
public static PropertyInfo GetPropertyInfo(this LambdaExpression expression)
{
if (expression.Body is UnaryExpression unaryExp)
{
if (unaryExp.Operand is MemberExpression memberExp)
{
return (PropertyInfo)memberExp.Member;
}
}
else if (expression.Body is MemberExpression memberExp)
{
return (PropertyInfo)memberExp.Member;
}
throw new Exception();
}
}
}
While calling the 2nd RegisterLambda, which is a ternery conditional lambda. The execution fails on the Compile()-line. Does anyone have a clue to solve that problem? I have tried many combinations using ParameterExpression
Thanks in advance!

Related

Expression and Automapper

Somewhere in the internet I found below class which I am using to convert Expression<Func<T, bool>> from DTO to Domain:
public class EvaluateVariableVisitor<TEntity, TDto> : ExpressionVisitor
{
private readonly ParameterExpression _dtoParameter;
private readonly ParameterExpression _entityParameter;
private readonly IMapper _mapper;
public EvaluateVariableVisitor()
{
_entityParameter = Expression.Parameter(typeof(TEntity));
_dtoParameter = Expression.Parameter(typeof(TDto));
_mapper = AutoMapperConfig.Initialize();
}
protected override Expression VisitMember(MemberExpression node)
{
try
{
//change dto to entity type.
if (node.Expression.Type == _dtoParameter.Type)
{
var reducedExpression = Visit(node.Expression);
//TypeMap typeMap = Mapper.Configuration.FindTypeMapFor<TDto, TEntity>();
TypeMap typeMap = _mapper.ConfigurationProvider.FindTypeMapFor<TDto, TEntity>();
//find the correct name of the property in the destination object by using the name of the source objekt
string destinationPropertyName = typeMap.GetPropertyMaps() //GetCustomPropertyMaps()
.Where(propertyMap => propertyMap.SourceMember.Name == node.Member.Name)
.Select(propertyMap => propertyMap.DestinationProperty.Name).FirstOrDefault();
//find the correct name of the property in the destination object by using the name of the source objekt
//string destinationPropertyName = typeMap.GetPropertyMaps() //GetCustomPropertyMaps()
// .Where(propertyMap => propertyMap.SourceMember.Name == node.Member.Name)
// .Select(propertyMap => propertyMap.DestinationProperty.Name).Single();
var newMember = _entityParameter.Type.GetMember(destinationPropertyName).First();
return Expression.MakeMemberAccess(reducedExpression, newMember);
}
//Recurse down to see if we can simplify...
var expression = Visit(node.Expression);
//If we've ended up with a constant, and it's a property or a field,
//we can simplify ourselves to a constant
var constantExpression = expression as ConstantExpression;
if (constantExpression != null)
{
object container = constantExpression.Value;
object value = null;
var memberAsFieldInfo = node.Member as FieldInfo;
var memberAsPropertyInfo = node.Member as PropertyInfo;
if (memberAsFieldInfo != null)
{
value = memberAsFieldInfo.GetValue(container);
}
if (memberAsPropertyInfo != null)
{
value = memberAsPropertyInfo.GetValue(container, null);
}
if (value != null)
{
return Expression.Constant(value);
}
}
return base.VisitMember(node);
}
catch (System.Exception exc)
{
var ex = exc.Message;
throw;
}
}
//change type from dto to entity --> otherwise the generated expression tree would throw an exception, because of missmatching types(Dto can't be used in Entity expression).
protected override Expression VisitParameter(ParameterExpression node)
{
return node.Type == _dtoParameter.Type ? _entityParameter : node;
}
}
It was working great but I faced an issue when I had property of the same type inside my DTO:
public class InventoryApplicationDto
{
public int Id { get; set; }
public string Name{ get; set; }
public InventoryApplicationDto ParentApplication { get; set; }
}
First of all I had to change:
string destinationPropertyName = typeMap.GetPropertyMaps()
.Where(propertyMap => propertyMap.SourceMember.Name == node.Member.Name)
.Select(propertyMap => propertyMap.DestinationProperty.Name).Single();
To:
string destinationPropertyName = typeMap.GetPropertyMaps()
.Where(propertyMap => propertyMap.SourceMember.Name == node.Member.Name)
.Select(propertyMap => propertyMap.DestinationProperty.Name).FirstOrDefault();
My problem is that I am getting error:
System.ArgumentException: Property 'Int32 ID' is not defined for type
'InventoryApplicationDto'
on line:
return Expression.MakeMemberAccess(reducedExpression, newMember);
Is MakeMemberAccess method case sensitive or there is another issue?

Rebuild Expression

I have an expression like: Expression<Func<TheObject, int, bool>> myExpression = (myObj, theType) => { myObj.Prop > theType };
I need to dynamically rebuild myExpression to a new expression of type Expression<Func<TheObject, bool>> and replace “theType” parameter from the first expression with a concrete value 123 like:
Expression<Func<TheObject, bool>> myNewExpression = myObj => { myObj.Prop > 123 };
How can I do that?
Br
Philip
With a simple expression replacer it is quite easy:
This is the one I've written for myself... supports simple replaces and multiple replaces.
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
// A simple expression visitor to replace some nodes of an expression
// with some other nodes. Can be used with anything, not only with
// ParameterExpression
public class SimpleExpressionReplacer : ExpressionVisitor
{
public readonly Dictionary<Expression, Expression> Replaces;
public SimpleExpressionReplacer(Expression from, Expression to)
{
Replaces = new Dictionary<Expression, Expression> { { from, to } };
}
public SimpleExpressionReplacer(Dictionary<Expression, Expression> replaces)
{
// Note that we should really clone from and to... But we will
// ignore this!
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 override Expression Visit(Expression node)
{
Expression to;
if (node != null && Replaces.TryGetValue(node, out to))
{
return base.Visit(to);
}
return base.Visit(node);
}
}
Your TheObject
public class TheObject
{
public int Prop { get; set; }
}
Then you only need to replace the 2nd parameter in the body of the expression and rebuild the Expression<>
public class Program
{
public static void Main(string[] args)
{
Expression<Func<TheObject, int, bool>> myExpression = (myObj, theType) => myObj.Prop > theType;
int value = 123;
var body = myExpression.Body;
var body2 = new SimpleExpressionReplacer(myExpression.Parameters[1], Expression.Constant(value)).Visit(body);
Expression<Func<TheObject, bool>> myExpression2 = Expression.Lambda<Func<TheObject, bool>>(body2, myExpression.Parameters[0]);
}
}

IEnumerable<dynamic> with linq

I am trying to build dynamic data context, linq is not support dynamic type
I found this solution on
http://jrwren.wrenfam.com/blog/2010/03/04/linq-abuse-with-the-c-4-dynamic-type/
public static class ObjectExtensionMethod
{
public static IEnumerable<dynamic> Select(this object source, Func<dynamic, dynamic> map)
{
foreach (dynamic item in source as dynamic)
{
yield return map(item);
}
}
public static IEnumerable<dynamic> Where(this object source, Func<dynamic, dynamic> predicate)
{
foreach (dynamic item in source as dynamic)
{
if (predicate(item))
yield return item;
}
}
}
the problem with this solution is getting all data from database after that applying the where statement. is there any way to apply where statement before getting the data from database with dynamic type
the problem with this solution is getting all data from database after that applying the where statement.
The problem here is not with dynamic, but with the way you are iterating over the source. You are using foreach, and expect it to be translated to an SQL or sort of, but that is just wrong assumption. As soon as iterator is created by GetEnumerator() method call the query will be "materialized", even if real type of the source is implementing IQueryable<T>, end everything else will be performed in memory.
If you want the conditions to be translated to an SQL you need to implement IQueryableProvider.
Or, at least you can try to call underlying IQueryableProvider. But I'm not sure if it would work.
public static class ObjectExtensionMethod
{
public static IQueryable Select<T>(this IQueryable source, Expression<Func<dynamic, dynamic>> map)
{
var method = new Func<IQueryable<dynamic>, Expression<Func<dynamic, dynamic>>, IQueryable<dynamic>>(Queryable.Select).Method;
var call = Expression.Call(null, method, source.Expression, Expression.Quote(map));
return source.Provider.CreateQuery(call);
}
public static IQueryable Where(this IQueryable source, Expression<Func<dynamic, bool>> predicate)
{
var method = new Func<IQueryable<dynamic>, Expression<Func<dynamic, bool>>, IQueryable<dynamic>>(Queryable.Where).Method;
var call = Expression.Call(null, method, source.Expression, Expression.Quote(predicate));
return source.Provider.CreateQuery(call);
}
}
Please note that type of source parameter has changed from object to IQueryable and types of map and predicate parameters have changed to Expression<Func<,>>.
After long search the solution that is working for me
public static class ObjectExtensionMethod
{
public static IQueryable Select(this IQueryable source, Expression<Func<dynamic, dynamic>> map)
{
try
{
var method = new Func<IQueryable<dynamic>, Expression<Func<dynamic, dynamic>>, IQueryable<dynamic>>(Queryable.Select).Method;
Expression conversion = Expression.Convert(source.Expression, typeof(System.Linq.IQueryable<dynamic>));
var call = Expression.Call(null, method, conversion, Expression.Quote(map));
return source.Provider.CreateQuery(call);
}
catch (Exception ex)
{
return null;
}
}
public static IQueryable Where<T>(this IQueryable source, Expression<Func<T, bool>> predicate)
{
try
{
var method = new Func<IQueryable<T>, Expression<Func<T, bool>>, IQueryable<T>>(Queryable.Where).Method;
Expression conversion = Expression.Convert(source.Expression, typeof(System.Linq.IQueryable<T>));
var call = Expression.Call(null, method, conversion, predicate);
return source.Provider.CreateQuery(call);
}
catch (Exception ex)
{
return null;
}
}
public static IEnumerable<dynamic> ToList(this IQueryable source)
{
return source as dynamic;
}
}
the problem know passing Expression<Func<dynamic, dynamic>>
to solve this problem
public Expression<Func<T, Boolean>> GetWhereFunc<T>(List<WhereCondition> searchFieldList,T item)
{
try
{
if (searchFieldList == null || searchFieldList.Count == 0)
{
return null;
}
ParameterExpression pe = Expression.Parameter(item.GetType(), "c");
//ParameterExpression pe = Expression.Parameter(typeof(object), "c");
Type itemType = item.GetType();
//combine them with and 1=1 Like no expression
Expression combined = null;
Type tempPropType;
if (searchFieldList != null)
{
foreach (WhereCondition fieldItem in searchFieldList)
{
if (string.IsNullOrEmpty(fieldItem.Value))
continue;
if (!string.IsNullOrEmpty(fieldItem.GridTblName))
continue;
//Expression for accessing Fields name property
Expression columnNameProperty = Expression.Property(pe, fieldItem.ColumName);
//the name constant to match
Expression columnValue = null;
tempPropType = itemType.GetProperty(fieldItem.ColumName).PropertyType;
if (tempPropType == typeof(DateTime) || tempPropType == typeof(DateTime?))
{
if (string.IsNullOrEmpty(fieldItem.Value))
{
}
else
{
DateTime tempdate = DateTime.Parse(fieldItem.Value);
TimeZone zoneclient = TimeZone.CurrentTimeZone;
TimeSpan spclient = zoneclient.GetUtcOffset(tempdate);
tempdate = tempdate.AddMinutes(-1 * spclient.TotalMinutes);
fieldItem.Value = tempdate.ToString();
}
}
if (tempPropType == typeof(Guid) || tempPropType == typeof(Guid?))
{
if (string.IsNullOrEmpty(fieldItem.Value))
{
columnValue = Expression.Constant(null);
}
else
{
if (tempPropType == typeof(Guid?))
{
columnValue = Expression.Constant((Guid?)Guid.Parse(fieldItem.Value), typeof(Guid?));
}
else
{
columnValue = Expression.Constant(Guid.Parse(fieldItem.Value));
}
}
}
else if (tempPropType.IsGenericType && tempPropType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
if (string.IsNullOrEmpty(fieldItem.Value))
{
columnValue = Expression.Constant(null);
}
else
{
columnValue = Expression.Constant(Convert.ChangeType(fieldItem.Value, tempPropType.GetGenericArguments()[0])
, tempPropType);
}
}
else
{
columnValue = Expression.Constant(Convert.ChangeType(fieldItem.Value, tempPropType));
}
Expression e1 = null;
MethodInfo method;
switch (fieldItem.Cond)
{
case Condetion.Equal:
e1 = Expression.Equal(columnNameProperty, columnValue);
break;
case Condetion.Greater:
e1 = Expression.GreaterThan(columnNameProperty, columnValue);
break;
case Condetion.GreaterOrEqual:
e1 = Expression.GreaterThanOrEqual(columnNameProperty, columnValue);
break;
case Condetion.Lower:
e1 = Expression.LessThan(columnNameProperty, columnValue);
break;
case Condetion.LowerOrEqual:
e1 = Expression.LessThanOrEqual(columnNameProperty, columnValue);
break;
case Condetion.NotEqual:
e1 = Expression.NotEqual(columnNameProperty, columnValue);
break;
case Condetion.Contaiens:
if (fieldItem.IsContaien)
{
Type tt = fieldItem.Values.GetType();
if (tt == typeof(List<dynamic>))
{
IEnumerable<dynamic> val = fieldItem.Values.Cast<dynamic>().ToList();
var someValueContain = Expression.Constant(val, val.GetType());
var convertExpression = Expression.Convert(columnNameProperty, typeof(object));
e1 = Expression.Call(someValueContain, "Contains", new Type[] { }, convertExpression);
}
else
{
var mval = fieldItem.Values.AsQueryable().Cast<dynamic>();
var someValueContain = Expression.Constant(mval, mval.GetType());
var convertExpression = Expression.Convert(columnNameProperty, typeof(object));
e1 = Expression.Call((
((Expression<Func<bool>>)
(() => Queryable.Contains(default(IQueryable<dynamic>), default(object))))
.Body as MethodCallExpression).Method,
someValueContain,
convertExpression);
}
}
else
{
method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var someValueContain = Expression.Constant(fieldItem.Value, columnValue.Type);
e1 = Expression.Call(columnNameProperty, method, someValueContain);
}
break;
case Condetion.StartWith:
method = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
var someValueStartWith = Expression.Constant(fieldItem.Value, columnValue.Type);
e1 = Expression.Call(columnNameProperty, method, someValueStartWith);
break;
case Condetion.EndWith:
method = typeof(string).GetMethod("EndsWith", new[] { typeof(string) });
var someValueEndWith = Expression.Constant(fieldItem.Value, columnValue.Type);
e1 = Expression.Call(columnNameProperty, method, someValueEndWith);
break;
case Condetion.NotContaiens:
if (fieldItem.IsContaien)
{
Type tt = fieldItem.Values.GetType();
if (tt == typeof(List<dynamic>))
{
IEnumerable<dynamic> val = fieldItem.Values.Cast<dynamic>().ToList();
var someValueContain = Expression.Constant(val, val.GetType());
var convertExpression = Expression.Convert(columnNameProperty, typeof(object));
e1 = Expression.Call(someValueContain, "Contains", new Type[] { }, convertExpression);
e1 = Expression.Not(e1);
}
else
{
var mval = fieldItem.Values.AsQueryable().Cast<dynamic>();
var someValueContain = Expression.Constant(mval, mval.GetType());
var convertExpression = Expression.Convert(columnNameProperty, typeof(object));
e1 = Expression.Call((
((Expression<Func<bool>>)
(() => Queryable.Contains(default(IQueryable<dynamic>), default(object))))
.Body as MethodCallExpression).Method,
someValueContain,
convertExpression);
e1 = Expression.Not(e1);
}
}
else
{
method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var someValueContain = Expression.Constant(fieldItem.Value, columnValue.Type);
e1 = Expression.Call(columnNameProperty, method, someValueContain);
e1 = Expression.Not(e1);
}
break;
}
if (combined == null)
{
combined = e1;
}
else
{
combined = Expression.And(combined, e1);
}
}
}
if (combined == null)
{
return null;
}
var mm = Expression.Lambda<Func<T, bool>>(combined, pe);
return mm;//.Compile();
}
catch (Exception ex)
{
Logs.Log(ex);
return null;
}
}
public class WhereCondition
{
public string ColumName { set; get; }
public string Value { set; get; }
public Condetion Cond { set; get; }
public string GridTblName { set; get; }
public IEnumerable<dynamic> Values { set; get; }
public bool IsContaien { set; get; }
public WhereCondition(string columName, string value, Condetion cond)
{
ColumName = columName;
Value = value;
Cond = cond;
}
public WhereCondition()
{
ColumName = "";
Value = "";
Cond = Condetion.Equal;
}
}
public enum Condetion { Equal, Greater, GreaterOrEqual, Lower, LowerOrEqual, NotEqual, Contaiens, NotContaiens, StartWith,EndWith }
We can now call the query like this.
WhereCondition whereCondition = new WhereCondition();
whereCondition.ColumName = "Id";
whereCondition.Cond = Condetion.Equal;
whereCondition.Value = id.ToString();
Expression<Func<T, bool>> whereEx = GetWhereFunc(new List<WhereCondition>(){whereCondition}, GetType());
return (from c in RDaynamicContext.GetTable(tbl)
select c).Where(whereEx).FirstOrDefault();

Best way to concat strings and numbers in SQL server using Entity Framework 5?

For some reason Microsoft decided to not support simple concat in EF5.
e.g.
Select(foo => new
{
someProp = "hello" + foo.id + "/" + foo.bar
}
This will throw if foo.id or foo.bar are numbers.
The workaround I've found is apparently this pretty peice of code:
Select(foo => new
{
someProp = "hello" +
SqlFunctions.StringConvert((double?)foo.id).Trim() +
"/" +
SqlFunctions.StringConvert((double?)foo.bar).Trim()
}
Which works fine, but is just horrid to look at.
So, is there some decent way to accomplish this with cleaner code?
I'm NOT interested in doing this client side, so no .AsEnumerable() answers please.
For those interested.
I got so pissed with the lack of this feature that I implemented it myself using an ExpressionVisitor.
You can now write code like the one in the original question.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Objects.SqlClient;
using System.Linq;
using System.Linq.Expressions;
namespace Crawlr.Web.Code
{
public static class ObjectSetExExtensions
{
public static ObjectSetEx<T> Extend<T>(this IQueryable<T> self) where T : class
{
return new ObjectSetEx<T>(self);
}
}
public class ObjectSetEx<T> : IOrderedQueryable<T>
{
private readonly QueryProviderEx provider;
private readonly IQueryable<T> source;
public ObjectSetEx(IQueryable<T> source)
{
this.source = source;
provider = new QueryProviderEx(this.source.Provider);
}
#region IQueryableEx<T> Members
public IEnumerator<T> GetEnumerator()
{
return source.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return source.GetEnumerator();
}
public Type ElementType
{
get { return source.ElementType; }
}
public Expression Expression
{
get { return source.Expression; }
}
public IQueryProvider Provider
{
get { return provider; }
}
#endregion
}
public class QueryProviderEx : IQueryProvider
{
private readonly IQueryProvider source;
public QueryProviderEx(IQueryProvider source)
{
this.source = source;
}
#region IQueryProvider Members
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
Expression newExpression = ExpressionReWriterVisitor.Default.Visit(expression);
IQueryable<TElement> query = source.CreateQuery<TElement>(newExpression);
return new ObjectSetEx<TElement>(query);
}
public IQueryable CreateQuery(Expression expression)
{
Expression newExpression = ExpressionReWriterVisitor.Default.Visit(expression);
IQueryable query = source.CreateQuery(newExpression);
return query;
}
public TResult Execute<TResult>(Expression expression)
{
Expression newExpression = ExpressionReWriterVisitor.Default.Visit(expression);
return source.Execute<TResult>(newExpression);
}
public object Execute(Expression expression)
{
Expression newExpression = ExpressionReWriterVisitor.Default.Visit(expression);
return source.Execute(newExpression);
}
#endregion
}
public class ExpressionReWriterVisitor : ExpressionVisitor
{
public static readonly ExpressionReWriterVisitor Default = new ExpressionReWriterVisitor();
protected override Expression VisitUnary(UnaryExpression node)
{
if (node.NodeType == ExpressionType.Convert && node.Operand.Type == typeof(int) && node.Type==typeof(object))
{
var operand = node.Operand;
var stringConvertMethod = typeof(SqlFunctions).GetMethod("StringConvert", new Type[] { typeof(double?) });
var trimMethod = typeof(string).GetMethod("Trim",new Type[] {});
var dOperand = Expression.Convert(operand, typeof(double?));
return Expression.Call(Expression.Call(stringConvertMethod, dOperand),trimMethod);
}
return base.VisitUnary(node);
}
}
}
usage:
var res = model
.FooSet
.Extend() //<- applies the magic
.Select(foo => new
{
someProp = "hello" + foo.id + "/" + foo.bar
}

Expression.Call and Count

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; }
}
}

Categories

Resources