Related
I want to combile two expressions :
public Expression<Func<AnyType,bool>> BuildExpression(int id, string filtreA, string filtreB)
{
Expression<Func<AnyType, bool>> extraExpression = null;
Expression<Func<AnyType, bool>> expression = null;
expression = a => a.ID == id;
var fullExpression = expression.Body;
if (!String.IsNullOrEmpty(filtreA))
{
extraExpression = a => a.TYPE == filtreA;
fullExpression = Expression.AndAlso(fullExpression, extraExpression.Body);
}
if (!String.IsNullOrEmpty(filtreB))
{
extraExpression = a => a.TYPE == filtreB;
fullExpression = Expression.AndAlso(fullExpression, extraExpression.Body);
}
expression = Expression.Lambda<Func<AnyType, bool>>(fullExpression, expression.Parameters[0]);
}
return expression;
}
//I want my final expression to be a.ID == id && a.TYPE == filtreB for example
The problem is that I get the following error : System.InvalidOperationException. Le paramètre 'a' n'est pas dans la portée.. It only happens if enter in one of my ifs.
Does anyone know how I can handle this ? Thanks
ps: My question il similar to this post but it looks like the solution doesn't work anymore : Combining two expressions (Expression<Func<T, bool>>)
You have missed parameters replacement. It is a tricky with visitors.
Trying to simplify your life, I would suggest to use popular library LINQKit
and rewrite your expression building.
public Expression<Func<AnyType, bool>> BuildExpression(int id, string filtreA, string filtreB)
{
var predicate = PredicateBuilder.New<AnyType>(true);
predicate = predicate.And(a => a.ID == id);
if (!string.IsNullOrEmpty(filtreA))
{
predicate = predicate.And(a => a.TYPE == filtreA);
}
if (!string.IsNullOrEmpty(filtreB))
{
predicate = predicate.And(a => a.TYPE == filtreB);
}
return predicate;
}
Each parameter is a different ParameterExpression instance. This problem would be more obvious if you had defined each expression with different parameter names;
expression = a => a.ID == id;
// ...
extraExpression = b => b.TYPE == filtreA;
// ...
extraExpression = c => c.TYPE == filtreB;
Note that entity framework core 2.1 and earlier didn't care if your parameters were different, and would seem to look through .Invoke operations. But since version 3, with an internal rewrite of the expression compiler, neither of those are supported. Hence why earlier examples may not work anymore.
An expression visitor to swap parameter expressions isn't too complicated;
public class MapParameters : ExpressionVisitor
{
private readonly Dictionary<ParameterExpression, ParameterExpression> mapping;
public MapParameters(IEnumerable<ParameterExpression> before, IEnumerable<ParameterExpression> after)
{
this.mapping = new Dictionary<ParameterExpression, ParameterExpression>(
before
.Zip(after)
.Select(p => KeyValuePair.Create(p.First,p.Second))
);
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (mapping.TryGetValue(node, out var replace))
return replace;
return base.VisitParameter(node);
}
}
Expression.Lambda<Func<AnyType, bool>>(
Expression.AndAlso(
expression.Body,
new MapParameters(
extraExpression.Parameters,
expression.Parameters
).Visit(extraExpression.Body)
),
expression.Parameters);
Also note that if you are using this to build a single expression for IQueryable<T>.Where(...). You could consider calling .Where(...) twice, which would achieve the same end result.
Consider this code:
public List<Clients> GetFilteredClients(DateTime? FromDate = null,
DateTime? ToDate = null,
int? fromLocationType = null,
int? toLocationType = null)
{
Func<Clients, bool> fromDateFilter = f => true;
if (FromDate.HasValue)
{
fromDateFilter = z => z.Insert_Date.Value.Date >= FromDate.Value.Date;
}
Func<Clients, bool> toDateFilter = f => true;
if (ToDate.HasValue)
{
toDateFilter = z => z.Insert_Date.Value.Date <= ToDate.Value.Date;
}
Func<Clients, bool> fromLocationTypeFilter = f => true;
if (fromLocationType.HasValue)
{
fromOrgFilter = z => z.LocationTypeId >= fromLocationType.Value;
}
Func<Clients, bool> toLocationTypeFilter = f => true;
if (toLocationType.HasValue)
{
toLocationTypeFilter = z => z.LocationTypeId <= toLocationType.Value;
}
var filtered = DB_Context.Clients
.Where(fromDateFilter)
.Where(toDateFilter)
.Where(fromLocationTypeFilter)
.Where(toLocationTypeFilter)
.OrderByDescending(k => k.Id)
.Take(1000)
.ToList();
return filtered;
}
I have something like 100K records in the DB, I need only the top 1000 that answer to the requirements of:
.Where(fromDateFilter)
.Where(toDateFilter)
.Where(fromLocationTypeFilter)
.Where(toLocationTypeFilter)
However the execution time still takes something like 10 seconds.
Any idea why?
You must use Expression<Func<...>> rather than Func<...>. When you use Func, only the enumerable methods can be used on the queryable, which in this case means you first download everything to memory, and then do the filtering. If you switch over to Expression<...>, the O/RM will do the filtering on the DB server, rather than in your application.
Also, there's better ways to do what you're doing. For example, you can build the conditions like so:
var query = DB_Context.Clients.AsQueryable();
if (FromDate.HasValue) query = query.Where(...);
if (ToDate.HasValue) query = query.Where(...);
...
return query.OrderByDescending(k => k.Id).Take(1000).ToList();
Of course, this means that whatever DB provider you're using must be able to support the kind of filtering you're trying to do - you'll need to consult the documentation.
You are using delegates instead LINQ expressions. That leads to processing a data by your application and not by SQL Server.
LINQ expressions look like lambda expressions thanks for the syntax, but they are not same thing. The compiler takes a decision what to create (delegates or LINQ expressions) depending on the situation.
If an object implements the IQueriable interface, then the compiler uses the Queryable class and generates LINQ expression trees, which later can be translated into a SQL query or other form by the specific IQueryProvider.
Otherwise, the compiler uses extensions from the Enumerable class, which create iterators over source collection (all records from the table in your case).
As an example. The code bellow will be compilled into LINQ expressions.
// Source code
IQueryable<Clients> source = null;
IQueryable<Clients> result = source.Where(c => c.LocationTypeId >= 1);
// Compiller generated code
IQueryable<Clients> source = null;
Expression parameterC = Expression.Parameter(typeof(Clients), "c");
IQueryable<Clients> result = Queryable.Where<Clients>(
source,
Expression.Lambda<Func<Clients, bool>>(
Expression.LessThanOrEqual(
Expression.Property(
parameterC ,
typeof(Clients).GetProperty("LocationTypeId").GetGetMethod()
),
Expression.Constant(1, typeof(int))
),
new ParameterExpression[]
{
parameterC
}
);
And this code uses delegates:
// Source code
IQueryable<Clients> source = null;
Func<Clients, bool> filter = c => c.LocationTypeId >= 1;
IEnumerable<Clients> result = source.Where(filter );
// Compiller generated code
IQueryable<Clients> source = null;
Func<Clients, bool> filter = c => c.LocationTypeId >= 1;
IEnumerable<Clients> result = Enumerable.Where(source, filter);
So, to solve you problem use Expression<Func<Clients, bool>> instead of Func<Clients, bool>:
IQueryable<Clients> result = DB_Context.Clients;
if (someFilter.HasValue)
result = result.Where(c => c.SomeProperty == someFilter.Value);
// other filters
return query
.OrderByDescending(k => k.Id)
.Take(1000)
.ToList();
In our database we have a number of tables which have corresponding Translation tables, with language and region IDs (mapped to other tables) with language 1 being English and the default region of language 1 being UK. All tables which have a translation table have the following default columns (although no interface has been defined on the entity framework classes):
<EntityTableName>
EntityTableNameID INT PK
Reference NVARCHAR NULL
[Any other columns]
<EntityTableNameTranslation>
EntityTableNameID INT NOT NULL
LanguageID INT NOT NULL
RegionID INT NULL
Title NVARCHAR NOT NULL
Description NVARCHAR NULL
The naming is consistent throughout the database, so we could add interfaces if required, but for now I've been trying to do it without to save the effort.
The logic for determining which translation title & description to return is:
1) If there is an exact match for both the language and region, return it
2) If there is a match for the language, but not the region, return the "default" for that language (which is where the RegionID is null, and there will always be one for every language)
3) If there is no match for language, just return the system default (LanguageID = 1, RegionID IS NULL).
I know this might all sound weird and everyone has better ways of doing it, but this is the brief I have to work with. So this is the lambda group join function I created, which is using an entity in the database called "OrgGroup":
public static IEnumerable<TransViewModel> GetUserAreaOrgGroups(TransTestEntities context, int companyID, int languageID, int? regionID)
{
var transFull = context.OrgGroupTranslations.Where(tr => tr.LanguageID == languageID && tr.RegionID == regionID);
var transLang = context.OrgGroupTranslations.Where(tr => tr.LanguageID == languageID && !tr.RegionID.HasValue);
var transDefault = context.OrgGroupTranslations.Where(tr => tr.LanguageID == 1 && !tr.RegionID.HasValue);
var results = context.OrgGroups.Where(en => en.CompanyID == companyID)
.GroupJoin(transFull, en => en.OrgGroupID, tr => tr.OrgGroupID,
(en, tr) => new TransJoin<OrgGroup, OrgGroupTranslation> { Entity = en, TransFull = tr.DefaultIfEmpty().FirstOrDefault(), TransLang = null, TransDefault = null})
.GroupJoin(transLang, en => en.Entity.OrgGroupID, tr => tr.OrgGroupID,
(en, tr) => new TransJoin<OrgGroup, OrgGroupTranslation> { Entity = en.Entity, TransFull = en.TransFull, TransLang = tr.DefaultIfEmpty().FirstOrDefault(), TransDefault = null })
.GroupJoin(transDefault, en => en.Entity.OrgGroupID, tr => tr.OrgGroupID,
(en, tr) => new TransJoin<OrgGroup, OrgGroupTranslation> { Entity = en.Entity, TransFull = en.TransFull, TransLang = en.TransLang, TransDefault = tr.DefaultIfEmpty().FirstOrDefault() })
.Select(vm => new TransViewModel
{
EntityID = vm.Entity.OrgGroupID,
Title = (vm.TransFull ?? vm.TransLang ?? vm.TransDefault).Title,
Description = (vm.TransFull ?? vm.TransLang ?? vm.TransDefault).Description
});
return results;
}
Which seems to work as expected, and now I'm trying to convert this into a function which will accept the two table types and use expression trees to create, execute, and return the equivalent query. I've got as far as:
public static IEnumerable<TransViewModel> GetUserAreaTranslations<TEntity, TTrans>(TransTestEntities context, int companyID, int languageID, int? regionID)
{
// Get types
Type entityType = typeof(TEntity);
Type transType = typeof(TTrans);
string entityName = entityType.Name;
string transName = transType.Name;
// Parameters
var entityParam = Expression.Parameter(entityType, "en");
var transParam = Expression.Parameter(transType, "tr");
var combinedParam = new ParameterExpression[] { entityParam, transParam };
// Properties
var CompanyIDProp = Expression.Property(entityParam, "CompanyID");
var entityIDProp = Expression.Property(entityParam, entityName + "ID");
var transIDProp = Expression.Property(transParam, entityName + "ID");
var transLanProp = Expression.Property(transParam, "LanguageID");
var transRegProp = Expression.Property(transParam, "RegionID");
var transTitleProp = Expression.Property(transParam, "Title");
var transDescProp = Expression.Property(transParam, "Description");
// Tables
//TODO: Better way of finding pluralised table names
var entityTable = Expression.PropertyOrField(Expression.Constant(context), entityName + "s");
var transTable = Expression.PropertyOrField(Expression.Constant(context), transName + "s");
// Build translation subqueries
//e.g. context.OrgGroupTranslations.Where(tr => tr.LanguageID == languageID && tr.RegionID == regionID);
MethodCallExpression fullTranWhereLambda = Expression.Call(typeof(Queryable),
"Where",
new Type[] { transType },
new Expression[]
{
transTable,
Expression.Quote
(
Expression.Lambda
(
Expression.AndAlso
(
Expression.Equal(transLanProp, Expression.Constant(languageID)),
Expression.Equal(transRegProp, Expression.Convert(Expression.Constant(languageID), transRegProp.Type))
), transParam
)
)
});
MethodCallExpression lanTranWhereLambda = Expression.Call(typeof(Queryable),
"Where",
new Type[] { transType },
new Expression[]
{
transTable,
Expression.Quote
(
Expression.Lambda
(
Expression.AndAlso
(
Expression.Equal(transLanProp, Expression.Constant(languageID)),
Expression.IsFalse(MemberExpression.Property(transRegProp, "HasValue"))
), transParam
)
)
});
MethodCallExpression defaultTranWhereLambda = Expression.Call(typeof(Queryable),
"Where",
new Type[] { transType },
new Expression[]
{
transTable,
Expression.Quote
(
Expression.Lambda
(
Expression.AndAlso
(
Expression.Equal(transLanProp, Expression.Constant(1)),
Expression.IsFalse(MemberExpression.Property(transRegProp, "HasValue"))
), transParam
)
)
});
MethodCallExpression entityWhereLambda = Expression.Call(typeof(Queryable),
"Where",
new Type[] { entityType },
new Expression[]
{
entityTable,
Expression.Quote(
Expression.Lambda
(
Expression.Equal(CompanyIDProp, Expression.Convert(Expression.Constant(companyID), CompanyIDProp.Type))
, entityParam
)
)
});
// Create the "left join" call:
// tr.DefaultIfEmpty().FirstOrDefault()
var joinType = typeof(TransJoin<TEntity, TTrans>);
var joinParam = Expression.Parameter(joinType, "tr");
var leftJoinMethods =
Expression.Call(
typeof(Enumerable),
"FirstOrDefault",
new Type[] { transType },
Expression.Call(
typeof(Enumerable),
"DefaultIfEmpty",
new Type[] { transType },
Expression.Parameter(typeof(IEnumerable<TTrans>), "tr"))
);
// Create the return bindings
var emptyTrans = Expression.Constant(null, typeof(TTrans));
//var emptyTrans = Expression.Constant(null);
var fullBindings = new List<MemberBinding>();
fullBindings.Add(Expression.Bind(joinType.GetProperty("Entity"), entityParam));
fullBindings.Add(Expression.Bind(joinType.GetProperty("TransFull"), leftJoinMethods));
fullBindings.Add(Expression.Bind(joinType.GetProperty("TransLang"), emptyTrans));
fullBindings.Add(Expression.Bind(joinType.GetProperty("TransDefault"), emptyTrans));
// Create an object initialiser which also sets the properties
Expression fullInitialiser = Expression.MemberInit(Expression.New(joinType), fullBindings);
// Create the lambda expression, which represents the complete delegate
Expression<Func<TEntity, TTrans, TransJoin<TEntity, TTrans>>> fullResultSelector =
Expression.Lambda <Func<TEntity, TTrans, TransJoin<TEntity, TTrans>>>(fullInitialiser, combinedParam);
// Create first group join
var fullJoin = Expression.Call(
typeof(Queryable),
"GroupJoin",
new Type[]
{
typeof (TEntity), // TOuter,
typeof (TTrans), // TInner,
typeof (int), // TKey,
typeof (TransJoin<TEntity, TTrans>) // TResult
},
new Expression[]
{
entityWhereLambda,
fullTranWhereLambda,
Expression.Lambda<Func<TEntity, int>>(entityIDProp, entityParam),
Expression.Lambda<Func<TTrans, int>>(transIDProp, transParam),
fullResultSelector
}
);
The problem is that groupjoin is expecting to return an IEnumerable of TTrans, which I don't seem to be able to bind, and I can't change it to a standard join because I won't be able to use the coalesce in the projection as no result will be returned.
I'm sure I'm doing something very dumb, so can someone help me get my group joins working please?
The expression node you're looking for is MemberInitExpression, which is what results from compiling a lambda containing a new { } statement.
Let's say we have a simple key-value class like this:
public class KV
{
public int Key;
public string Value;
}
I can build a new expression for this to load some constants like so:
Type tKV = typeof(KV);
MemberInfo miKey = tKV.GetMember("Key")[0];
MemberInfo miValue = tKV.GetMember("Value")[0];
Expression meminit =
Expression.MemberInit(
Expression.New(tKV),
Expression.Bind(miKey, Expression.Constant(1)),
Expression.Bind(miValue, Expression.Constant("Some Value"))
);
Or for the more complete version, constructing a lambda expression that fully initializes the variable:
public Expression<Func<int, string, KV>> InitKV()
{
var pK = Expression.Parameter(typeof(int), "k");
var pV = Expression.Parameter(typeof(string), "v");
Type tKV = typeof(KV);
MemberInfo miKey = tKV.GetMember("Key")[0];
MemberInfo miValue = tKV.GetMember("Value")[0];
Expression meminit =
Expression.MemberInit(
Expression.New(tKV),
Expression.Bind(miKey, pK),
Expression.Bind(miValue, pV)
);
return (Expression<Func<int, string, KV>>)Expression.Lambda(meminit, pK, pV);
}
In your case there are going to be a lot more Bind expressions in there.
Answer #2... this time with more actual answer :P
The problem appears to be that the types of the lambdas and so on that you are passing to the GroupJoin method are wrong.
Specifically:
// Create the lambda expression, which represents the complete delegate
Expression<Func<TEntity, TTrans, TransJoin<TEntity, TTrans>>> fullResultSelector =
Expression.Lambda<Func<TEntity, TTrans, TransJoin<TEntity, TTrans>>>(fullInitialiser, combinedParam);
...although some of the others also look a bit iffy, but that could just be me.
The selector expression expected by GroupJoin is of type Expression<Func<TEntity, IEnumerable<TTrans>, TransJoin<TEntity, TTrans>>>. It's going to pass in a single TEntity and a group of TTrans (as IEnumerable<TTrans>) even if there's only one instance in that group. Your expression tree needs to deal with that IEnumerable<TTrans> correctly, which it currently doesn't.
Are you sure you wanted a GroupJoin and not a Join here?
I wrote some code in LINQPad to test the concept. It's over at >PasteBin< if you want to look into it.
Incidentally, using LINQPad's Dump extension on an expression will give you a full breakout of how the expression is constructed. Assign a lambda to a variable of an appropriate Expression<Func<....>> type and then call Dump to see how it is constructed. Helps ferret out the usages and show what you need to do to construct it.
I have a few checkboxes on top of my webpage which correspond to the columns of my Table. Like there is a column studentId then there will be a checkbox studentId and so on. I want to write such a linq/lamda expression for List which will filter the on the basis of the checkboxes selected. For example if selects studentId and studentType checkbox then linq/lambda expression should bring all the rows matching the selection.
example:
If studentId and studentType checked then:
foreach (Child c in SomeList)
{
if (chkStudentId.checked)
{
List.FindAll (h=> h.StudentId == c.studentId);
}
if (chkStudentType.checked)
{
List.FindAll (h => h.StudentType == c.studentType)
}
}
}
I can't figure out how am I going to write such a code that if user selects multiple checkboxes, the query should compare to all the columns and bring the values based only on the checkboxes checked. The above is only static and does not help. please help. Thanks.
Expression trees are a great help if you want your query to be totally dynamic. But if the number of checkboxes is static you can also choose for the following solution:
var students = <your list of students>.AsQueryable();
if ( chkStudentId.checked)
{
students = students.Where(s => s.StudentId == c.StudentId);
}
if (chkStudentType.checked))
{
students = students.Where(s => s.StudentType== h.StudentType);
}
In such a way you can combine the where clauses in a dynamic way.
Disclaimer: I'm fairly new to this and there is probably much better ways to do this. Any feedback is higlhy appreciated.
Note that this method has no error/null checking. Since it uses reflection, you should profile it if you plan to use it in production.
public static IEnumerable<T> Filter<T>(IEnumerable<T> collection, Dictionary<string, object> filters) {
var type = typeof (T);
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
var queryable = collection.AsQueryable();
var instance = Expression.Parameter(type, "instance");
var expressions = new Stack<Expression>();
foreach (var filter in filters) {
var propertyName = filter.Key;
var property = properties.FirstOrDefault(x => x.Name == propertyName);
if (property == null)
continue;
var left = Expression.Property(instance, property);
var right = Expression.Constant(filter.Value, property.PropertyType);
var expr = Expression.Equal(left, right);
expressions.Push(expr);
}
Expression call = null;
Expression previousExpression = null;
while(expressions.Count > 0) {
var expr = expressions.Pop();
if(previousExpression == null) {
previousExpression = expr;
call = expr;
} else {
var and = Expression.AndAlso(previousExpression, expr);
call = and;
previousExpression = and;
}
}
var whereCallExpression = Expression.Call(
typeof(Queryable),
"Where",
new[] { queryable.ElementType },
queryable.Expression,
Expression.Lambda<Func<T, bool>>(call, new[] { instance }));
return queryable.Provider.CreateQuery<T>(whereCallExpression);
}
It overates all filters and tries to find a matching property. If it does find a property, it creates a EqualExpression which compares the actual value and the value you want to filter by. It then creates a MethodCallExpression which is passed to the query provider.
Theese expressions are then combined. I think the part with the stack is wrong, and that there is a better way to do it.
Usage:
var persons = new List<Person> {new Person {Name = "Alex", Age = 22}, new Person {Name = "Jesper", Age = 30}};
var filters = new Dictionary<string, object>();
filters.Add("Name", "Alexander Nyquist");
var results = Filter(persons, filters);
Since it's building expressions, it does works with Linq 2 Sql (tested) and probably Entity Framework. Linq 2 sql produces the following query:
SELECT [t0].[Id], [t0].[Name], [t0].[Email]
FROM [dbo].[Persons] AS [t0]
WHERE [t0].[Name] = #p0
-- #p0: Input VarChar (Size = 8000; Prec = 0; Scale = 0) [Alexander Nyquist]
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 4.0.30319.1
Hope this helps.
From your description of your problem it seemed fairly straight forward that you have a list of predicates, that are enabled or disable based on the check boxes, and then you want to aggregate them to create a single composite filter. Easy!
Your predicates look like this:
h => h.StudentId == c.studentId
h => h.StudentType == c.studentType
But because of the check boxes, you really want them to look like this:
h => chkStudentId.Checked ? h.StudentId == c.studentId : true
h => chkStudentId.Checked ? h.StudentType == c.studentType : true
You're effectively extending the predicates to include the check box. Here's a function that does that:
Func<CheckBox, Func<Student, bool>, Func<Student, bool>> extend =
(cb, p) =>
s => cb.Checked ? p(s) : true;
Now you can write your list of predicates like this:
var predicates = new Func<Student, bool>[]
{
extend(chkStudentId, h => h.StudentId == c.studentId),
extend(chkStudentType, h => h.StudentType == c.studentType),
// etc
};
Next LINQ has an easy way to turn a list of somethings into a single something:
Func<Student, bool>
predicate =
predicates.Aggregate((a, p) => s => a(s) && p(s));
Now you just have to execute this code to get your filtered list:
var filtered = SomeList.Where(predicate);
Now anytime any of the check boxes change you just need to enumerate the filtered collection and you'll get your filtered results.
Expression Trees can do what you want, but they are rather complicated. They allow you to dynamically build the linq query.
http://msdn.microsoft.com/en-us/library/bb882637.aspx
http://msdn.microsoft.com/en-us/library/bb397951.aspx
The Dynamic Linq Library may also be of use. I haven't personally used it though.
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
The solution that worked for me was a bit modification of what Wouter de Kort
suggest and was like this:
var students = StudentList.AsQueryable();
foreach (Student sc in GroupingList)
{
students = students.Where(s => (chkStudentId.Checked? h.StudentId.Trim() == sc.StudentId.Trim() : true) && (chkStudentType.Checked? h.StudentType.Trim() == sc.StudentType.Trim() : true) );
}
I have looked at few examples here Calling a Method from an Expression and on MSDN but I have not been able to get the right method call/object type for Any() for the query below. I seem to be able to get the property call but not IEnumerable part of the child property.
billing_map_set_lu is the parent of billmaps_lu and is defined as an association in the Entity Framework.
The reason I am using expression trees is that
I need to be able to define the query at runtime with 1-n .SelectMany(p => p.billmaps_lu).Where(predicate) clauses. So I figured if I could build the expression trees I could handle all the different combinations I have for this system which are many.
var myResults = ctx.billing_map_set_lu
.Where(p => p.billmaps_lu.Any(b => b.billmap_columnname == "templatesittings_key" && b.billmap_columnvalue == 428264))
SelectMany(p => p.billmaps_lu)
.Where (b =>b.billmap_columnname =="locations_key" && b.billmap_columnvalue == 12445)
Select(z => z.billing_map_set_lu);
I have tried a quite a few attempts using the samples above...
ParameterExpression bms = Expression.Parameter(typeof(billmaps_lu));
Expression left1 = Expression.Property(bms, typeof(billmaps_lu).GetProperty("billmap_columnname"));
Expression right1 = Expression.Constant("templatesittings_key", typeof(string));
Expression InsideAny1 = Expression.Equal(left1, right1);
Expression left2 = Expression.Property(bms, typeof(billmaps_lu).GetProperty("billmap_columnvalue"));
Expression right2 = Expression.Constant(428264, typeof(int));
Expression InsideAny2 = Expression.Equal(left2, right2);
Expression myWhereClause1 = Expression.AndAlso(InsideAny1, InsideAny2);
The above part seems fine but when I try to do the .Any It is like I can't get the right property/method to get the right objects out. (I feel like I am on a physics problem where I am working with the wrong units.) I am hoping it is something simple that I am missing, I am pretty new to Expression Trees.. I have included non-working code to try to show you where my head is at and how someone can steer me in the right direction.
MethodInfo method = typeof(Enumerable).GetMethods().Where(m => m.Name == "Any" && m.GetParameters().Length == 2).Single().MakeGenericMethod(typeof(billing_map_set_lu).GetProperty("billmaps_lu").PropertyType);
ParameterExpression billMapSetParameter = Expression.Parameter(typeof(billing_map_set_lu), "p");
ParameterExpression billMaps = Expression.Parameter(typeof(billmaps_lu), "p1");
var myFunction = Expression.Lambda<Func<billmaps_lu, bool>>(Expression.Call(method, Expression.Property(billMapSetParameter, typeof(billing_map_set_lu).GetProperty("billmaps_lu")), myWhereClause1), billMaps)
Disclaimer, I haven't got any compiled working code.
2 problems.
First problem probably lies in:
ParameterExpression billMapSetParameter = Expression.Parameter(typeof(billing_map_set_lu), "p");
That's not a parameter you need in:
Expression.Lambda<Func<billmaps_lu, bool>>(Expression.Call(method, Expression.Property(**billMapSetParameter**, typeof(billing_map_set_lu).GetProperty("billmaps_lu")), myWhereClause1), billMaps)
Change the billMapSetParameter to the billMaps ParamterExpression, then you should be good to go. You are calling the PropertyExpression to obtain your billMapSet for you from the ParameterExpression.
2nd problem: (Not sure, but my gut feeling)
You may need to pass the Where clause as a ConstantExpression with type Expression<.Func<>>. .Any method takes two parameters, of which, the second is an Expression<.Func<>> (Or just a Func<>? can't remember).
var whereExpression = Expression.Lambda<.Func<.billmaps_lu, bool>>(myWhereClause1, bms);
var ce = Expression.Constant(whereExpression)
Then pass back ce into originally where you "myWhereClause1" is.
Cross finger it works
Edit- Scrap that, SHOW MI ZEH CODEZ
public class Foo
{
public List<string> Strings { get; set; }
}
class Program
{
static void Main(string[] args)
{
Func<Foo, bool> func =
a => a.Strings.Any(b => b == "asdf");
// b => b == "asdf";
var bParameter = Expression.Parameter(typeof (string));
var asdfConstant = Expression.Constant("asdf");
var compare = Expression.Equal(bParameter, asdfConstant);
var compareExpression = Expression.Lambda<Func<string, bool>>(compare, bParameter);
var ceCompareExpression = Expression.Constant(compareExpression.Compile());
// a => a.Strings.Any(compareExpression)
var parameter = Expression.Parameter(typeof (Foo));
var foosProperty = Expression.Property(parameter, typeof (Foo).GetProperty("Strings"));
MethodInfo method = typeof(Enumerable).GetMethods().Where(m => m.Name == "Any" && m.GetParameters().Length == 2).Single().MakeGenericMethod(typeof(string));
var anyMethod = Expression.Call(method, foosProperty, ceCompareExpression);
var lambdaExpression = Expression.Lambda<Func<Foo, bool>>(anyMethod, parameter);
// Test.
var foo = new Foo {Strings = new List<string> {"asdf", "fdsas"}};
Console.WriteLine(string.Format("original func result: {0}", func(foo)));
Console.Write(string.Format("constructed func result: {0}", lambdaExpression.Compile()(foo)));
Console.ReadKey();
}
}