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();
Related
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.
I need to create an expression tree returning Expression<Func<TDataStructure, Double[]>> expression. Tricky part about it, is that TDataStructure is generic what implies, that each passed class contains different set of properties. I need to get properties of type Double? and names starting with specific string, like "output".
Simple example case (concrete class):
Expression<Func<ConcreteDataStructure, Double[]>> expression =
structure => new Double[] {
structure.inputProperty1.Value,
structure.outputProperty1.Value,
structure.outputProperty2.Value
};
where: structure.inputProperty1.Value, structure.outputProperty1.Value, structure.outputProperty2.Value are of type Double?
My case:
I do not know how many properties of type Double? are in TDataStructure
I need to get only these of type Double?, starting with "output"
Expression will be used in IQueryable<TDataStructure> Select method for projection of each sequence element into new form.
Try this:
public static Expression<Func<T, Double[]>> BuildExpression<T>()
{
ParameterExpression param = Expression.Parameter(typeof(T));
Expression[] array = typeof(T).GetProperties()
.Where(p => p.Name.StartsWith("input") || p.Name.StartsWith("output"))
.OrderBy(p => p.Name)
.Select(p => (Expression)Expression.Property(Expression.Property(param, p), "Value"))
.ToArray();
Expression body = Expression.NewArrayInit(typeof(Double), array);
return Expression.Lambda<Func<T, Double[]>>(body, param);
}
I'd suggest this:
static Expression<Func<T, double[]>> DoublePropertiesSelector<T>()
{
var data = Expression.Parameter(typeof(T));
return Expression.Lambda<Func<T, double[]>>(
Expression.NewArrayInit(typeof(double),
from property in typeof(T).GetProperties()
where property.PropertyType == typeof(double?)
select Expression.Property(Expression.Property(data, property.Name), "Value")),
data);
}
If you are okay with possible null-refs, in case your double? has no value.
Does anyone have/know of an IQueryable.OrderBy extension that takes an Expression (retrieved, for example, by Reflection)? I believe the function would look something like this:
public static IQueryable<TEntity> OrderBy<TEntity>
(this IQueryable<TEntity> source, Expression sortExpression)
Expression would be assumed to be an Expression<Func<TEntity, T>> where TEntity is the same object being sorted on, and T is a type that needs to be determined in order to create the new IQueryable.
I've found many examples of extensions that take a string, including Dynamic Linq, like this:
public static IQueryable<TEntity> OrderBy<TEntity>(
this IQueryable<TEntity> source, string sortExpression)
If it's possible to take the string and use Reflection to look up the type from the object in question, it should also be possible to take the Expression, and get the value type which is right there IN the Expression.
Following is a detailed explanation of why I'd like to have this, which you may or may not need.
I have a rather large list of complex records to sort. Because the list is so long, I prefer to have the sorting done on the database side. To handle more complex properties, I've created Expressions that provide the sorting functionality, like so:
if (model.sortExpression == "PlannedValue")
{
Expression<Func<BDopp, decimal>> sorter = BDopp.PlannedValueSorter;
if (model.sortDirection == "DESC")
opps = opps.OrderByDescending(sorter).AsQueryable();
else
opps = opps.OrderBy(sorter).AsQueryable();
}
BDOpp.PlannedValueSorter retrieves a static expression from the object which allows sorting to be done without opps are still of type IQueryable:
public static Expression<Func<BDopp, decimal>> PlannedValueSorter
{
get
{
return z => z.BudgetSchedules
.Where(s => s.Type == 1)
.Sum(s => s.Value * s.Workshare * z.valueFactor / 100 / 100);
}
}
Sorting for simple properties is done with Extension methods that use Reflection to build an expression based on the name of the property passed as a string.
This works well, but for the complex types, I still need branching logic, and I'd rather not do that. What I'd rather do is check for a static property containing the expression, and then simply apply it. I can get the expression like this:
PropertyInfo info = typeof(BDopp).GetProperty(model.sortExpression + "Sorter",
BindingFlags.Static | BindingFlags.Public);
Expression expr = (Expression)info.GetValue(null, null);
For the PlannedValue property, this gets me the expression sorted in PlannedValueSorter, which I already know works.
Update:
Various digging around has gotten me what I think might be some progress:
public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source,
Expression<Func<TEntity, dynamic>> sortExpression)
{
var unary = sortExpression.Body as UnaryExpression;
Type actualExpressionType = unary.Operand.Type;
actualExpressionType is in fact the return type of the Expression (for this particular property, it's decimal).
Unfortunately I'm mostly just working by trial and error, since I haven't yet wrapped my brain around how all this works, so my attempt to update the query like so is not working:
MethodCallExpression resultExp = Expression.Call(typeof(Queryable),
"OrderBy",
new Type[] { typeof(TEntity), actualExpressionType },
source.Expression, sortExpression);
return source.Provider.CreateQuery<TEntity>(resultExp);
It compiles okay, but the following error is thrown at runtime:
No generic method 'OrderBy' on type 'System.Linq.Queryable' is
compatible with the supplied type arguments and arguments. No type
arguments should be provided if the method is non-generic.
As I understand it you have an Expression and want to order/filter by it. It might surprise you how simple it is:
Expression<Func<TEntity, T>> sortExpr = ...; //you already have this
var ordered = myQuery.OrderBy(sortExpr);
OrderBy always uses an Expression so you can directly use it. If you only have a variable of type Expression (which points to an object of type Expression<Func<TEntity, T>>) and don't know statically what T is, you can do this:
Expression sortExpr = ...; //you already have this
var ordered = myQuery.OrderBy((dynamic)sortExpr);
dynamic will figure this out at runtime using reflection. No need to do reflection yourself. All that is needed here is overload resolution (performed by the C# runtime binder).
Okay, I've got a solution:
public static IQueryable<TEntity> OrderBy<TEntity>(
this IQueryable<TEntity> source,
Expression<Func<TEntity, dynamic>> sortExpression,
bool descending)
{
var unary = sortExpression.Body as UnaryExpression;
var operand = unary.Operand;
Type actualExpressionType = operand.Type;
MethodCallExpression resultExp =
Expression.Call(typeof(Queryable),
descending? "OrderByDescending" : "OrderBy",
new Type[] { typeof(TEntity), actualExpressionType },
source.Expression,
Expression.Lambda(operand, sortExpression.Parameters));
return source.Provider.CreateQuery<TEntity>(resultExp);
}
The bool descending is to allow for the standard OrderBy and OrderByDescending overloads. Two major breakthroughs here, at least for me:
Getting the Operand out of the expression.
Using Expression.Call and Expression.Lambda to create the new expression - this allows me to use an actual "Type" variable, whereas the Expression<Func<TEntity, T>> syntax requires you to use a type that's known at compile time.
Try this:
public static IEnumerable<T> OrderBy<T>(
this IEnumerable<T> collection,
string columnName
//, SortDirection direction
)
{
ParameterExpression param = Expression.Parameter(typeof(T), "x"); // x
Expression property = Expression.Property(param, columnName); // x.ColumnName
Func<T, object> lambda = Expression.Lambda<Func<T, object>>( // x => x.ColumnName
Expression.Convert(property, typeof(object)),
param)
.Compile();
Func<IEnumerable<T>, Func<T, object>, IEnumerable<T>> expression = (c, f) => c.OrderBy(f); // here you can use OrderByDescending basing on SortDirection
IEnumerable<T> sorted = expression(collection, lambda);
return sorted;
}
Usage:
IEnumerable<T> collection = ...
IEnumerable<T> ordered = collection.OrderBy("PropName");
See Code Project and sandbox.
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.
How can I build an expression tree when parts of the expression are passed as arguments?
E.g. what if I wanted to create expression trees like these:
IQueryable<LxUser> test1(IQueryable<LxUser> query, string foo, string bar)
{
query=query.Where(x => x.Foo.StartsWith(foo));
return query.Where(x => x.Bar.StartsWith(bar));
}
but by creating them indirectly:
IQueryable<LxUser> test2(IQueryable<LxUser> query, string foo, string bar)
{
query=testAdd(query, x => x.Foo, foo);
return testAdd(query, x => x.Bar, bar);
}
IQueryable<T> testAdd<T>(IQueryable<T> query,
Expression<Func<T, string>> select, string find)
{
// how can I combine the select expression with StartsWith?
return query.Where(x => select(x) .. y => y.StartsWith(find));
}
Result:
While the samples didn't make much sense (sorry but I was trying to keep it simple), here's the result (thanks Quartermeister).
It can be used with Linq-to-Sql to search for a string that starts-with or is equal to the findText.
public static IQueryable<T> WhereLikeOrExact<T>(IQueryable<T> query,
Expression<Func<T, string>> selectField, string findText)
{
Expression<Func<string, bool>> find;
if (string.IsNullOrEmpty(findText) || findText=="*") return query;
if (findText.EndsWith("*"))
find=x => x.StartsWith(findText.Substring(0, findText.Length-1));
else
find=x => x==findText;
var p=Expression.Parameter(typeof(T), null);
var xpr=Expression.Invoke(find, Expression.Invoke(selectField, p));
return query.Where(Expression.Lambda<Func<T, bool>>(xpr, p));
}
e.g.
var query=context.User;
query=WhereLikeOrExact(query, x => x.FirstName, find.FirstName);
query=WhereLikeOrExact(query, x => x.LastName, find.LastName);
You can use Expression.Invoke to create an expression that represents applying one expression to another, and Expression.Lambda to create a new lambda expression for the combined expression. Something like this:
IQueryable<T> testAdd<T>(IQueryable<T> query,
Expression<Func<T, string>> select, string find)
{
Expression<Func<string, bool>> startsWith = y => y.StartsWith(find);
var parameter = Expression.Parameter(typeof(T), null);
return query.Where(
Expression.Lambda<Func<T, bool>>(
Expression.Invoke(
startsWith,
Expression.Invoke(select, parameter)),
parameter));
}
The inner Expression.Invoke represents the expression select(x) and the outer one represents calling y => y.StartsWith(find) on the value returned by select(x).
You could also use Expression.Call to represent the call to StartsWith without using a second lambda:
IQueryable<T> testAdd<T>(IQueryable<T> query,
Expression<Func<T, string>> select, string find)
{
var parameter = Expression.Parameter(typeof(T), null);
return query.Where(
Expression.Lambda<Func<T, bool>>(
Expression.Call(
Expression.Invoke(select, parameter),
"StartsWith",
null,
Expression.Constant(find)),
parameter));
}
This Works:
public IQueryable<T> Add<T>(IQueryable<T> query, Expression<Func<T, string>> Selector1,
Expression<Func<T, string>> Selector2, string data1, string data2)
{
return Add(Add(query, Selector1, data1), Selector2, data2);
}
public IQueryable<T> Add<T>(IQueryable<T> query, Expression<Func<T, string>> Selector, string data)
{
var row = Expression.Parameter(typeof(T), "row");
var expression =
Expression.Call(
Expression.Invoke(Selector, row),
"StartsWith", null, Expression.Constant(data, typeof(string))
);
var lambda = Expression.Lambda<Func<T, bool>>(expression, row);
return query.Where(lambda);
}
You use it like:
IQueryable<XlUser> query = SomehowInitializeIt();
query = Add(query, x => x.Foo, y => y.Bar, "Foo", "Bar");
Usually you don't do that in the way you descirbed (using the IQueryable Interface) but you rather use Expressions like Expression<Func<TResult, T>>. Having said that, you compose higher order functions (such as where or select) into a query and pass in expressions that will "fill in" the desired functionality.
For example, consider the signature of the Enumerable.Where method:
Where<TSource>(IEnumerable<TSource>, Func<TSource, Boolean>)
The function takes a delegate as its second argument that is called on each element. The value you return from that delegate indicates to the higher order function if it shall yield the current element (include it in the result or not).
Now, let's take a look at Queryable.Where:
Queryable.Where<TSource>-Methode (IQueryable<TSource>, Expression<Func<TSource, Boolean>>)
We can observe the same pattern of a higher order function, but instead of an Func<> delegate it takes an Expression. An expression is basically a data representation of your code. Compiling that expression will give you a real (executable) delegate. The compiler does a lot of heavy lifting to build expression trees from lambdas you assign to Expression<...>. Expression trees make it possible to compile the described code against different data sources, such as a SQL Server Database.
To come back to your example, what I think you're looking for is a selector. A selector takes each input element and returns a projection of it. It's signature looks like this: Expression<Func<TResult, T>>. For example you could specify this one:
Expression<Func<int, string>> numberFormatter = (i) => i.ToString(); // projects an int into a string
To pass in a selector, your code would need to look like this:
IQueryable<T> testAdd<T>(IQueryable<T> query, Expression<Func<string, T>> selector, string find)
{
// how can I combine the select expression with StartsWith?
return query.Select(selector) // IQueryable<string> now
.Where(x => x.StartsWith(find));
}
This selector would allow you to project the input string to the desired type. I hope I got your intention corrrectly, it's hard to see what you're trying to achieve.