Linq Expression with multiple nested properties - c#

I have read through the question, and answer, Dynamic linq expression tree with nested properties. It does seem to be very similar, though a lack of understanding with Expressions is resulting in me not being able to translate the answer to my own scenario.
Given a class structure that looks a little like this:
public class Parent
{
public Parent()
{
ParentField = new HashSet<ParentField>();
}
public int ParentId { get; set; }
public ICollection<ParentField> ParentField { get; set; }
}
public class ParentField
{
public int ParentField { get; set; }
public Field Field { get; set; }
public string Value {get;set;}
}
public class Field
{
public int FieldId { get; set; }
public string Name { get; set; }
}
I am trying to build up a query that would be represented by this:
var query = _unitOfWork.ParentRepository.Queryable().Include("ParentField.Field");
query = query.Where(i =>
(
i.ParentField.Any(pField => pField.Field.Name.Equals("anId", StringComparison.OrdinalIgnoreCase)) &&
i.ParentField.Any(pField =>
// There may be multiple values to search for, so require the OR between each value
pField.Value.Equals("10", StringComparison.OrdinalIgnoreCase) || pField.Value.Equals("20", StringComparison.OrdinalIgnoreCase))
)
// There may be multiple Names to search for, so require the AND between each Any
&&
(
i.ParentField.Any(pField => pField.Field.Name.Equals("anotherId", StringComparison.OrdinalIgnoreCase)) &&
i.ParentField.Any(pField =>
pField.Value.Equals("50", StringComparison.OrdinalIgnoreCase) || pField.Value.Equals("60", StringComparison.OrdinalIgnoreCase))
));
The important parts to note is that there can be many "Field.Name"'s to search for, as well as multiple "Values" within each of the groups.
I'm not able to give much of an example of what I have tried so far, given I am not sure where to actually start.
Any pointers would be fantastic.

In this particular case there is no need to build dynamic expression predicate. The && can be achieved by chaining multiple Where, and || by putting the values into IEnumerable<string> and using Enumerable.Contains.
For single name / values filter it would be something like this:
var name = "anId".ToLower();
var values = new List<string> { "10", "20" }.Select(v => v.ToLower());
query = query.Where(p => p.ParentField.Any(
pf => pf.Field.Name == name && values.Contains(pf.Value.ToLower())));
And for multiple key / values pairs:
var filters = new Dictionary<string, List<string>>
{
{ "anId", new List<string> { "10", "20" } },
{ "anotherId", new List<string> { "50", "60" } },
};
foreach (var entry in filters)
{
var name = entry.Key.ToLower();
var values = entry.Value.Select(v => v.ToLower());
query = query.Where(p => p.ParentField.Any(
pf => pf.Field.Name == name && values.Contains(pf.Value.ToLower())));
}
#geraphl's answer presents the same idea, but w/o taking into account EF query provider specific requirements (no dictionary methods, only primitive value list Contains, no Equals with StringComparison.OrdinalIgnoreCase usage, but == and ToLower etc.)

I am not sure but i think you are searching for something like this.
First you have to put your searched values in a data-type that is capable of doing what you want, which is in this case a dictionary:
var nameValues = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase)
{
{ "anId" , new string[] {"10", "20"} },
{ "anotherId" , new string[] {"50", "60"} },
};
Then you first check if the name is in the dictionary and if so, you also check if one of the values also listed is in the list you search.
var query = _unitOfWork.ParentRepository.Queryable().Include("ParentField.Field");
query = query.Where(i =>
(
i.ParentField.Any(pFieldOuter => nameValues.ContainsKey(pFieldOuter.Field.Name) ?
i.ParentField.Any(pFieldInner => nameValues[pFieldOuter.Field.Name].Contains(pFieldInner.Value, StringComparer.OrdinalIgnoreCase))
: false
)
));
But if you want, that all your searched names and values are included you have to do this.
var names = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "anId", "anotherId" };
var values = new List<List<string>>() {
new List<string> { "10", "20" },
new List<string> { "50", "60" }
};
var query = _unitOfWork.ParentRepository.Queryable().Include("ParentField.Field");
query = query.Where(i =>
(
names.All(n => i.ParentField.Any(pField => pField.Name.Equals(n, StringComparison.OrdinalIgnoreCase))) &&
values.All(l => l.Any(v => i.ParentField.Any(pField => pField.Value.Equals(v, StringComparison.OrdinalIgnoreCase))))
));
Even if i am sure this is not the most effective way to do is, it may be a good hint for you.

Related

every Parameter object property that is not null, to be added to expression predicate as a condition

I am looking for a way to dynamically build an expression, based upon the method parameters.
This is the code snippet from my service method, where I would like to build a predicate expression.
public async Task<Account> GetCustomerAccountsAsync(Parameters params)
{
var items = await _unitOfWork.Accounts.GetWhereAsync(a => a.CustomerId == params.CustomerId && ... );
...
}
GetWhereAsync is a method from the generic repository that looks like:
public async Task<IEnumerable<TEntity>> GetWhereAsync(Expression<Func<TEntity, bool>> predicate)
{
return await Context.Set<TEntity>().Where(predicate).ToListAsync();
}
And Parameters class:
public class Parameters
{
public string CustomerId { get; set; }
public string AccountId { get; set; }
public string ProductId { get; set; }
public string CurrencyId { get; set; }
}
What I would like to implement, is that every Parameter object property that is not null,
to be added to expression predicate as a condition.
For example, if CustomerId and AccountId have some values, I would like to dynamically build predicate expression
that would have functionality same as the following predicate:
var items = await _unitOfWork.Accounts.GetWhereAsync(a => a.CustomerId == params.CustomerId && a.AccountId == params.AccountId);
Appreciate any help.
You don't need to use Expressions to build something dynamically here. You can do something like this:
_unitOfWork.Accounts.Where(a =>
(params.CustomerId == null || a.CustomerId == params.CustomerId) &&
(params.AccountId == null || a.AccountId == params.AccountId) &&
(params.ProductId == null || a.ProductId == params.ProductId) &&
(params.CurrencyId == null || a.CurrencyId == params.CurrencyId)
);
This is how I've done queries before for a search form with multiple optional search parameters.
I'm currently working a lot with Expressions so I think I can help you.
I just built a custom code for you.
The code accepts that you add Properties to your filtered class (Account) without having to change the filter building code.
The code filters string Properties and use it to create the Predicate.
public Func<Account, bool> GetPredicate(Parameters parameters)
{
var stringProperties = typeof(Parameters)
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(x => x.PropertyType == typeof(string));
var parameterExpression = Expression.Parameter(typeof(Account));
var notNullPropertyNameToValue = new Dictionary<string, string>();
BinaryExpression conditionExpression = null;
foreach (var stringProperty in stringProperties)
{
var propertyValue = (string)stringProperty.GetValue(parameters);
if (propertyValue != null)
{
var propertyAccessExpression = Expression.PropertyOrField(parameterExpression, stringProperty.Name);
var propertyValueExpression = Expression.Constant(propertyValue, typeof(string));
var propertyTestExpression = Expression.Equal(propertyAccessExpression, propertyValueExpression);
if (conditionExpression == null)
{
conditionExpression = propertyTestExpression;
}
else
{
conditionExpression = Expression.And(conditionExpression, propertyTestExpression);
}
}
}
//Just return a function that includes all members if no parameter is defined.
if (conditionExpression == null)
{
return (x) => true;
}
var lambdaExpression = Expression.Lambda<Func<Account, bool>>(conditionExpression, parameterExpression);
return lambdaExpression.Compile();
}
It returns a typed predicate that you can use in Linq for example.
See this example :
void Main()
{
var customers = new List<Account>()
{
new Account()
{
CustomerId = "1",
},
new Account()
{
CustomerId = "2",
}
};
var predicate = GetPredicate(new Parameters() { CustomerId = "1" });
customers.Where(predicate);
}
If any help is needed, feel free to ask !

Using LINQ. How can I compare two simple lists to give another list of objects in the new and not in the old list?

I have two objects of type IList:
public class SampleSentence
{
public int SampleSentenceId { get; set; }
public string Text { get; set; }
}
IList<SampleSentence> Old =
[new SampleSentence() { SampleSentenceId = 1; Text = 'cat' }]
IList<SampleSentence> New =
[new SampleSentence() { Text = 'cat' }],
new SampleSentence() { Text = 'dog' }]
What I need to get is:
IList<SampleSentence> whatINeed =
[new SampleSentence() { Text = 'dog' }]
Object Old is a list of SampleSentences with the SampleSentenceId and Text fields populated.
Object New is a list of SampleSentences with the only the Text fields populated. It will have the same or more rows than object Old
Using LINQ how can I compare the Old and New objects (linking them with the contents of Text) and create another IList that has the additional columns in the list named New?
You can use simple antijoin implemented with LINQ GroupJoin method/operator:
IList<SampleSentence> whatINeed = New.GroupJoin(Old,
newElement => newElement.Text, oldElement => oldElement.Text,
(newElement, oldElements) => new { newElement, oldElements })
.Where(match => !match.oldElements.Any())
.Select(match => match.newElement)
.ToList();
The same with query syntax (preferable when using joins due to transparent identifiers - note the lack of match anonymous type):
IList<SampleSentence> whatINeed =
(from newElement in New
join oldElement in Old on newElement.Text equals oldElement.Text into oldElements
where !oldElements.Any()
select newElement)
.ToList();
Create a custom IEqualityComparer:
public class SampleSentenceComparer : IEqualityComparer<SampleSentence> {
public bool Equals(SampleSentence x, SampleSentence y) {
if (x == y) return true;
if (x == null || y == null) return false;
return x.Text.Equals(y.Text);
}
public int GetHashCode(SampleSentence obj) {
return obj.Text.GetHashCode();
}
}
Usage:
List<SampleSentence> newItems = New.Except(Old, new SampleSentenceComparer()).ToList();

Querying array of strings by array of strings in elasticsearch.net

I'm using elasticsearch.net library in C# and I'm trying to query for objects matching specified filter.
I would like the query to return objects where at least one of input names from filter exists in object's Names collection.
The problem is that I always get 0 hits as result with this query, even tho I am certain that data matching specified filter does exist in the database and I would love to find out what's wrong with my query...
The model:
public class A
{
public int AId { get; set; }
public IEnumerable<string> Names { get; set; }
}
The filtering object:
public class Filter
{
public IEnumerable<string> NamesToSearch { get; set; }
}
The method for querying data:
public async Task<IEnumerable<A>> GetFilteredData(Filter filter)
{
var query = await _elasticClient.SearchAsync<A>(x => x.Query(q => q.Terms(a => a.Names, filter.NamesToSearch))
.Fields(a => a.AId, a => a.Names));
return query.Hits
.Select(x => new A
{
AId = x.Fields.FieldValues<A, int>(a => a.AId)[0]
})
.ToList();
}
I have also tried following query, but it didn't yield expected result neither:
var query = await _elasticClient.SearchAsync<A>(x => x.Query(q => q.Nested(n => n.Filter(f => f.Terms(y => y.Names, filter.NamesToSearch))))
.Fields(a => a.AId, a => a.Names));
SOLUTION WHICH WORKED FOR ME:
I have upgraded a bit code from SÅ‚awomir Rosiek's answer to actually compile using ElasticSearch.net 1.7.1 and be type-safe (no references to field name by string) and ended up with following extension method, which worked like a charm for my scenario:
public static QueryContainer MatchAnyTerm<T>(this QueryDescriptor<T> descriptor, Expression<Func<T, object>> field, object[] values) where T : class, new()
{
var queryContainer = new QueryContainer();
foreach (var value in values)
{
queryContainer |= descriptor.Term(t => t.OnField(field).Value(value));
}
return queryContainer;
}
and usage:
var query = await _elasticClient.SearchAsync<A>(x => x.Query(q =>
q.Bool(b =>
b.Should(s => s.MatchAnyTerm(a => a.Names, filter.NamesToSearch.ToArray()))
.Fields(a => a.AId, a => a.Names));
I think that your problem is that you tries to pass whole array to query. Instead of that you should treat that as OR expression.
Below is the raw query that you should use:
{
"query": {
"bool": {
"should": [
{ "term": {"names": "test" } },
{ "term": {"names": "xyz" } }
]
}
}
}
And that the C# code to achive that. First I have defined helper function:
private static QueryContainer TermAny<T>(QueryContainerDescriptor<T> descriptor, Field field, object[] values) where T : class
{
QueryContainer q = new QueryContainer();
foreach (var value in values)
{
q |= descriptor.Term(t => t.Field(field).Value(value));
}
return q;
}
And now the query:
string[] values = new[] { "test", "xyz" };
client.Search<A>(x => x.Query(
q => q.Bool(
b => b.Should(s => TermAny(s, "names", values)))));

How to add if statement to a LINQ query in C#?

Consider the the following query method:
internal List<Product> productInStockQuery(InStock inStock1)
{
var inStock =
from p in products
where p.INStock == inStock1
select p;
return inStock.ToList<Product>();
}
I would like the method to do the following:
If inStock1 == InStock.needToOrder I would like it to search for both (something like):
(instock1==InStock.needToOrder && instock1==InStock.False)
How can I do that?
I would also like to know if it is possible to create a method that allows me to search all of my Product fields in one Method.
EDITED SECTION: (second question i had)
trying to explain myself better: my class has several fields and right now for each field i have a method like shown above i wanted to know if it is possible to create a special variable that will allow me to acces each field in Product without actually typing the fields name or getters like instead of p.price / p.productName i would just type p.VARIABLE and depending on this variable i will acces the wanted field / getter
if such option exists i would appreciate it if you could tell me what to search the web for.
p.s
thanks alot for all the quick responses i am checking them all out.
Do you mean using something like an Enum?
internal enum InStock
{
NeedToOrder,
NotInStock,
InStock
}
internal class Product
{
public string Name { get; set; }
public InStock Stock { get; set; }
}
Then pass a collection to the method...
internal static List<Product> ProductInStockQuery(List<Product> products)
{
var inStock =
from p in products
where p.Stock != InStock.NeedToOrder && p.Stock != InStock.NotInStock
select p;
return inStock.ToList();
}
Create a list and pass it in...
var prods = new List<Product>
{
new Product
{
Name = "Im in stock",
Stock = InStock.InStock
},
new Product
{
Name = "Im in stock too",
Stock = InStock.InStock
},
new Product
{
Name = "Im not in stock",
Stock = InStock.NotInStock
},
new Product
{
Name = "need to order me",
Stock = InStock.NotInStock
},
};
var products = ProductInStockQuery(prods);
You can compose queries based upon an input variable like so:
var inStock =
from p in products
select p;
if(inStock1 == InStock.needToOrder)
{
inStock = (from p in inStock where p.INStock == InStock.needToOrder || instock1 == InStock.False select p);
}
else
{
inStock = (from p in inStock where p.INStock == inStock1 select p);
}
return inStock.ToList<Product>();
I believe you can use WhereIf LINQ extension method to make it more clear and reusable. Here is an example of extension
public static class MyLinqExtensions
{
public static IQueryable<T> WhereIf<T>(this IQueryable<T> source, Boolean condition, System.Linq.Expressions.Expression<Func<T, Boolean>> predicate)
{
return condition ? source.Where(predicate) : source;
}
public static IEnumerable<T> WhereIf<T>(this IEnumerable<T> source, Boolean condition, Func<T, Boolean> predicate)
{
return condition ? source.Where(predicate) : source;
}
}
Below you can see an example how to use this approach
class Program
{
static void Main(string[] args)
{
var array = new List<Int32>() { 1, 2, 3, 4 };
var condition = false; // change it to see different outputs
array
.WhereIf<Int32>(condition, x => x % 2 == 0) // predicate will be applied only if condition TRUE
.WhereIf<Int32>(!condition, x => x % 3 == 0) // predicate will be applied only if condition FALSE
.ToList() // don't call ToList() multiple times in the real world scenario
.ForEach(x => Console.WriteLine(x));
}
}
q = (from p in products);
if (inStock1 == InStock.NeedToOrder)
q = q.Where(p => p.INStock == InStock.False || p.InStock == InStock.needToOrder);
else
q = q.Where(p => p.INStock == inStock1);
return q.ToList();

How to validate values in a list of object?

I have a List<ObjectA> populated.
Class ObjectA has two properties -string property1 bool? property2
[1]I need to loop through the entire list to check if property1 contains a particular string say 'xyz'.
[2] I need to check if value of property2 is true(which i believe i can figure out after getting to know how to handle [1])
thanks
Adarsh
With List<T> you can also use its method TrueForAll:
bool valid = list.TrueForAll(a => a.property1.Contains("xyz") && a.property2);
This will work on any version of .NET >= 2.0. You also can use LINQ as #Hassan suggested:
bool valid = list.All(a => a.property1.Contains("xyz") && a.property2);
That will work on .NET >= 3.5. Benefit of this option is ability to work with any enumerable source (i.e. if you'll change list to ICollection, or some other enumerable type).
if you want to make sure if all elements of a particular collection satisfy a condition you can use All method in Linq, like this:
new List<MyClass>().All(m => m.StringProp.Contains("ss") && m.IsValid == true)
Use LINQ:
List<ObjectA> list;
//Check for string
if (list.Any(a => a.property1.Contains("xyz")))
{
}
// Check for true
if (list.Any(a => a.property2 ?? false))
{
}
If you need to get the objects that satisfies your conditions, use list.Where(...)
There are several possible checks and results you can easily get.
void Main()
{ //ignore Dump() method
var list = new List<ObjectA>()
{
new ObjectA("name1", true),
new ObjectA("name2", true),
new ObjectA("name3", null),
new ObjectA("name4", false),
new ObjectA("name5", null),
new ObjectA("name6", false),
new ObjectA("name7", true)
};
//check if all Items are valid
var allItems = list.All(m => m.Name.Contains("ss") && m.valid == true); // false
//only get valid items (name contains name AND true)
var validItems = list.Where (l => l.Name.Contains("name") && l.valid == true).Dump();
//get all items which are invalid
var nonvalidItems = list.Where(l =>!(l.Name.Contains("name")) || l.valid != true).Dump();
}
public class ObjectA
{
public ObjectA(string name, bool? _val)
{
this.Name = name;
if(_val.HasValue)
this.valid = _val;
}
public string Name { get; set; }
public bool? valid { get; set; }
}

Categories