Unique Column Values Linq To Entities Dynamically (Expression Tree) - c#

I realize there have been a ton of posts related to this and I've researched extensively and can't seem to figure this out. It should be super simple. I simply need to generate a column domain with a dynamic column name. Something like
public IEnumerable<ColumnEntity> GetColumnDomain(string column)
{ List<ColumnEntity> columnEntities = new List<ColumnEntity>();
var query = db.CITATIONs.Select(m => m."column").Distinct();
....
}
Where "column" is the dynamic parameter value. I started building and expression tree to dynamically generate the query expression
ParameterExpression pe = Expression.Parameter(typeof(CITATION), "c");
Expression theColumn = Expression.Property(pe, typeof(string).GetProperty(column));
But that is about it. Thanks in advance

Use the Expression.PropertyOrField() method to generate the member access. You'll also need to know the type of the column as well or it just won't work.
This could all be generalized into this generic method:
public static Expression<Func<TSource, TResult>>
GenerateSelector<TSource, TResult>(string propertyOrFieldName)
{
var parameter = Expression.Parameter(typeof(TSource));
var body = Expression.Convert(
// generate the appropriate member access
Expression.PropertyOrField(parameter, propertyOrFieldName),
typeof(TResult)
);
var expr = Expression.Lambda<Func<TSource, TResult>>(body, parameter);
return expr;
}
Then you could just do:
public IEnumerable<ColumnEntity> GetColumnDomain<TColumn>(string column)
{
var query = db.CITATIONs
.Select(GenerateSelector<CITATION, TColumn>(column))
.Distinct();
// ...
}

So not entirely sure what you're after. But simply put:
public PropertyInfo GetPropertyInfo<T>(Expression<Func<T, Object>> expression)
{
MemberExpression memberData = (MemberExpression)expression.Body;
return (PropertyInfo)memberData.Member;
}
this much will give you property info about the member, so if you have:
PropertyInfo info = GetPropertyInfo<FileInfo>(file => file.FullName);
Console.WriteLine(info.Name);
It will show the 'FullName' corresponding to the property you put in the expression.

Related

Linq extending Expressions

I'm trying to write a generic wildcard Search for the ServiceStack.OrmLite.SqlExpressionVisitor that has the following signature:
public static SqlExpressionVisitor<T> WhereWildcardSearch<T> (this SqlExpressionVisitor<T> ev, Expression<Func<T,string>> field, string search)
where ev is the rest of the filter, field is the getter for the field to search by and search is the entered term.
Normally (non-generic) I would write the following:
if(search.StartsWith('*') && search.EndsWith('*'))
ev = ev.Where(x => x.foo.Contains(search.Trim('*')));
and of course also variants for x.foo.StartsWith or EndsWith.
Now I am searching for something like (pseudocode:)
ev = ev.Where(x => field(x).Contains(search.Trim('*')));
Of course I can't compile and call the expression directly, as this should be translated to Sql using Linq2Sql.
This is my code so far:
var getFieldExpression = Expression.Invoke (field, Expression.Parameter (typeof (T), "getFieldParam"));
var searchConstant = Expression.Constant (search.Trim('*'));
var inExp = Expression.Call (getFieldExpression, typeof(String).GetMethod("Contains"), searchConstant);
var param = Expression.Parameter (typeof (T), "object");
var exp = Expression.Lambda<Func<T, bool>> (inExp, param);
ev = ev.Where (exp);
Please don't tell me that I should directly write SQL with $"LIKE %search%" or something - I know that there are other ways, but solving this would help my understanding of Linq and Expressions in general and it bugs me when I can't solve it.
Here is how it can be done (I think it will be clear for you without much additional explanations what you did wrong, but if not - feel free to request a clarification):
// extract property name from passed expression
var propertyName = ((MemberExpression)field.Body).Member.Name;
var param = Expression.Parameter(typeof(T), "object");
var searchConstant = Expression.Constant(search.Trim('*'));
var contains = typeof(String).GetMethod("Contains");
// object.FieldName.Contains(searchConstant)
var inExp = Expression.Call(Expression.PropertyOrField(param, propertyName), contains, searchConstant);
// object => object.FieldName.Contains(searchConstant)
var exp = Expression.Lambda<Func<T, bool>>(inExp, param);
In response to comment. You have two expression trees: one is being passed to you and another one which you are building (exp). In this simple case they both use the same number of parameters and those parameters are of the same type (T). In this case you can reuse parameter from field expression tree, like this:
// use the same parameter
var param = field.Parameters[0];
var searchConstant = Expression.Constant(search.Trim('*'));
var contains = typeof(String).GetMethod("Contains");
// note field.Body here. Your `field` expression is "parameter => parameter.Something"
// but we need just "parameter.Something" expression here
var inExp = Expression.Call(field.Body, contains, searchConstant);
// pass the same parameter to new tree
var exp = Expression.Lambda<Func<T, bool>>(inExp, param);
In more complicated cases you might need to use ExpressionVisitor to replace parameters in one expression tree to reference to parameters from another (final) expression tree.

I can't seem to be able to access a Dictonary<string, object> values while building a dynamic linq expression

I'm trying to generate a linq expression in asp.net core for MongoDB using the MongoDriver and I can't access the runtime value of a dictonary with the expression generator. Thanks in advance!
private Expression<Func<Dictionary<string, object>, bool>> GenerateWhereExpression(Dictionary<string, object> filterParams)
{
var pe = Expression.Parameter(typeof(Dictionary<string, object>), "x");
var dictPropery = Expression.PropertyOrField(pe, "name"); // Dictonary value with respect to the name key
var methodCall = Expression.Call(dictPropery, typeof(string).GetMethod("Contains"), Expression.Constant(filterParams["name"], typeof(string)));
var lambda = Expression.Lambda<Func<Dictionary<string, object>, bool>>(methodCall, pe);
return lambda;
}
What I'm trying to accomplish is the where clause of the query to get all the data for a report.
Expresssiom, that should be created as result of the method should look like:
x => ((string)x["name"]).Contains(term)
Ok, you need to change your method this way:
private Expression<Func<Dictionary<string, object>, bool>>
GenerateWhereExpression(Dictionary<string, object> filterParams)
{
var pe = Expression.Parameter(typeof(Dictionary<string, object>), "x");
// it is call of x.getItem("name") what is the same as x["name"]
var dictPropery = Expression.Call(pe,
typeof(Dictionary<string, object>).GetMethod("get_Item"),
Expression.Constant("name"));
//cast to ((string)x.getItem("name"))
var castProperty = Expression.Convert(dictPropery, typeof(string));
var methodCall = Expression.Call(castProperty,
typeof(string).GetMethod("Contains"),
Expression.Constant(filterParams["name"], typeof(string)));
var lambda = Expression.Lambda<Func<Dictionary<string, object>, bool>>(methodCall, pe);
return lambda;
}
I have tested it with my mongodriver and it seems to work.
Actually, for getting Items from dictionary you need to call Item property (Building Expression Tree Using a Parameter's Indexer). But either i have used it wrong way, or MongoDb driver couldn't translate it properly, so i am doing it calling get_Item method, what is the same thing.

Setting a dynamic sort name field in a linq query [duplicate]

This question already has answers here:
Dynamic LINQ OrderBy on IEnumerable<T> / IQueryable<T>
(24 answers)
How to use a string to create a EF order by expression?
(1 answer)
Closed 6 years ago.
I want to be able to get an OrderBy query working with a lambda expression so that I get a SQL query with the TOP(n) key word (big performance boost).
I am able to do this if I specifiy ...
PaginatedList = query.OrderBy(x => x.QuoteID).Skip(() => skipValue).Take(() => pageSize)
But because I want the orderBy field to be dynamic through a UI selection of a name I want to do something like this:
var propertyInfo = typeof(Data.Quote).GetProperty(sortName);
Expression<Func<Data.Quote, object>> orderField = x => propertyInfo.GetValue(x, null);
PaginatedList = query.OrderBy(orderField).Skip(() => skipValue).Take(() => pageSize)
This gives me the error:
"LINQ to Entities does not recognize the method 'System.Object
GetValue(System.Object)' method, and this method cannot be translated
into a store expression."
I tried this that's not of type Expression<Func<T, object>>
var propertyInfo = typeof(Data.Quote).GetProperty(sortName);
Func<Data.Quote, object> orderField = x => propertyInfo.GetValue(x, null);
PaginatedList = query.OrderBy(x => orderField).Skip(() => skipValue).Take(() => pageSize)
And I get this error:
"Unable to create a constant value of type [...]. Only primitive types
or enumeration types are supported in this context"
I'm sure there is a way to achieve this but at the moment not sure how.
Here is how to achieve what you want:
var propertyInfo = typeof(Data.Quote).GetProperty(sortName);
ParameterExpression parameter = Expression.Parameter(typeof(T), "s");
MemberExpression property = Expression.Property(parameter, propertyInfo);
LambdaExpression sort = Expression.Lambda(property, parameter);
MethodCallExpression call = Expression.Call(
typeof(Queryable),
"OrderBy",
new[] {typeof(T), property.Type},
Query.Expression,
Expression.Quote(sort));
var orderedQuery = (IOrderedQueryable<T>)Query.Provider.CreateQuery<T>(call);
PaginatedList = orderedQuery.Skip(skipValue).Take(pageSize);
Instead of that you need to create an expression to select that property.From this source:
public static class Utility
{
//makes expression for specific prop
public static Expression<Func<TSource, object>> GetExpression<TSource>(string propertyName)
{
var param = Expression.Parameter(typeof(TSource), "x");
Expression conversion = Expression.Convert(Expression.Property
(param, propertyName), typeof(object)); //important to use the Expression.Convert
return Expression.Lambda<Func<TSource, object>>(conversion, param);
}
public static IOrderedQueryable<TSource>
OrderBy<TSource>(this IQueryable<TSource> source, string propertyName)
{
return source.OrderBy(GetExpression<TSource>(propertyName));
}
}
Then you can order by as I show below:
var result=Query.OrderBy(sortName)...;
The value being copied in to propertyInfo is just an Object, as that is the type returned by GetProperty(). Hover over var and you will confirm this.
The GetValue method doesn't exist for an Object, so you need to cast it to the right type before making the call to GetValue.

Build LambdaExpression with Func<,> where returnType is IQueryable<T>

I am trying to create a generic way of getting an EntityFramework object based on its own id without passing in a lambda expression as parameter to the method GetById(). For the code below the entity T is of type Message, is known to the class where GetById() is implemented and has a property MessageId along with several other properties. The MessageId name has been hard-coded in the example below as this is still experimental - extracting the id property name from T is quite easy to fix later.
I have been struggling to find a way to construct a simple LambdaExpression which has IQueryable<T> as parameter type and hope that someone would have a clue on how this could be done. The reason why I want IQueryable<T> is because my underlying channel factory provider requires this for more complex queries.
The line with var exp = Expression.Lambda<...> in the code below shows the expression function type definition which I want to end up with, but the line gives the exception:
Expression of type System.Boolean cannot be used for return type IQueryable
That's because the body has the Boolean type while my expression parameter queryParamtRet is of type IQueryable<Message>. Further, if I change the body type to be an IQueryable<Message>, I'm not able to find the property MessageId since the type is no longer type T as Message but type IQueryable<T>.
public T GetById(int id)
{
var queryParamLeft = Expression
.Parameter(typeof(System.Data.Entity.DbSet<T>), "o");
var queryParamRet = Expression
.Parameter(typeof(IQueryable<T>), "o");
var entityFrameworkType = Expression
.Parameter(typeof(T), "o");
var queryProperty = Expression
.PropertyOrField(entityFrameworkType, "MessageId");
var body = Expression
.Equal(queryProperty, Expression.Constant(id));
var exp = Expression
.Lambda<Func<System.Data.Entity.DbSet<T>, IQueryable<T>>>(
body,
queryParamRet);
var returnXml = DoWithChannel(channel
=> channel.Load(serializer.Serialize(exp)));
}
TLDR: Write out code that you want to create an expression for, then deliberately create the expression, starting with any inner expressions before combining them into the outer expression.
If you write your intended code as a function, it would look something like this
public static IQueryable<T> FilterADbSet(DbSet<T> dbSet)
{
return Queryable.Where<T>(dbSet, o => o.MessageId == 34);
}
It has one input parameter of type DbSet<T>, an output of type IQueryable<T> and it calls Queryable.Where<T> with parameters of the dbSet variable and an expression.
Working from the outside in, you first need to build the expression to pass to the where clause. You have already done that in your code.
Next you need to create a lambda expression for the where clause.
var whereClause = Expression.Equal(queryProperty, Expression.Constant(id));
var whereClauseLambda = Expression.Lambda<Func<T, bool>>(whereClause, entityFrameworkType);
Next, as the comments indicate, you need to use Expression.Call to create a body.
My end result with making your code work is below.
static Expression<Func<IQueryable<T>, IQueryable<T>>> WhereMethodExpression = v => v.Where(z => true);
static MethodInfo WhereMethod = ((MethodCallExpression)WhereMethodExpression.Body).Method;
public T GetById(int id)
{
var queryParamLeft = Expression
.Parameter(typeof(System.Data.Entity.DbSet<T>), "dbSet");
var entityFrameworkType = Expression
.Parameter(typeof(T), "entity");
var queryProperty = Expression
.PropertyOrField(entityFrameworkType, "MessageId");
var whereClause = Expression
.Equal(queryProperty, Expression.Constant(id));
var whereClauseLambda = Expression.Lambda<Func<T, bool>>(whereClause, entityFrameworkType);
var body = Expression.Call(
WhereMethod,
queryParamLeft,
whereClauseLambda
);
var exp = Expression
.Lambda<Func<System.Data.Entity.DbSet<T>, IQueryable<T>>>(
body,
queryParamLeft);
var returnXml = DoWithChannel(channel
=> channel.Load(serializer.Serialize(exp)));
}
I used an expression to fetch the MethodInfo object of Queryable.Where<T>
Your body expression needed queryParamLeft passed in. queryParamRet is not needed

Use reflection to get lambda expression from property Name

I want to give the user the choice of searching by different properties. For instance
[INPUT TEXT] | [SELECT OPTION {ID, NAME, PHONE}] | [SEARCH]
And I would later build my query like this:
repository.Where(lambda-expression)
Where lambda-expression is build from the selected option {ID, NAME, PHONE}
(For example: x => x.NAME.Equals(INPUT TEXT))
Is there a way to build the lambda from the Property name perhaps using reflection?
Thanks
You don't build a lambda expression - you build an expression tree. It's not terribly hard, but it takes a little patience. In your sample you'd probably need:
ParameterExpression parameter = Expression.Parameter(typeof(Foo), "x");
Expression property = Expression.Property(parameter, propertyName);
Expression target = Expression.Constant(inputText);
Expression equalsMethod = Expression.Call(property, "Equals", null, target);
Expression<Func<Foo, bool>> lambda =
Expression.Lambda<Func<Foo, bool>>(equalsMethod, parameter);
That's assuming:
The repository element type is Foo
You want to use a property called propertyName
You want to compare for equality against inputText
For that sort of thing, I use something like this (note: does a Where "Like") :
public static IQueryable<TEntity> Where<TEntity>(this IQueryable<TEntity> source, string propertyName, string value)
{
Expression<Func<TEntity, bool>> whereExpression = x => x.GetType().InvokeMember(propertyName, BindingFlags.GetProperty, null, x, null).ObjectToString().IndexOf(value, StringComparison.InvariantCultureIgnoreCase) >= 0;
return source.Where(whereExpression);
}
I had to face the same sort of problem and the following method resolved my problem perfectly.
PropertyInfo propertyInfoObj = MyClassObject.GetType().GetProperty(PropertyName);
repository.Where(x => propertyInfoObj.GetValue(x) == SearchValue).Select(x => propertyInfoObj.GetValue(x)).FirstOrDefault();

Categories