I have a business layer call that works like so:
CustomerRepository.Get(c => SqlFunctions.PatIndex("%" + arg + "%", c.FirstName));
I am trying to build this using expressions:
public virtual IEnumerable<TEntity> Like(string LikeString, string Target)
{
MethodInfo method = typeof(SqlFunctions).GetMethod("PatIndex");
var arg1 = Expression.Constant(LikeString);
var item = Expression.Parameter(typeof(TEntity), "item");
var prop = Expression.Property(item, Target);
MethodCallExpression resultExp =
Expression.Call(method, arg1, prop);
var value = Expression.Constant(0, typeof(int?));
var greaterThan = Expression.GreaterThan(resultExp, value);
var lambda = Expression.Lambda<Func<TEntity, bool>>(greaterThan, item);
var result = Repo<TEntity>.Get().AsQueryable().Where(lambda);
return result;
}
When I call the above method, I get the following exception:
This function can only be invoked from LINQ to Entities.
Any ideas of how to get past this or do what I want it to do? Does my Expression code look ok?
Figured out the issue
from:
var result = Repo<TEntity>.Get().AsQueryable().Where(lambda);
to:
var result = Repo<TEntity>.Get(lambda);
Related
I want to create an extension method for a LINQ expression but I'm stuck. What I need is just to create a method which will add a specific Where clause to a Queryable. Something like:
var hierarchy = "a string";
Session.Query<SomeClass>.Where(x => x.Layer.Hierarchy.StartsWith(hierarchy) ||
x.Layer.Hierarchy == hierarchy);
to become:
var hierarchy = "a string";
Session.Query<SomeClass>.LayerHierarchy(x => x.Layer, hierarchy);
And do that Where logic inside. So basicly the extension method LayerHierarchy() is running over the Queryable of T but the subject is of type Layer:
public static IQueryable<T> LayerHierarchy<T>(this IQueryable<T> query,
Expression<Func<T, Layer>> layer,
string hierarchy)
{
var parameterExp = Expression.Parameter(typeof(Layer), "layer");
var propertyExp = Expression.Property(parameterExp, "Hierarchy");
// StartWith method
MethodInfo methodStartsWith = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
var valueStartsWith = Expression.Constant(string.Concat(hierarchy, "|"), typeof(string));
var methodExpStartsWith = Expression.Call(propertyExp, methodStartsWith, valueStartsWith);
var startsWith = Expression.Lambda<Func<Layer, bool>>(methodExpStartsWith, parameterExp);
// Equals method
MethodInfo methodEquals = typeof(string).GetMethod("Equals", new[] { typeof(string) });
var valueEquals = Expression.Constant(hierarchy, typeof(string));
var methodExpEquals = Expression.Call(propertyExp, methodEquals, valueEquals);
var equals = Expression.Lambda<Func<Layer, bool>>(methodExpEquals, parameterExp);
return query
.Where(startsWith)
.Where(equals);
}
Everything works fine above the return line. It complains that...
Cannot convert from System.Linq.Expressions.Expression<System.Func<Layer, bool>> to System.Linq.Expressions.Expression<System.Func<T, int, bool>>
when trying to pass the expressions to query.Where() method. How can I fix it?
Well, the problem is how you are creating the Lambdas. They should begin from T, not from Layer:
var startsWith = Expression.Lambda<Func<T, bool>>(methodExpStartsWith, parameterExp);
var equals = Expression.Lambda<Func<T, bool>>(methodExpEquals, parameterExp);
However, in order for this to work, you are missing one more PropertyExpression.
Your query now looks like:
(Layer)x => x.Hierarchy.StartsWith(...)
When, what you want is this:
(T)x => x.Layer.Hierarchy.StartsWith(...)
So, use this instead:
var parameterExp = Expression.Parameter(typeof(T), "item");
var layerExp = Expression.Property(parameterExp, "Layer");
var propertyExp = Expression.Property(layerExp, "Hierarchy");
Your logic should change a little though, since two .Where will generate an AND condition between them, and it seems like you want one of them to be true (StartsWith or Equals), so:
var parameterExp = Expression.Parameter(typeof(T), "item");
var layerExp = Expression.Property(parameterExp, "Layer");
var propertyExp = Expression.Property(layerExp, "Hierarchy");
// StartWith method
MethodInfo methodStartsWith = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
var valueStartsWith = Expression.Constant(string.Concat(hierarchy, "|"), typeof(string));
var methodExpStartsWith = Expression.Call(propertyExp, methodStartsWith, valueStartsWith);
// Equals method
MethodInfo methodEquals = typeof(string).GetMethod("Equals", new[] { typeof(string) });
var valueEquals = Expression.Constant(hierarchy, typeof(string));
var methodExpEquals = Expression.Call(propertyExp, methodEquals, valueEquals);
var orElseExp = Expression.OrElse(methodExpStartsWith, methodExpEquals);
var orElse = Expression.Lambda<Func<T, bool>>(orElseExp, parameterExp);
return query.Where(orElse);
I need make custom orderby for enum. I try use SwitchExpression:
public static IQueryable<T> MyOrderByEnum<T>(this IQueryable<T> source, string propName, Type enumType)
{
var type = typeof (T);
var property = type.GetProperty(propName);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.Property(parameter, property);
var enumValues = Enum.GetValues(enumType);
var switchCases = new SwitchCase[enumValues.Length];
int i = 0;
foreach (var val in enumValues)
{
switchCases[i] = Expression.SwitchCase(
Expression.Constant(val.ToDisplay()),
Expression.Constant(val)
);
i++;
}
var switchExpr1 =
Expression.Switch(
propertyAccess,
Expression.Constant(""),
switchCases
);
var orderByExp1 = Expression.Lambda(switchExpr1, parameter);
MethodCallExpression resultExp = Expression.Call(typeof (Queryable), "OrderBy", new[] {type, orderByExp1.Body.Type}, source.Expression, orderByExp1);
return (IOrderedQueryable<T>) source.Provider.CreateQuery(resultExp);
}
But when I execute
filtered.MyOrderBy("field1", typeof(FieldState)).ToList();
I get error:
Unknown LINQ expression of type 'Switch'.
Is there another way to make order expression that will translate into sql construction "CASE WHEN ..."?
Try Expression.Condition (https://msdn.microsoft.com/en-us/library/bb340500%28v=vs.110%29.aspx)
I think that translates to CASE When if used in an anonymous projection
I am trying search each property value of an IQueryable collection of T against the value of a search query. I have the following function and would like to know how do I ALSO test for NOT NULL and CONTAINS together?
private Expression<Func<T, bool>> PropertySearch
{
get
{
// Object that is passed to the lambda expression
ParameterExpression instance = Expression.Parameter(typeof(T), "val");
Expression whereExpr = Expression.Constant(true); // default is val => True
var _properties = typeof(T).GetProperties();
foreach (var prop in _properties)
{
var query = _httpRequest["query"].ToLower();
var property = Expression.Property(instance, prop);
var toStringCall = Expression.Call(Expression.Call(
property,
"ToString",
new Type[0]),
typeof(string).GetMethod("ToLower", new Type[0]));
whereExpr = Expression.And(whereExpr,
Expression.Call(toStringCall, typeof(string).GetMethod("Contains"),
Expression.Constant(query)));
}
return Expression.Lambda<Func<T, bool>>(whereExpr, instance);
}}
I have created a search extensions nuget package that performs this type of check. For your example I would do something like the following.
Note, this is without an IDE so may have some errors
/* *** Start: These can be made private reaonly fields ***/
var comparisonExpr = Expression.Constant(StringComparison.OrdinalIgnoreCase);
var zeroExpression = Expression.Constant(0)
var nullExpression = Expression.Constant(null)
MethodInfo IndexOfMethod = typeof(string).GetMethod("IndexOf", new[] { typeof(string), typeof(StringComparison) });
/* *** End ***/
Expression finalExpression = null
ParameterExpression instance = Expression.Parameter(typeof(T), "val");
var _properties = typeof(T).GetProperties();
var query = _httpRequest["query"].ToLower();
var queryExpr = Expression.Constant(query);
foreach (var prop in _properties)
{
//Get property
var propertyExpr = Expression.Property(instance, prop);
//Get property as string
var propStringExpr = Expression.Call(property, "ToString", new Type[0]);
//Perform IndexOf call
var indexOfExpr = Expression.Call(propStringExpr,
IndexOfMethod,
queryExpr,
comparisonExpr);
// Check index of is greater than or equal to zero
var containsExpr = Expression.GreaterThanOrEqual(containsExpr, zeroExpression);
if(finalExpression == null)
{
finalExpression = containsExp;
}
else
{
finalExpression = Expression.AndAlso(containsExpr);
}
}
return Expression.Lambda<Func<T, bool>>(finalExpression, instance);
I've removed the need for ToLower() and instead used IndexOf with a string comparison type
If you want to see how I have achieved similar functionality, take a look at NinjaNye.SearchExtensions on Github
https://github.com/ninjanye/SearchExtensions
If you wanted to search a collection of IQueryable you could use NinjaNye.SearchExtensions as follows
string query = _httpRequest["query"];
var result = data.SearchAll().Containing(query);
This will search all string properties (not all properties as you have above) and return just those where any property mathes the search term.
Hope this helps
You could probably use PredicateBuilder so you don't have to mess with expression trees yourself.
I have a simple method which retrieves a table from an azure mobile service.
public static async List<T>GetDataFromListTable<T>()
{
var data = await MobileService.GetTable<T>().ToListAsync();
return data.Count != 0 ? data : null;
}
This works fine.
What I am trying to do is have another method that takes a parameter name which is returned from the service and return the value of that parameter. So far I have this
public static async Task<T> GetDataFromTable<T>(string paramName)
{
var k = Activator.CreateInstance(typeof(T));
var members = typeof(T).GetProperties().Select(t=>t.Name).ToList();
if (!members.Contains(paramName))
return (T)k;
var mn = typeof(T).GetProperties()[members.IndexOf(paramName)];
var data = GetDataFromListTable<T>();
var retval = data.Select(t => t.mn);
}
The issue is obviously that I can't do the Linq query as T doesn't contain mn. I can also not use
var retval = data.Select(t=>t.paramName);
as paramname is a just a string representation of a member within a class.
In a nutshell...
method 1 has the parameter name, grabs a list from method 2. From the returned list in method 2, find the parameter name and return the associated value.
Is there a way to do what I'm trying to do?
You can do:
var retval = data.Select(t => mn.GetGetMethod().Invoke(t, null));
or
var retval = data.Select(t => mn.GetValue(t, null));
You can also simplify your code with something like this (not tested, sorry):
public static async Task<T> GetDataFromTable<T>(string paramName)
{
var k = Activator.CreateInstance(typeof(T));
var mn = typeof(T).GetProperty(paramName);
if (mn == null)
return (T)k;
var data = GetDataFromListTable<T>();
var retval = data.Select(t => mn.GetGetMethod().Invoke(t, null));
...
}
I think using expression trees would be more convenient since you're working with collections. Your method signature needs to incorporate the types T and TResult since it is using Select which returns an IEnumerable<TResult>.
public static async Task<IEnumerable<TResult>> SelectData<T, TResult>(
string propertyName
)
{
if(string.IsNullOrWhiteSpace(propertyName))
{
return Enumerable.Empty<TResult>();
}
var dataTask = GetTableData<T>();
var tType = Expression.Parameter(typeof(T), "t");
var property = Expression.Property(tType, propertyName);
var selectExpression =
Expression.Lambda<Func<T, TResult>>(property, tType)
.Compile();
return (await dataTask).Select(selectExpression);
}
Isn't it possible to do this
var retval = data.Select(t => mn.GetValue(t, null));
I have been trying to write a method that will build an expression based on types and parameters passed in. Currently the method is:
// tuple: CollectionName, ClassName, PropertyName
public Expression<Func<T, bool>> BuildCollectionWithLike<T, TSub>(Dictionary<Tuple<string, string, string>, string> properties)
{
// each one should generate something like:
// x => x.PreviousSKUs.Where(y => y.PreviousSku.Contains("a"))
try
{
var type = typeof(T);
List<Expression> expressions = new List<Expression>();
var xParameter = Expression.Parameter(typeof(T), "x");
foreach (var key in properties.Keys)
{
var collectionType = typeof(TSub);
var yParameter = Expression.Parameter(typeof(TSub), "y");
var propertyExp = Expression.Property(yParameter, key.Item3);
MethodInfo methodContains = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var someValue = Expression.Constant(properties[key], typeof(string));
var containsMethodExp = Expression.Call(propertyExp, methodContains, someValue);
var whereProperty = type.GetProperty(key.Item1);
var wherePropertyExp = Expression.Property(xParameter, whereProperty);
Func<IEnumerable<T>, Func<T, bool>, IEnumerable<T>> whereDelegate = Enumerable.Where;
MethodInfo whereMethodInfo = whereDelegate.Method;
var whereMethodExp = Expression.Call(whereMethodInfo, wherePropertyExp, containsMethodExp);
expressions.Add(whereMethodExp);
}
Expression final = expressions.First();
foreach (var expression in expressions.Skip(1))
{
final = Expression.Or(final, expression);
}
Expression<Func<T, bool>> predicate =
(Expression<Func<T, bool>>)Expression.Lambda(final, xParameter);
return predicate;
}
catch (Exception ex)
{
return null;
}
}
However at this line:
var whereMethodExp = Expression.Call(whereMethodInfo, wherePropertyExp, containsMethodExp);
I get this exception:
Expression of type 'System.Collections.Generic.ICollection`1[Model.ProductPreviousSku]'
cannot be used for parameter of type 'System.Collections.Generic.IEnumerable`1[Model.Product]'
of method 'System.Collections.Generic.IEnumerable`1[Model.Product] Where[Product](System.Collections.Generic.IEnumerable`1[Model.Product], System.Func`2[Model.Product,System.Boolean])'"
My class Model.Product has a property of type ICollection called PreviousSKUs.
I have a class called ProductPreviousSku which has a property of type string called PreviousSku.
As per my comment in at the start of the method I am trying to get this method to be able to construct an expression inside the foreach loop that looks like:
x => x.PreviousSKUs.Where(y => y.PreviousSku.Contains("a"))
I'm struggling to get passed this error at the moment so any help would be fantastic !