Chaining lambda expressions by dot - c#

I have two expressions and I want to chain them so the resulting expression contains both input expressions.
Expression<Func<IQueryable<Material>, object>> expression1 = x => x.Include(m => m.MaterialGroup);
Expression<Func<IQueryable<Material>, object>> expression2 = x => x.Include(m => m.MaterialSomething);
var expression3 = expression1.Update(expression2.Body, expression2.Parameters);
Right now expression3 only contains x => x.Include(m => m.MaterialSomething) so it overrides the second expression. I'd like it to be x => x.Include(m => m.MaterialGroup).Include(m => m.MaterialSomething).
What I intend to achieve is to programatically join multiple include expressions in order to be able to build more efficient system for eager loading in EF Core.
EDIT:
This is not a matter of ANDing, ORing etc. because I want these expressions to be chained (like a dot chaining), not logically joined.
Daniel

Because Include is extension method your expression
x => x.Include(m => m.MaterialGroup);
actually is
x => QueryableExtensions.Include(x, m => m.MaterialGroup);
So to chain your expressions you need to replace first argument of Include with call to another Include
x => QueryableExtensions.Include(
QueryableExtensions.Include(x, m => m.MaterialSomething),
m => m.MaterialGroup);
Next code will do this chaining
public static Expression<Func<IQueryable<T>, object>> Chain<T>(
params Expression<Func<IQueryable<T>, object>>[] expressions)
{
if (expressions.Length == 0)
throw new ArgumentException("Nothing to chain");
if (expressions.Length == 1)
return expressions[0];
Expression body = expressions[0].Body;
var parameter = expressions[0].Parameters[0];
foreach (var expression in expressions.Skip(1))
{
var methodCall = (MethodCallExpression)expression.Body;
var lambda = (UnaryExpression)methodCall.Arguments[1];
body = Expression.Call(typeof(QueryableExtensions),
"Include",
new []{ typeof(T), ((LambdaExpression)lambda.Operand).Body.Type},
body, lambda
);
}
return Expression.Lambda<Func<IQueryable<T>, object>>(body, parameter);
}
Usage:
var expression = Chain(expression1, expression2 /*, expression3 .... */);
You can test it online here
Please note that this code skip expression validation for brevity.

I'd like to add another way to archive chaining lambda expressions:
add the follow static method somewhere easy access
public static Expression<Func<T, bool>> ConcatLambdaExpression<T>(Expression<Func<T, bool>> firstExpression, Expression<Func<T, bool>> secondExpression)
{
var invokedThird = Expression.Invoke(secondExpression, firstExpression.Parameters.Cast<Expression>());
var finalExpression = Expression.Lambda<Func<T, bool>>(Expression.AndAlso(firstExpression.Body, invokedThird), firstExpression.Parameters);
return finalExpression;
}
Then you can use it on this way:
public PersonDTO GetAll()
{
Expression<Func<Person, bool>> expression = x => x != null;
expression = x => x.Name == "John";
Expression<Func<Person, bool>> pred = x => x.LastName == "Doe" || x.LastName == "Wick";
//result of expression would be:
////expression = x.Name == "John" && (x => x.LastName == "Doe" || x.LastName == "Wick")
expression = Utilities.ConcatLambdaExpression(expression, pred);
var result = Context.PersonEntity.Where(expression);
//your code mapping results to PersonDTO
///resultMap...
return resultMap;
}

Related

How can i get property names from an Expression tree?

My method is
public Task<Product> GetProduct(int productId, params Expression<Func<Product, object>>[] properties)
{
var member = properties[0].Body as MemberExpression;
var v = member.Member.Name;
}
i can get a single property name by using the appropriate index
var member = properties[0].Body as MemberExpression;
var v = member.Member.Name;
But this is not what i want.
I would love to get all property names and string.join them with linq.
How can i do that?
Use as operator and then filter the ones which were not properties. If you use casting, it will throw exception but as will just return null.
var all =
string.Join(", ", properties
.Select(x =>
x.Body as MemberExpression))
.Where(x => x != null)
.Select(x =>
x.Member.Name));

PredicateBuilder with multiple (OR) and (AND)

I'm trying to create an expression in order to retrieve a Store object that should be on a list of countries and also that their Store.Id = X.
I'm trying to do that with the following expression, but that returns all Stores stored on the database, I don't know what I'm missing.
public Expression<Func<Store, bool>> CreateExpression(List<Country> countries, long storeId)
{
var predicate = PredicateBuilder.False<Store>();
predicate = countries.Aggregate(predicate, (current, p) =>
current.Or(e => e.Country.Id == p.Id));
predicate = predicate.And(e => e.Id == storeId);
return predicate;
}

How do I define a SELECT TOP using LINQ with a dynamic query?

I want to pass dynamic lambda expressions to the function below, but I'm not sure how to define the .Take() or .OrderByDescending() on the expression object.
If I want to call the function below, then I want to be able to do this:
dbprovider.Query = (x => x.ConfigurationReference == "172.16.59.175")
.Take(100)
.OrderByDescending(x.Date)
FindEntities(db, dbprovider.Query)
But I can't (this syntax is invalid). Any ideas?
public static List<T> FindEntities<T>(TrackingDataContext dataContext, System.Linq.Expressions.Expression<Func<T, bool>> find) where T : class
{
try
{
var val = dataContext.GetTable<T>().Where(find).ToList<T>();
return val;
}
catch (Exception ex)
{
throw ex;
}
}
The parameter is of type:
System.Linq.Expressions.Expression<Func<T, bool>> find
That means it can take a predicate (the "where" clause), and only a predicate. Thus the only bit you can pass in there is the filter:
x => x.ConfigurationReference == "172.16.59.175"
To do what you want, you would need to add the rest of the code in FindEntities, so that it becomes:
var val = dataContext.GetTable<T>().Where(find)
.OrderByDescending(x => x.Date).Take(100).ToList<T>();
(note also that the Take should really be after the OrderByDescending)
One way you could do that would be:
public static List<T> FindEntities<T>(TrackingDataContext dataContext,
System.Linq.Expressions.Expression<Func<T, bool>> find,
Func<IQueryable<T>, IQueryable<T>> additonalProcessing = null
) where T : class
{
var query = dataContext.GetTable<T>().Where(find);
if(additonalProcessing != null) query = additonalProcessing(query);
return query.ToList<T>();
}
and call:
var data = FindEntities(db, x => x.ConfigurationReference == "172.16.58.175",
q => q.OrderByDescending(x => x.Date).Take(100));
However, frankly I'm not sure what the point of this would be... the caller could do all of that themselves locally more conveniently, without using FindEntities at all. Just:
var data = db.GetTable<T>()
.Where(x => x.ConfigurationReference == "172.16.58.175")
.OrderByDescending(x => x.Date).Take(100).ToList();
or even:
var data = db.SomeTable
.Where(x => x.ConfigurationReference == "172.16.58.175")
.OrderByDescending(x => x.Date).Take(100).ToList();
or just:
var data = (from row in db.SomeTable
where row.ConfigurationReference == "172.16.58.175"
orderby row.Date descending
select row).Take(100).ToList();

Expression tree nested wheres

I am trying to dynamically build an expression tree that queries a datasource effectively using an in query. The query i am trying to replicate is
Countries.Where(y => Countries
.Where(x =>
x.CountryLanguage.Any(b => b.CountryID == 73) &&
x.CountryLanguage.Any(b => b.CountryID == 150))
.Select(z => z.ShortCode)
.Contains(y.ShortCode))
I have tried many ways of doing this, but this is my latest attempt:
public void AddContainsWhereClause(IQueryable<T> objectSet, string predicateIdentifier)
{
ParameterExpression pe = Expression.Parameter(typeof(T), predicateIdentifier);
Expression expInner = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { typeof(T) },
objectSet.Expression,
Expression.Lambda<Func<T, bool>>(rootExperession, resultExpression));
Expression expOuter = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { typeof(T) },
objectSet.Expression,
Expression.Lambda<Func<T, bool>>(expInner, pe));
}
NB rootExpression is:
x => x.CountryLanguage.Any(b => b.CountryID == 73) &&
x.CountryLanguage.Any(b => b.CountryID == 150)
But this returns:
[ApplicationFramework.LINQBuilder.tests.Country]' cannot be used for return type 'System.Boolean'
Does anyone know what i am doing wrong?
I'm assuming that what you want is a lambda function to emulate the predicate component of your Where call.
A where clause needs to be of type Func<TSource, bool>, but you're calling Queryable.Where, which actually returns an IEnumerable.
Rather that going down the expression tree path, which can be pretty complex and hard to maintain, is your real problem just that you need to select a list of countries which support the languages of a supplied country list?
int[] requiredCountryIds = {73, 150};
// Select countries which contain all required country IDs in their CountryLanguage set
var resultSet =
countries.Where(
y => requiredCountryIds.All(requiredCountryId => y.CountryLanguage.Any(b => b.CountryId == requiredCountryId)));

Linq In Clause & Predicate building

I have two tables. Report and ReportData.
ReportData has a constraint ReportID.
How can I write my linq query to return all Report objects where the predicate conditions are met for ReportData? Something like this in SQL:
SELECT * FROM Report as r
Where r.ServiceID = 3 and r.ReportID IN (Select ReportID FROM ReportData WHERE JobID LIKE 'Something%')
This is how I'm building my predicate:
Expression<Func<ReportData, bool>> predicate = PredicateBuilder.True<ReportData>();
predicate = predicate.And(x => x.JobID.StartsWith(QueryConfig.Instance.DataStreamName));
var q = engine.GetReports(predicate, reportsDataContext);
reports = q.ToList();
This is my query construction at the moment:
public override IQueryable<Report> GetReports(Expression<Func<ReportData, bool>> predicate, LLReportsDataContext reportDC)
{
if (reportDC == null) throw new ArgumentNullException("reportDC");
var q = reportDC.ReportDatas.Where(predicate).Where(r => r.ServiceID.Equals(1)).Select(r => r.Report);
return q;
}
I've tried doing the following as well:
public override IQueryable GetReports(Expression> predicate, LLReportsDataContext reportDC)
{
if (reportDC == null) throw new ArgumentNullException("reportDC");
var q = from r in reportDC.Reports
where r.ServiceID.Equals(1)
where r.ReportDatas.Where(predicate.Compile()).Select(x => r.ReportID).Contains(r.ReportID)
select r;
return q;
}
However, I get the this Exception: "Unsupported overload used for query operator 'Where'."
UPDATE
This fixed it:
var q = reportDC.Reports.AsExpandable().
Where(r => r.ReportDatas.Any(predicate.Compile()))
.Where(r => r.ServiceID.Equals(1));
Query
ReportDatas
.Where( reportData => reportData.StartsWith( "Something%" ) &&
reportData.Report.Id ==3)
.Select( reportData => reportData.Report )
.Distinct()
AboutLinqKit
When using LinqKit, sometimes you need to call AsExpandable() in the entity collection and to compile the predicate expression. see this example : ): how-to-use-predicate-builder-with-linq2sql-and-or-operator

Categories

Resources