How can i get property names from an Expression tree? - c#

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));

Related

Chaining lambda expressions by dot

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;
}

Iterating over properties and use each one in a lambda expression

I am trying to simplify my code a bit by iterating over the columns in a table and use each column to map to a value instead of having multiple lines of code with a single different value.
So going from this:
foreach(var n in groupedResults){
SetCellForData("Column1", n.Sum(x => x.ColumnName1FromTable));
SetCellForData("Column2", n.Sum(x => x.ColumnName2FromTable));
...
SetCellForData("Column10", n.Sum(x => x.ColumnName10FromTable));
}
To something like this:
var columnProperties = typeof(TableClass).GetProperties().Select(t => t);
foreach(var n in groupedResults){
foreach(var columnProperty in columnProperties ){
SetCellForData(columnProperty.Name, n.Sum(x => x.????);
}
}
Where the ???? part uses the columnProperty to Sum the column in the n grouped result.
Since arekzyla decided to delete his answer before I could confirm that it was what I needed then I have decided to answer my own question with arekzyla's answer.
This method creates the function:
private static Func<T, U> GetPropertyFunc<T, U>(string propertyName)
{
var parameter = Expression.Parameter(typeof(T));
var body = Expression.PropertyOrField(parameter, propertyName);
var lambda = Expression.Lambda<Func<T, U>>(body, parameter);
return lambda.Compile();
}
This code iterates over the properties in the database type and creates a function for each of them returning an integer.
var columnProperties = typeof(DatabaseType).GetProperties()
.Where(x => x.PropertyType == typeof(int))
.Select(t => new { t.Name, GetPropertyFunc = GetPropertyFunc<DatabaseType, int>(t.Name) })
.ToList();
Then we iterate over some grouped list:
foreach (var n in groupedList)
{
foreach (var property in columnProperties)
{
var sumOfElementsInGrouping = n.Sum(x => property.GetPropertyFunc(x));
}
}
Then we have the sum of the specific column based on the property.

Get Method is null off IQueryable (Entity Framework)

I'm trying to pass lambda expressions and a type to my DAL. I have this statement:
(entities).GetType().GetMethod("Where")
"entities" is the Table of entities on the DataContext.
When I run the statement I get a null even though Linq.Table inherits IQueryable.
Anyone have an idea?
Here is the entire method:
public object GetResultSet(Dictionary<Type, Func<object, bool>> values)
{
using (ICSDataContext db = DataContextFactory.CreateDataContext<ICSDataContext>(DataContexts.ICS))
{
foreach (var entry in values)
{
var property = db.GetType().GetProperty(entry.Key.Name + "s");
IQueryable entities = (IQueryable)property.GetValue(db, null);
var whereMethod = (entities).GetType().GetMethod("Where")
.MakeGenericMethod(Type.GetType(entry.Key.AssemblyQualifiedName));
return whereMethod.Invoke(entities, new object[] { entry.Value });
}
}
return null;
}
Thanks
As an alternative you could do something like
db.Set<Type>()
which will return you the DBSet of the appropriate type, with Where accessible without reflection. Also you may want to use Expression> rather than Func, expressions work on queryables where as funcs work on enumerables. If you pass a func into a Where clause it pulls the entire dbset down and processes it in memory.
Typed expressions are also a little easier to work with (intellesence, type checking).
Expression<Func<User,bool>> filter = c=>c.FirstName == "Bob";
As another alternative you can look into System.Linq.Dynamic, ScottGu has a write up on it here. The article and the code are old, but it works with EF 6. It allows things like
.Where("CategoryId=2 and UnitPrice>3")
From answer by LukeH under here:
var where1 = typeof(Queryable).GetMethods()
.Where(x => x.Name == "Where")
.Select(x => new { M = x, P = x.GetParameters() })
.Where(x => x.P.Length == 2
&& x.P[0].ParameterType.IsGenericType
&& x.P[0].ParameterType.GetGenericTypeDefinition() == typeof(IQueryable<>)
&& x.P[1].ParameterType.IsGenericType
&& x.P[1].ParameterType.GetGenericTypeDefinition() == typeof(Expression<>))
.Select(x => new { x.M, A = x.P[1].ParameterType.GetGenericArguments() })
.Where(x => x.A[0].IsGenericType
&& x.A[0].GetGenericTypeDefinition() == typeof(Func<,>))
.Select(x => new { x.M, A = x.A[0].GetGenericArguments() })
.Where(x => x.A[0].IsGenericParameter
&& x.A[1] == typeof(bool))
.Select(x => x.M)
.SingleOrDefault();
Then this:
var gmi = where1.MakeGenericMethod(typeof(T));

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();

Categories

Resources