How can I convert expression tree to Dictionary?
For example:
class Dummy
{
public int Id { get; set; }
public string Name { get; set; }
}
Example 1:
MyStaticClass.ParseExpression<Dummy>(t => t.Id == 2)
//Result is dictionary with item:
<key, value>Id,2
Example 2:
var s = "Foo";
MyStaticClass.ParseExpression<Dummy>(t => t.Id == 2 && t.Name == s)
//Result is dictionary with items:
<key, value>Id,2
<key, value>Name,"Foo"
I know EF Core does this, but don't know how, and source code is to complicated for me to parse it.
I should say expression doesn't contain || and ().
For example:
MyStaticClass.ParseExpression<Dummy>(t => t.Id == 2 || t.Id == 3)
or
MyStaticClass.ParseExpression<Dummy>(t => t.Id == 2 && (Name == "Foo" || Id Name == "Test")
If you are sure that expressions will be in provided format only - you can do something like this:
public class Dummy
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
public class ExpressionConverter
{
public static Dictionary<string, string> Convert<T>(Expression<Func<T,bool>> expression)
{
var result = new Dictionary<string,string>();
var current = (BinaryExpression)expression.Body;
while (current.NodeType != ExpressionType.Equal)
{
ParseEquals((BinaryExpression)current.Right);
current = (BinaryExpression)current.Left;
}
ParseEquals(current);
void ParseEquals(BinaryExpression e)
{
var key = (MemberExpression) e.Left;
var value = (ConstantExpression) e.Right;
result.Add(key.Member.Name, value.Value.ToString());
}
return result;
}
}
Usage:
var test = ExpressionConverter.Convert<Dummy>(x => x.Id == 5 && x.Name == "dummy" && x.Age == 11);
Or replace ParseEquals:
void ParseEquals(BinaryExpression e)
{
var key = (MemberExpression) e.Left;
object value;
switch (e.Right)
{
case ConstantExpression constantExpression:
value = constantExpression.Value;
break;
case MemberExpression memberExpression:
var obj = ((ConstantExpression)memberExpression.Expression).Value;
value = obj.GetType().GetField(memberExpression.Member.Name).GetValue(obj);
break;
default:
throw new UnknownSwitchValueException(e.Right.Type);
}
result.Add(key.Member.Name, value);
}
To support:
var myVar = "dummy";
var test = ExpressionConverter.Convert<Dummy>(x => x.Id == 5 && x.Name == myVar && x.Age == 11);
Related
I have method that gets data from Db with nested collection
Here is model
public class FilterOptionDto
{
public Guid Id { get; set; }
public string FilterName { get; set; }
public ICollection<OptionDto> Options { get; set; }
}
Here is method that get's data
public async Task<List<FilterOptionDto>?> SetFilterOptions(SetFilterOptionsInputDto input)
{
var useCase = await _dbContext.UseCases.FirstOrDefaultAsync(x => x.Id == input.UseCaseId);
var dimensionsConfiguration =
_analysisDimensionsProvider.AnalysisDimensionsSetting.FirstOrDefault(x => x.FieldNameDomain == input.Name);
if (dimensionsConfiguration != null)
{
var filters = _mapper.Map<List<FilterOptionDto>>(await _dbContext.VwAnalysisUseCaseFilters.Where(x =>
x.JobId == useCase!.JobId && x.IsUseCaseFilter == true && x.FilterType == "multiselect" &&
x.IsAvailable && x.FieldNameDomain == input.Name)
.ToListAsync());
foreach (FilterOptionDto item in filters)
{
item.Options = await GetOptions(useCase?.JobId, dimensionsConfiguration.Source,
dimensionsConfiguration.FieldNameDomain);
}
if (!string.IsNullOrEmpty(input.SearchInput))
{
filters = filters.Where(x => x.Options.Any(x => x.Value.Contains(input.SearchInput))).ToList();
}
// filters = filters.Where(x => x.FilterName.Contains(input.Name)).ToList()
// .GetRange(input.Offset, input.Offset + 3);
return filters;
}
return null;
}
on this line I need to get range on nested collection Options from model, how I can do this?
// filters = filters.********
// .GetRange(input.Offset, input.Offset + 3);
You can chain Select, Skip, and Take.
filters.Select(x => x.Options.Skip(input.Offset).Take(3));
I want to combine some separated lambda expressions and build one final expression of them.
example classes :
class Address {
public string city { get; set; }
public string country { get; set; }
}
class ClassA {
public int Id { get; set; }
public Address address { get; set; }
}
class ClassB {
public int Id { get; set; }
public ClassA objectA { get; set; }
}
each class have one lambda expression :
Expression<Func<ClassA,bool>> classARule = a =>
a.Id > 1 && a.address.city == "city1" || a.address.country == "us"
Expression<Func<ClassB,bool>> classBRule = b => b.Id == 100
because ClassB has one property of ClassA it's possible to create an expression with both conditions. example :
// I want to create this expected object at runtime using classARule and classBRule
Expression<Func<ClassB,bool>> expected = b =>
(b.Id == 100) &&
(b.objectA.Id > 1 && b.objectA.address.city == "city1" || b.objectA.address.country == "us")
if I want to generate expected expression at runtime I should somehow convert a parameter of classARule to b.objectA
the problem is I know how to combine two expressions but I don't know how to replace a parameter with some other object. in this case b.objectA
Update - To avoid more confusion
the goal is to achieve Expression<Func<ClassB,bool>> expected expression at runtime using classARule and classBRule
Fortunately, I solved the problem.
The final result here is for others if they encounter such a problem.
public static Expression<Func<B, bool>> Combine<B, A>(this Expression<Func<B, bool>> expr1, Expression<Func<A, bool>> expr2, Expression<Func<B, A>> property)
{
// this is (q) parameter of my property
var replaceParameter = property.Parameters[0];
// replacing all (b) parameter with the (q)
// these two lines converts `b => b.Id == 100` to `q => q.Id == 100`
// using ReplaceExpVisitor class
var leftVisitor = new ReplaceExpVisitor(replaceParameter);
var left = leftVisitor.Visit(expr1.Body);
// the property body is 'q.objectA'
var replaceBody = property.Body;
// now i'm replacing every (a) parameter of my second expression to 'q.objectA'
// these two lines convert this statement:
// a.Id > 1 && a.address.city == "city1" || a.address.country == "us"
// to this :
// q.objectA.Id > 1 && q.objectA.address.city == "city1" || q.objectA.address.country == "us"
var rightVisitor = new ReplaceExpVisitor(replaceBody);
var right = rightVisitor.Visit(expr2.Body);
// creating new expression and pass (q) reference to it (replaceParameter).
return Expression.Lambda<Func<B, bool>>(Expression.AndAlso(left, right), replaceParameter);
}
// this is a simple class to replace all parameters with new expression
private class ReplaceExpVisitor : ExpressionVisitor
{
private readonly Expression _newval;
public ReplaceExpVisitor(Expression newval) => _newval = newval;
protected override Expression VisitParameter(ParameterExpression node)
{
return _newval;
}
}
usage :
var result = classBRule.Combine(classARule, q => q.objectA);
// or
Expression<Func<ClassB,bool>> result =
Combine<ClassB, ClassA>(classBRule, classARule, q => q.objectA);
/*
result is equal to the expected expression in the first example now
result output :
q =>
((q.Id == 100) &&
(((q.objectA.Id > 1) && (q.objectA.address.city == "city1")) ||
(q.objectA.address.country == "us")))
*/
https://dotnetfiddle.net/KnV3Dz
You'll need to compile the expression:
class Address
{
public string city { get; set; }
public string country { get; set; }
}
class ObjectA
{
public int Id { get; set; }
public Address address { get; set; }
}
class ObjectB
{
public int Id { get; set; }
public ObjectA objectA { get; set; }
}
Expression<Func<ObjectB, bool>> expected = b =>
(b.Id == 100) &&
(b.objectA.Id > 1 && b.objectA.address.city == "City1" || b.objectA.address.country == "US");
// Compile the Expression
var expectedItems = expected.Compile();
List<ObjectB> objBs = new List<ObjectB>();
var address = new Address();
var objA = new ObjectA();
var objB = new ObjectB();
address.city = "City1";
address.country = "US";
objA.Id = 1;
objB.Id = 100;
objA.address = address;
objB.objectA = objA;
objBs.Add(objB);
address = new Address();
objA = new ObjectA();
objB = new ObjectB();
address.city = "City2";
address.country = "US";
objA.Id = 3;
objB.Id = 100;
objA.address = address;
objB.objectA = objA;
objBs.Add(objB);
// Use expectedItems
var result = objBs.FirstOrDefault(b => expectedItems(b));
For sure very simple question for most of you.
But I am struggling with a solution at the moment.
Imagine you have a list of cats (List) where each cat has a list of babys (Kitten)
public class Cat
{
public string Name { get; set; }
public int Age { get; set; }
public string Race { get; set; }
public bool Gender { get; set; }
public List<Kitten> Babys { get; set; }
}
public class Kitten
{
public string Name { get; set; }
public double Age { get; set; }
public bool Gender { get; set; }
}
now I want to find the Cat that has the most matches for given requirements. It could easily be the case that a cat matches only 2 of 3 requirements. I simple want to find the cat that has the most matches to my requirements.
where my requirements could be:
Name has to be "Micky"
Age is 42
Has a Kitten named "Mini"
My actual solution would be to compare all properties and take the one with the highest count of matching properties. But this is not generic and I am sure there are mutch better ways to do it.
Thanks in advance
Well, I have no opportunity to test this solution, but you can try this:
Assume that you have a list of cats:
var cats = new List<Cat>();
Now you have defined what are your criteria:
var desiredName = "Micky";
var desiredAge = 42;
var desiredKitten = "Mini";
And then you have to get your desired cat:
var desiredCat = cats
.Select(c => new {
Rating =
Convert.ToInt32(c.Age == desiredAge) + // Here you check first criteria
Convert.ToInt32(c.Name == desiredName) + // Check second
Convert.ToInt32(c.Babys.Count(b => b.Name == desiredKitten) > 0), // And the third one
c })
.OrderByDescending(obj => obj.Rating) // Here you order them by number of matching criteria
.Select(obj => obj.c) // Then you select only cats from your custom object
.First(); // And get the first of them
Please check if this works for you.
And if you need more specific answer or some edits for me to add.
If you really will compare 2 ou 3 requirements you can simplify using Linq by:
// try to find with 3 requirements
var foundCats = catList.Where(t => t.Name == desiredName &&
t.Age == desiredAge &&
t.Babys.Any(k => k.Name == desiredKitten)
).ToList();
if (foundCats.Any())
{
// you found the desired cat (or cats)
return foundCats;
}
// try to find with 2 requirements
foundCats = catList.Where(t =>
(t.Name == desiredName && t.Age == desiredAge) ||
(t.Name == desiredName && t.Babys.Any(k => k.Name == desiredKitten)) ||
(t.Age == desiredAge && t.Babys.Any(k => k.Name == desiredKitten)
).ToList();
if (foundCats.Any())
{
// you found the desired cat (or cats)
return foundCats;
}
// try to find with only 1 requirement
foundCats = catList.Where(t => t.Name == desiredName ||
t.Age == desiredAge ||
t.Babys.Any(k => k.Name == desiredKitten)
).ToList();
return foundCats;
So, I see that the problem is you don't know if in any near future you will have more properties, so I will suggest going to the hardway and make reflection, the following is ugly af but you can probably (you will) make it better and hopefully serves you well as guiadance:
public static List<Cat> CheckProperties(List<Cat> inCatList, Cat inQueryCat)
{
Dictionary<Cat, List<PropertyInfo>> dict = new Dictionary<Cat, List<PropertyInfo>>();
foreach (PropertyInfo pI in inQueryCat.GetType().GetProperties())
{
var value = pI.GetValue(inQueryCat);
if (value != null)
{
var cats = inCatList.Where(cat => cat.GetType().GetProperty(pI.Name).GetValue(cat).Equals(value));
foreach (Cat cat in cats)
{
if (dict.ContainsKey(cat))
{
dict[cat].Add(pI);
}
else
{
dict.Add(cat, new List<PropertyInfo>() {pI});
}
}
}
}
int max = Int32.MinValue;
foreach (KeyValuePair<Cat, List<PropertyInfo>> keyValuePair in dict)
{
if (keyValuePair.Value.Count > max)
{
max = keyValuePair.Value.Count;
}
}
return dict.Where(pair => pair.Value.Count == max).Select(pair => pair.Key).ToList();
}
While this is the most generic solution there is (need some edge case improvements):
public class ReflectCmpare
{
public PropertyInfo PropertyInfo { get; set; }
public dynamic Value { get; set; }
}
public Cat GetBestCat(List<Cat> listOfCats, List<ReflectCmpare> catParamsToCompare, List<ReflectCmpare> kittensParamsToCompare)
{
var bestScore = 0;
var ret = listOfCats[0];
foreach (var cat in listOfCats)
{
var score = catParamsToCompare.Sum(param => param.PropertyInfo.GetValue(cat, null) == param.Value ? 1 : 0);
foreach (var baby in cat.Babys)
{
score+= kittensParamsToCompare.Sum(param => param.PropertyInfo.GetValue(baby, null) == param.Value ? 1 : 0);
}
if (score <= bestScore) continue;
bestScore = score;
ret = cat;
}
return ret;
}
You should really think about just doing simple compare function
considering this objects is not dynamic this is the way to go:
public Cat GetBestCat(List<Cat> listOfCats, string name , int? age , bool? gender, string race ,string babyName,int? babyAge,bool? babyGender )
{
var ret = listOfCats[0];
var highestScore = 0;
foreach (var cat in listOfCats)
{
var score = 0;
score += name != null && cat.Name.Equals(name) ? 1 : 0;
score += age.HasValue && cat.Age.Equals(age.Value) ? 1 : 0;
score += gender.HasValue && cat.Gender.Equals(gender.Value) ? 1 : 0;
score += race != null && cat.Race.Equals(race) ? 1 : 0;
score += name != null && cat.Name.Equals(name) ? 1 : 0;
score += cat.Babys
.Where(k => babyName==null || k.Name.Equals(babyName))
.Where(k => !babyAge.HasValue || k.Age.Equals(babyAge.Value))
.Any(k => !babyGender.HasValue || k.Gender.Equals(babyGender.Value))?1:0;
if (score <= highestScore) continue;
highestScore = score;
ret = cat;
}
return ret;
}
I'm trying to make some article-filtering based on properties that are stored in another dbset. I'm using some classes:
public class Article
{
public string ArticleCode { get; set; }
public string var1 { get; set; }
public string var2 { get; set; }
public string var3 { get; set; }
public virtual List<ArticleProperty> Properties { get; set; }
}
public class ArticleProperty
{
public string ArticleCode { get; set; }
public string PropertyCode { get; set; }
public string var4 { get; set; }
public string var5 { get; set; }
public string var6 { get; set; }
}
public class ArticleSummary
{
public string ArticleCode { get; set; }
public string var7 { get; set; }
public string var8 { get; set; }
}
public class WebDbContext : DbContext
{
public virtual DbSet<Article> Article{ get; set; }
public virtual DbSet<ArticleProperty> ArticleProperty{ get; set; }
/* some more code */
}
When I create a query like this, it does what I want to do:
IQueryable<ArticleSummary> Articles = _DbContext.Article
.Where(a => a.var1 == SomeLocalVariable1)
.Where(a => a.var2 == SomeLocalVariable2 || a.var2 == SomeLocalVariable3)
.Where(a =>
a.Properties.Any(ap =>
(
(ap.ArticleCode == a.ArticleCode && ap.var4 == "A" && ap.var5 == "X") ||
(ap.ArticleCode == a.ArticleCode && ap.var4 == "A" && ap.var5 == "Y")
)
)
&&
a.Eigenschappen.Any(ap =>
(
(ap.ArticleCode == a.ArticleCode && ap.var4 == "B" && ap.var5 == "Z")
)
)
)
.OrderByDescending(a => a.var6)
.Select(a => new ArticleSummary
{
ArticleCode = a.ArticleCode ,
var7 = a.var1
var8 = a.var3
});
But now I want to create the last Where-statement dynamically, like this (dataFilter is a Dictionary< string, Dictionary< string, bool>> with some filter-properties):
var query ="";
bool firstA = true;
foreach (KeyValuePair<string, Dictionary<string, bool>> filter in dataFilter)
{
if (firstA)
query += "a => ";
else
query += " && ";
query += "a.Properties.Any(ap =>"
+ "(";
bool firstB = true;
foreach (KeyValuePair<string,bool> filterDetail in filter.Value)
{
if (!firstB)
query += " || ";
query += "(ap.ArticleCode == a.ArticleCode && ap.var4 == \""+filter.Key+"\" && ap.var5 == \""+filterDetail.Key+"\")";
firstB = false;
}
query += ")"
+ ")";
firstA = false;
}
IQueryable<ArticleSummary> Articles = _DbContext.Article
.Where(a => a.var1 == SomeLocalVariable1)
.Where(a => a.var2 == SomeLocalVariable2 || a.var2 == SomeLocalVariable3)
.Where(query)
.OrderByDescending(a => a.var6)
.Select(a => new ArticleSummary
{
ArticleCode = a.ArticleCode ,
var7 = a.var1
var8 = a.var3
});
The 'query' is as expected but the Where doesn't work, the error:
System.Linq.Dynamic.Core.Exceptions.ParseException: 'No applicable aggregate method 'Any' exists'
This only occurs when there are 2 'Any'- statements (divided by &&, the same as when I do it 'hard-coded'). I don't know why...
Dynamic LINQ has its own expression language. Lambda expressions do not start with a => or ap =>, there is something called current scope which simplifies some queries, but in general is problematic with accessing outer level parameters. All queryable extensions define single scoped parameter called it, which can be omitted.
Shortly, Dynamic LINQ is not well suitable for complex queries with nested lambda expression accessing outer lambda parameters.
The goal can be achieved relatively easy with the combination of compile time and runtime expressions. The idea is simple.
First you create a compile time lambda expression with additional parameters which serve as placeholders. Then you replace the placeholders with actual expressions using the following simple expression visitor:
public static class ExpressionExtensions
{
public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
{
return new ParameterReplacer { Source = source, Target = target }.Visit(expression);
}
class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression Source;
public Expression Target;
protected override Expression VisitParameter(ParameterExpression node)
=> node == Source ? Target : base.VisitParameter(node);
}
}
Pretty much like string.Format, but with expressions. Then you can use Expression.AndAlso and Expression.OrElse to produce the && and || parts.
With that being said, here is how it looks in your case:
Expression<Func<Article, string, string, bool>> detailExpr = (a, var4, var5) =>
a.Properties.Any(ap => ap.ArticleCode == a.ArticleCode && ap.var4 == var4 && ap.var5 == var5);
var p_a = detailExpr.Parameters[0];
var p_var4 = detailExpr.Parameters[1];
var p_var5 = detailExpr.Parameters[2];
var body = dataFilter
.Select(filter => filter.Value
.Select(filterDetail => detailExpr.Body
.ReplaceParameter(p_var4, Expression.Constant(filter.Key))
.ReplaceParameter(p_var5, Expression.Constant(filterDetail.Key)))
.Aggregate(Expression.OrElse))
.Aggregate(Expression.AndAlso);
var predicate = Expression.Lambda<Func<Article, bool>>(body, p_a);
Then use Where(predicate) in place of your current Where(query).
Instead of using a string, just use the query directly:
IQueryable<ArticleSummary> Articles = _DbContext.Article
.Where(a => a.var1 == SomeLocalVariable1)
.Where(a => a.var2 == SomeLocalVariable2 || a.var2 == SomeLocalVariable3)
.Where(query);
foreach(...) {
Articles = Articles.Where(...);
}
Articles = Articles.OrderByDescending(a => a.var6)
.Select(a => new ArticleSummary
{
ArticleCode = a.ArticleCode ,
var7 = a.var1
var8 = a.var3
});
I have sucha a situation. I have list inside which I am holding objects of one class (this objects has 6 properties "Name", "Type", "Index", "Value", "Status", "Parameter"). Later I am binding it with gridView.
Now I would like to be able to make a filter for each of this properties. I want to be able for example to insert into textbox "Name" : John, and I would like gridView to display only rows where I have John.
Second thing is that I would like to be able to mix filters, so have for example "Name" set to : 'John' and "Index" to : 5, and display "John" with "Index" : 5.
How can I do this?
Now I only have function to insert everything into list. This objects are stored inside Middle property of class WholeLine.
Correction method = new Correction();
while ((line = sr.ReadLine()) != null)
{
string[] _columns = line.Split(",".ToCharArray());
object returnValue;
MyColumns mc = new MyColumns();
mc.Time = _columns[0];
mc.Information = _columns[1];
mc.Description = _columns[2];
if (String.IsNullOrEmpty(mc.Information) )
{ continue; }
else
{
returnValue = method.HandleRegex(mc.Information);
}
Line main = new Line();
main.LeftColumn = mc.Time;
main.Middle= returnValue;
main.RightColumn = mc.Description;
list3.Add(main);
}
EDIT:
It is not that simple in my situation (I think...), because I have main class where I have this while shown above. Later I call method HadleRegex from class Correction . Bellow I will show how it looks like:
class Correction
{
private MoreInfor _MoreInfor = new MoreInfor();
public MoreInfor MoreInfor { get { return _ID; } }
Correction sk = new Correction();
Match matches = Regex.Match(newStr, #"\b(?:complete\b)", RegexOptions.IgnoreCase);
if (matches.Success)
{
string[] lines = newStr.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
Regex regex1 = new Regex(#"^(?:(?<C0>Command) (?:answer|sent) from (?<C1>\S+) to (?<C2>\S+) (?<C3>.+))$");
var matches1 = lines.Select(line => regex1.Match(line));
foreach (var match in matches1)
{
sk._MoreInfor.Name= match.Groups["C1"].ToString();
sk._MoreInfor.Type = match.Groups["C2"].ToString();
sk._MoreInfor.Value = match.Groups["C3"].ToString();
sk._MoreInfor.Index = match.Groups["C4"].ToString();
}
}
return sk;
}
public class MoreInfo
{
public string Name{ get; set; }
public string Type { get; set; }
public string Index { get; set; }
public string Value { get; set; }
public string Status { get; set; }
public string Parameter { get; set; }
}
This returned vale of class Correction is later passed to returnValue from my main class and added to Middle property of class Line
I'm sorry if I really messed up!
You can use linq - where
var result = list.Where(x => x.Name == textBoxName.Text).ToList();
This assumes your textbox will search for a name
For multiple filters,
list.Where(x => x.Property == "something" && x.Name == textBoxName.Text).ToList();
list.Where(x => result.Where(ix => x.Property == "something").ToList();
You may define some "NoFilter" values. For instance index may be -1, or Name = ""
public List<person> FilterPersons (List<person> persons,
string name = "",
string type = "",
int index = -1,
int value = -1,
int status = -1,
string parameter = "")
{
return persons
.Where
(
x=>
(name == "" || x.Name == name)
&& (type == "" || x.Type == type)
&& (index == -1 || x.Index == index)
&& (value == -1 || x.Value == value)
&& (status == -1 || x.Status == status)
&& (parameter == "" || x.Parameter == parameter)
)
.Select(x=>x);
}
And then you may call it as:
filterPersons = FilterPersons(persons: persons,name: "John");
or
filterPersons = FilterPersons(persons: persons,index: 10, status: 5);
use linq to filer
yourlist.Select(x=>x.Name == "John");