i wrote function
private Func<CategorizedPosts, bool> CompileExpression(IEnumerable<Category> categories)
{
Expression predicateBody;
ParameterExpression pe = Expression.Parameter(typeof(CategorizedPosts), "post");
Expression left = Expression.Property(pe, typeof(CategorizedPosts).GetProperty("CATEGORY_ID"));
Expression right = Expression.Constant(categories.ElementAt(0).ID);
Expression equal = Expression.Equal(left, right);
predicateBody = equal;
for (int i = 1, j = categories.Count() - 1; i < categories.Count(); ++i )
{
var category = categories.ElementAt(i);
//y => y.CATEGORY_ID == 1 || y.CATEGORY_ID == 2)
left = Expression.Property(pe, typeof(CategorizedPosts).GetProperty("CATEGORY_ID"));
right = Expression.Constant(category.ID);
equal = Expression.Equal(left, right);
predicateBody = Expression.OrElse(predicateBody, equal);
}
var lll = Expression.Lambda<Func<CategorizedPosts, bool>>(predicateBody, pe);
var compiled = lll.Compile();
return compiled;
}
it compiles OK, but when I try to run this query
var ctx = db.Posts.Where(x => true);
if(predicate != null)
{
ctx = ctx.Where(x => x.CategorizedPosts.Where(**predicate**).Count() > 0);
}
IList<Post> posts = ctx.OrderByDescending(x => x.CREATION_DATE).Skip((page - 1) * perPage).Take(perPage).Select(x => new Post
{
POST_ID = x.ID,
TYPE = new Type { ID = x.TYPE_ID, NAME = x.Types.NAME },
AUTHOR = new Author()
{
ID = x.AUTHOR_ID,
NAME = x.Authors.NAME,
},
CATEGORIES = x.CategorizedPosts.Select(y => new Category() { ID = y.CATEGORY_ID, NAME = y.Categories.NAME }),
CREATION_DATE = x.CREATION_DATE,
}).ToList();
EF throws exception about internal error 1025 for Entity Data Provider. How can I perform this query with dynamic where?
You could use the Contains of a collection of Ids (int) and apply it on a where, for sample:
int[] categorieIds = categories.Select(x => x.Id).ToArray();
ctx = ctx.Where(x => x.CategorizedPosts.Any(c => categorieIds .Contains(c.Id));
Some Tips
Remember the Entity Framework works with Expression<Func<T, bool>> in the Where method, not only Func<T, bool>.
You also could try to apply PredicateBuilder class which provides some extensions methods like Or, And, Not, so, you could try this:
var predicate = PredicateBuilder.False<Product>();
foreach (string keyword in keywords)
{
string temp = keyword;
predicate = predicate.Or (p => p.Description.Contains (temp));
}
return dataContext.Products.Where(predicate).ToList();
Related
I'm trying to dynamically create an predicate to pass into a linq where clause. This is for a generic method that takes two list of the same Type and and list of property names to compare.
void SomeMethod<T>(List<T> oldRecords, List<T> newRecords, List<string> propertiesOfT)
{
// dynamically build predicate for this
var notMatch = oldRecords.Where(o => !newRecords.Any(n => n.Prop1 == o.Prop1 && n.Prop2 == o.Prop2)).ToList();
// do somethind with notMatch
}
I would like to convert this:
var notMatch = oldRecords.Where(o => !newRecords.Any(n => n.Prop1 == o.Prop1 && n.Prop2 == o.Prop2)).ToList();
To achieve this:
var predicate = "n => n.Prop1 == o.Prop1 && n.Prop2 == o.Prop2"; // sudo code
var notMatch = oldRecords.Where(o => !newRecords.Any(predicate));
or this
var predicate = "o => !newRecords.Any(n => n.Prop1 == o.Prop1 && n.Prop2 == o.Prop2)" // sudo code
var notMatch = oldRecords.Where(predicate);
How do I populate newRecords when dynamically creating the Expression?
And how would I reference parameter o and parameter n in the Expresssion.
I've gotten this far:
//construct the two parameters
var o = Expression.Parameter(typeof(T), "o");
var n = Expression.Parameter(typeof(T), "n");
// How to I go about populating o with values and n with values
// from oldRecords and newRecords? or is that no neccessary
var property = Expression.Property(o, typeof(T).GetProperty("Id").Name);
var value = Expression.Constant(Convert.ChangeType("12345", typeof(T).GetProperty("Id").PropertyType), typeof(T).GetProperty("Id").PropertyType);
BinaryExpression binaryExpression = Expression.MakeBinary(ExpressionType.Equal, property, value);
Any sudo code or clue where to look to achieve this?
With reflection it's quite easy. You just to have to think about it. Here's the working version.
void SomeMethod<T>(List<T> oldRecords, List<T> newRecords, List<string> propertiesOfT)
{
// get the list of property to match
var properties = propertiesOfT.Select(prop => typeof(T).GetProperty(prop)).ToList();
// Get all old record where we don't find any matching new record where all the property equal that old record
var notMatch = oldRecords.Where(o => !newRecords.Any(n => properties.All(prop => prop.GetValue(o).Equals(prop.GetValue(n))))).ToList();
}
And here's a sample set i tried and it works
public class test
{
public int id { get; set; } = 0;
public string desc { get; set; } = "";
public test(string s, int i)
{
desc = s;id = i;
}
}
private void Main()
{
var oldRecords = new List<test>()
{
new test("test",1),
new test("test",2)
};
var newRecords = new List<test>()
{
new test("test1",1),
new test("test",2)
};
SomeMethod(oldRecords, newRecords, new List<string>() { "id", "desc" });
}
So, I'm experimenting with expressions atm. Got code below:
Code works fine except for 1 thing: I need to replace ViewModel type with ForeignKeyProperty.PropertyType, which is only known at runtime, at the line of var condition = Expression.Lambda < Func < ViewModel, bool> >
Expected end result:
ForeignKeyProperty.SetValue(model, repository.GetList <ForeignKeyProperty.PropertyType >().Single(x => x.Id == model.Id));
protected List < Action < IVenturaRepository, ViewModel>> SetForeignKeyProperties<ViewModel>() where ViewModel : BaseViewModel
{
var viewModelType = typeof(ViewModel);
var foreignKeyProperties = viewModelType.GetProperties().Where(x => x.PropertyType.IsSubclassOf(typeof(BaseViewModel)));
var actions = new List < Action < IVenturaRepository, ViewModel>>();
var repositoryType = typeof(IVenturaRepository);
foreach(var ForeignKeyProperty in foreignKeyProperties)
{
var foreignKeyIdProperty = viewModelType.GetProperties().SingleOrDefault(x => x.Name == ForeignKeyProperty.Name + "Id");
//ForeignKeyProperty.SetValue(model, repository.GetList<ViewModel>().Single(x => x.Id == model.Id));
var listMethod = repositoryType.GetMethods().SingleOrDefault(x => x.Name == "GetList").MakeGenericMethod(ForeignKeyProperty.PropertyType);
//Expression.Call(singleMethod,);
var repositoryVariable = Expression.Parameter(repositoryType, "repository");
var paramViewModelType = Expression.Parameter(viewModelType, "model");
var paramForeignEntityId = Expression.Property(paramViewModelType, "Id");
var listMethodCall = Expression.Call(repositoryVariable, listMethod);
var modelParameter = Expression.Parameter(ForeignKeyProperty.PropertyType, "x");
var foreignKeyTypeConstant = Expression.Constant(ForeignKeyProperty.PropertyType);
var condition =
Expression.Lambda < Func < ViewModel, bool>>(
Expression.Equal(
Expression.Property(paramViewModelType, foreignKeyIdProperty.Name),
Expression.Convert(Expression.Property(modelParameter, "Id"),foreignKeyIdProperty.PropertyType)
),
modelParameter
);
//var singleMethod = typeof(Enumerable).GetMethods().SingleOrDefault(x => x.Name.Equals("SingleOrDefault") && x.GetParameters().Count() ==2).MakeGenericMethod(viewModelType);
//var singleMethod = typeof(IEnumerable<ViewModel>).GetMethods().SingleOrDefault(x => x.GetParameters().Count() > 0).MakeGenericMethod(viewModelType);
//var singleLambda = Expression.Lambda(Expression.Property(modelParameter, "Id"), modelParameter);
var singleMethodCall = Expression.Call(typeof(Enumerable), "SingleOrDefault", new[] { ForeignKeyProperty.PropertyType },listMethodCall, condition);
//var singleMethodCall = Expression.Call(listMethodCall, singleMethod, condition);
var setMethod = ForeignKeyProperty.GetSetMethod();
var oParameter = Expression.Parameter(viewModelType, "obj");
var vParameter = Expression.Parameter(typeof(ViewModel),"value");
var method = Expression.Call(oParameter,setMethod, singleMethodCall);
var expression = Expression.Lambda<Action<IVenturaRepository, ViewModel>>(method);
actions.Add(expression.Compile());
}
return actions;
}
Could someone point me in the right direction please?
use Object as type. then you can check type by getType() at runtime and after check cast to correct type. or use dynamic to avoid casting.
So, I did as you suggested and used object instead. It works now.
Code below works in a specific controller that inherits from BaseController.
public override ActionResult Edit(long id, DeliveryEditViewModel model)
{
model.SourceModel.DeliveryRound = Repository.DeliveryRounds.Single(x => x.Id == model.DeliveryRoundId);
model.SourceModel.Sale = Repository.Sales.Single(x => x.Id == model.SaleId);
return base.Edit(id, model);
}
Idea is that code below works in BaseController:
public virtual ActionResult Edit(long id, EditModel model)
{
try
{
if (setForeignKeyActionList == null)
setForeignKeyActionList = SetForeignKeyProperties();
setForeignKeyActionList.ForEach(action => action(Repository, model.SourceModel));
Repository.SaveItem(model);
return RedirectToAction("Index");
}
catch(Exception ex)
{
ModelState.AddModelError("Error", ex);
return View(Repository.GetEditableItem(id));
}
}
This is the revised code based on your suggestion to use object type
protected List< Action< IVenturaRepository, ViewModel>> SetForeignKeyProperties()
{
var viewModelType = typeof(ViewModel);
var foreignKeyProperties = viewModelType.GetProperties().Where(x => x.PropertyType.IsSubclassOf(typeof(BaseViewModel)));
var actions = new List< Action< IVenturaRepository, ViewModel>>();
var repositoryType = typeof(IVenturaRepository);
foreach (var ForeignKeyProperty in foreignKeyProperties)
{
var foreignKeyIdProperty = viewModelType.GetProperties().SingleOrDefault(x => x.Name == ForeignKeyProperty.Name + "Id");
//ForeignKeyProperty.SetValue(model, repository.GetList< OtherViewModel>().Single(x => x.Id == model.Id));
var listMethod = repositoryType.GetMethods().SingleOrDefault(x => x.Name == "GetList").MakeGenericMethod(ForeignKeyProperty.PropertyType);
var repositoryVariable = Expression.Parameter(repositoryType, "repository");
var paramViewModelType = Expression.Parameter(viewModelType, "model");
var paramForeignEntityId = Expression.Property(paramViewModelType, "Id");
var listMethodCall = Expression.Call(repositoryVariable, listMethod);
var foreignKeyTypeConstant = Expression.Constant(ForeignKeyProperty.PropertyType);
var objectType = Expression.Parameter(typeof(object), "model");
var modelParameter = Expression.Parameter(typeof(object), "x");
var expressionForeignKeyId = Expression.Property(paramViewModelType, foreignKeyIdProperty.Name);
var expressionForeignEntityId = Expression.Convert(Expression.Property(Expression.Convert(modelParameter, ForeignKeyProperty.PropertyType), "Id"), foreignKeyIdProperty.PropertyType);
var condition =
Expression.Lambda<Func<object, bool>>(
Expression.Equal(
expressionForeignKeyId,
expressionForeignEntityId
),
modelParameter
);
var singleMethodCall = Expression.Call(typeof(Enumerable), "SingleOrDefault", new[] { ForeignKeyProperty.PropertyType }, listMethodCall, condition);
//var singleMethodCall = Expression.Call(listMethodCall, singleMethod, condition);
var setMethod = ForeignKeyProperty.GetSetMethod();
//var oParameter = Expression.Parameter(viewModelType, "obj");
var vParameter = Expression.Parameter(typeof(ViewModel), "value");
var method = Expression.Call(paramViewModelType, setMethod, singleMethodCall);
var lamdaParameterExpressions = new[]
{
repositoryVariable,
paramViewModelType
};
var expression = Expression.Lambda<Action<IVenturaRepository, ViewModel>>(method, lamdaParameterExpressions);
actions.Add(expression.Compile());
}
return actions;
}
I am using the dictionary inside the IQueryable lambda linq throws the
Unable to create a constant value of type 'System.Collections.Generic.KeyValuePair`2
Code :
Dictionary<int, int> keyValues = new Dictionary<int, int>();
IQueryable<Account> = context.Account
.Where(W => keyValues
.Where(W1 => W1.Key == S.AccountID)
.Where(W1 => W1.Value == S.Balance)
.Count() > 0);
Details:
I have the data inside the dictionary like this
AccountID Balance
1 1000
2 2000
3 3000
I want the user which have the (ID = 1 AND Balance = 1000) OR (ID = 2 AND BALANCE = 2000) OR (ID = 3 AND BALANCE = 3000)
So how can I write the lambda for it ?
Edited
Thanks #caesay, Your answer help me lots.
I want one more favor from you.
From you answer I create the expression which look like below:
private static Expression<Func<Accounting, bool>> GenerateExpression(Dictionary<int, int> lstAccountsBalance)
{
try
{
var objAccounting = Expression.Parameter(typeof(Accounting));
Expression expr = null;
const bool NOT_ALLOWED = false;
if (lstAccountsBalance != null && lstAccountsBalance.Count > 0)
{
var clauses = new List<Expression>();
foreach (var kvp in lstAccountsBalance)
{
clauses.Add(Expression.AndAlso(
Expression.Equal(Expression.Constant(kvp.Key), Expression.Property(objAccounting, nameof(Accounting.ID))),
Expression.Equal(Expression.Constant(kvp.Value), Expression.Property(objAccounting, nameof(Accounting.Balance)))
));
}
expr = clauses.First();
foreach (var e in clauses.Skip(1))
{
expr = Expression.OrElse(e, expr);
}
var notAllowedExpr = Expression.AndAlso(
Expression.Equal(Expression.Constant(NOT_ALLOWED), Expression.Property(objAccounting, nameof(Accounting.ALLOWED))),
Expression.Equal(Expression.Constant(true), Expression.Constant(true))
);
expr = Expression.And(notAllowedExpr, expr);
}
var allowedExpr = Expression.AndAlso(
Expression.Equal(Expression.Constant(!NOT_ALLOWED), Expression.Property(objAccounting, nameof(Accounting.ALLOWED))),
Expression.Equal(Expression.Property(objAccounting, nameof(Accounting.ID)), Expression.Property(objAccounting, nameof(Accounting.ID)))
);
if (expr != null)
{
expr = Expression.OrElse(allowedExpr, expr);
}
else
{
expr = allowedExpr;
}
return Expression.Lambda<Func<Accounting, bool>>(expr, objAccounting);
}
catch (Exception ex)
{
throw objEx;
}
}
After that I compiled the expression like this:
Expression<Func<Accounting, bool>> ExpressionFunctions = GenerateExpression(lstAccountsBalance);
var compiledExpression = ExpressionFunctions.Compile();
And I used like this:
.Select(S => new
{
Accounting = S.Accounts
.Join(context.AccountInfo,
objAccounts => objAccounts.ID,
objAccountInfo => objAccountInfo.ID,
(objAccounts, objAccountInfo) => new Accounting
{
ID = objAccounts.ID,
Balance = objAccountInfo.Balance,
})
.Where(W => W.ID == user.ID)
.AsQueryable()
.Where(W => compiledExpression(W))
.Select(S1 => new Accounting()
{
ID = S1.ID,
Balance = S1.Balance
})
.ToList(),
}
And it throws the exception with the message:
System.NotSupportedException: The LINQ expression node type 'Invoke'
is not supported in LINQ to Entities.
Without Compile
Without the compile it works like charm. It gives the output want I want.
Expression<Func<Accounting, bool>> ExpressionFunctions = GenerateExpression(lstAccountsBalance);
Use:
.Select(S => new
{
Accounting = S.Accounts
.Join(context.AccountInfo,
objAccounts => objAccounts.ID,
objAccountInfo => objAccountInfo.ID,
(objAccounts, objAccountInfo) => new Accounting
{
ID = objAccounts.ID,
Balance = objAccountInfo.Balance,
})
.Where(W => W.ID == user.ID)
.AsQueryable()
.Where(ExpressionFunctions)
.Select(S1 => new Accounting()
{
ID = S1.ID,
Balance = S1.Balance
})
Thank you..
Everything inside of a EF linq query needs to be compiled to an Expression tree, and then to SQL, but the dictionary is an IEnumerable so there is no way that EF could know how to compile that.
You can either build the expression tree yourself, or use the System.Linq.Dynamic nuget package to build the sql yourself.
The example using System.Linq.Dynamic, first install the nuget package and add the using to the top of your file, then there will be a Where overload that takes a string as a parameter:
context.Account.Where(
String.Join(" OR ", keyValues.Select(kvp => $"(ID = {kvp.Key} AND Balance = {kvp.Value})")));
Essentially, everything inside of the Where clause will be executed directly as SQL, so be careful to use the Where(string, params object[]) overload to paramaterize your query if accepting user input.
The expression tree (untested) approach might look like the following:
var account = Expression.Parameter(typeof(Account));
var clauses = new List<Expression>();
foreach(var kvp in keyValues)
{
clauses.Add(Expression.AndAlso(
Expression.Equal(Expression.Constant(kvp.Key), Expression.Property(account, nameof(Account.AccountID))),
Expression.Equal(Expression.Constant(kvp.Value), Expression.Property(account, nameof(Account.Balance)))
));
}
var expr = clauses.First();
foreach (var e in clauses.Skip(1))
expr = Expression.OrElse(e, expr);
context.Account.Where(Expression.Lambda<Func<Account, bool>>(expr, account));
Essentially inside the foreach loop we're creating all of the (ID = ... AND Balance = ...) and then at the end we join them all with an OR.
I have created three classes.
Two classes Data and IntArrayEqualityComparer are below-
public class Data
{
public Dictionary<int[], List<double>> s = new Dictionary<int[], List<double>>(new IntArrayEqualityComparer());
public Data()
{
}
public Data(Dictionary<int[], List<double>> s)
{
this.s = s;
}
public Dictionary<int[], List<double>> S
{
get { return s; }
set { s = value; }
}
}
public class IntArrayEqualityComparer : IEqualityComparer<int[]>
{
public bool Equals(int[] x, int[] y)
{
if (x.Length != y.Length)
{
return false;
}
for (int i = 0; i < x.Length; i++)
{
if (x[i] != y[i])
{
return false;
}
}
return true;
}
public int GetHashCode(int[] obj)
{
int result = 17;
for (int i = 0; i < obj.Length; i++)
{
unchecked
{
result = result * 23 + obj[i];
}
}
return result;
}
}
A third class named Expression is created in which I need to convert LINQ expression into Reflection -
public class Expresion
{
public void CreateExpression()
{
Expression<Func<Data, List<int>>> exp1 = null;
//Below is the LINQ expression I want to convert
exp1 = p2 => p2.s[new int[] { 14, 5 }].Select((item, index) => new { item, index }).Select(x => x.index).ToList();
ParameterExpression p1 = Expression.Parameter(typeof(Data), "p");
MethodInfo mInfo = typeof(List<double>).GetMethod("get_Item");
MethodInfo mInfo1 = typeof(Dictionary<int, List<double>>).GetMethod("get_Item");
MethodInfo mInfo2 = typeof(Dictionary<int[], List<double>>).GetMethod("get_Item");
MethodInfo mInfo3 = typeof(List<int[]>).GetMethod("get_Item");
MemberExpression s1 = Expression.Property(p1, "s");
ParameterExpression index1 = Expression.Parameter(typeof(int), "index");
ParameterExpression item1 = Expression.Parameter(typeof(double), "item");
//Here I want to covert the "(item, index) => new { item, index }" part from LINQ expression into Reflection
}
}
Probably the most complex and useless Expression tree I've ever built by hand. Comments inline.
public class Expresion {
// We need the anonymous type that we want to use
private static readonly Type AnonymousType = new { item = 0.0, index = 0 }.GetType();
public void CreateExpression() {
//Below is the LINQ expression I want to convert
Expression<Func<Data, List<int>>> exp2 = p => p.s[new int[] { 14, 5 }].Select((item, index) => new { item, index }).Select(x => x.index).ToList();
ParameterExpression p1 = Expression.Parameter(typeof(Data), "p");
MemberExpression s1 = Expression.PropertyOrField(p1, "s");
// The indexer
PropertyInfo dictItem = s1.Type.GetProperty("Item");
// The key to the dictionary, new int[] { 14, 5 }
var key = Expression.NewArrayInit(typeof(int), Expression.Constant(14), Expression.Constant(5));
// s[new int[] { 14, 5 }]
var getItem = Expression.Property(s1, dictItem, key);
// Enumerable.Select with indexer (generic)
var enumerableSelectIndexTSourceTResult = (from x in typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
where x.Name == "Select" && x.IsGenericMethod
let args = x.GetGenericArguments()
where args.Length == 2
let pars = x.GetParameters()
where pars.Length == 2 &&
pars[0].ParameterType == typeof(IEnumerable<>).MakeGenericType(args[0]) &&
pars[1].ParameterType == typeof(Func<,,>).MakeGenericType(args[0], typeof(int), args[1])
select x).Single();
// Enumerable.Select with indexer (non-generic)
var enumerableSelectIndex = enumerableSelectIndexTSourceTResult.MakeGenericMethod(typeof(double), AnonymousType);
// Inner function start
ParameterExpression item1 = Expression.Parameter(typeof(double), "item");
ParameterExpression index1 = Expression.Parameter(typeof(int), "index");
var innerExpression1 = Expression.Lambda(Expression.New(AnonymousType.GetConstructors().Single(), item1, index1), item1, index1);
// .Select((item, index) => new { item, index })
var select1 = Expression.Call(enumerableSelectIndex, getItem, innerExpression1);
// Inner function end
// Enumerable.Select without indexer (generic)
var enumerableSelectTSourceTResult = (from x in typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
where x.Name == "Select" && x.IsGenericMethod
let args = x.GetGenericArguments()
where args.Length == 2
let pars = x.GetParameters()
where pars.Length == 2 &&
pars[0].ParameterType == typeof(IEnumerable<>).MakeGenericType(args[0]) &&
pars[1].ParameterType == typeof(Func<,>).MakeGenericType(args[0], args[1])
select x).Single();
// Enumerable.Select without indexer (non-generic)
var enumerableSelect = enumerableSelectTSourceTResult.MakeGenericMethod(AnonymousType, typeof(int));
// Inner function start
ParameterExpression anonymousType1 = Expression.Parameter(AnonymousType, "x");
var innerExpression2 = Expression.Lambda(Expression.Property(anonymousType1, "index"), anonymousType1);
// Inner function end
// .Select((previous select), x => x.index)
var select2 = Expression.Call(enumerableSelect, select1, innerExpression2);
// Enumerable.ToList (generic)
var enumerableToListTSource = (from x in typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
where x.Name == "ToList" && x.IsGenericMethod
let args = x.GetGenericArguments()
where args.Length == 1
let pars = x.GetParameters()
where pars.Length == 1 &&
pars[0].ParameterType == typeof(IEnumerable<>).MakeGenericType(args[0])
select x).Single();
// Enumerable.ToList (non-generic)
var enumerableToList = enumerableToListTSource.MakeGenericMethod(typeof(int));
// .ToList((previous select))
var toList1 = Expression.Call(enumerableToList, select2);
var exp1 = Expression.Lambda<Func<Data, List<int>>>(toList1, p1);
var func1 = exp1.Compile();
// Test
var data = new Data();
data.S[new int[] { 14, 5 }] = new List<double> { 1.0, 2.0 };
var result = func1(data);
}
}
Note that there are some limitations: the anonymous type used must be known at compile time. Using a Tuple<> is often an alternative. In the code the Type AnonymousType line makes the compiler know the type and gets it (through the final .GetType()).
Another important part is the one about finding functions in the Enumerable class. The Select especially is quite complex to find, because there are two different Select with the same number of parameters.
How to build dynamic lambda function like below?
item => item.AnalyzeData.Any(subitem =>
subitem.DepartmentIDs.Any(subitem2 => subitem2 == "abc"))
AnalyzeData is custom class array type, DepartmentIDs is string array type
The class of AnalyzeData has DepartmentIDs Property
So how to dynamic generator the above lambda expression via expression tree.
After some research, the problem has been resolved. now share the solution code to others who has the some issue.
[TestMethod]
[Description("Dynamic generate lambda expression")]
public void DynamicGenerateMethod1()
{
Item item1 = new Item()
{
ItemID = "test1",
AnalyzeData = new ItemAnalyzeData[]
{
new ItemAnalyzeData()
{
DepartmentIDs = new []{"Department1","Department2"}
},
}
};
Item item2 = new Item()
{
ItemID = "test2",
AnalyzeData = new ItemAnalyzeData[]
{
new ItemAnalyzeData()
{
DepartmentIDs = new []{"Department3","Department4"}
},
}
};
Expression<Func<Item, bool>> expectedExpression =
item => item.AnalyzeData.Any(subitem => subitem.DepartmentIDs.Any(subitem2 => subitem2 == "Department3"));
//Get Enumerable.Any generic method
var anyPredicate =
typeof(Enumerable).GetMethods().First(m => m.Name == "Any" && m.GetParameters().Length == 2);
#region Build inner most lambda expression : subitem2 => subitem2 == "Department3"
var subitem2Para = Expression.Parameter(typeof(string), "subitem2");
var subitem2CompareValue = Expression.Equal(subitem2Para, Expression.Constant("Department3", typeof(string)));
var subitem2CompareFun = Expression.Lambda<Func<string, bool>>(subitem2CompareValue, subitem2Para);
#endregion
#region Build center lambda expression : subitem => subitem.DepartmentIDs.Any( ... )
var subitemPara = Expression.Parameter(typeof(ItemAnalyzeData), "subitem");
var departmentIDsAnyMethodCall = anyPredicate.MakeGenericMethod(typeof(string));
var subItemDepartmentIDsCall = Expression.Call(departmentIDsAnyMethodCall, Expression.Property(subitemPara, "DepartmentIDs"), subitem2CompareFun);
var subitemCompareFun = Expression.Lambda<Func<ItemAnalyzeData, bool>>(subItemDepartmentIDsCall, subitemPara);
#endregion
#region Build outer most lambda expression : item => item.AnalyzeData.Any( ... )
var itemExpr = Expression.Parameter(typeof(Item), "item");
var analyzeAnyMethodCall = anyPredicate.MakeGenericMethod(typeof(ItemAnalyzeData));
var itemAnalyzeDataCall = Expression.Call(analyzeAnyMethodCall, Expression.Property(itemExpr, "AnalyzeData"), subitemCompareFun);
var itemCompareFun = Expression.Lambda<Func<Item, bool>>(itemAnalyzeDataCall, itemExpr);
#endregion
var method = itemCompareFun.Compile();
var actualLambdaCode = itemCompareFun.ToString();
var expectLambdaCode = expectedExpression.ToString();
Assert.AreEqual(expectLambdaCode, actualLambdaCode);
Assert.IsFalse(method.Invoke(item1));
Assert.IsTrue(method.Invoke(item2));
}