I'm using Visual Studio 2015, Entity Framework 6, and trying to build a LINQ expression to fetch results based on a dynamic WHERE clause. The user can choose to search on employeeId, securityId (which is a string), or lastName. For last name, it should do a case-insensitive search, so user can enter upper or lowercase searchValue.
Here's what I've got:
public async Task<ObservableCollection<EmployeeViewModel>>
SearchEmployeesAsync(string selectedColumn, string searchValue)
{
var paramEmployee = Expression.Parameter(typeof(Employee), "e");
Func<EmployeeBase, bool> comparison = null;
if (selectedColumn.Equals("employeeId"))
{
var employeeId = -1;
int.TryParse(searchValue, out employeeId);
comparison = Expression.Lambda<Func<Employee, bool>>(
Expression.Equal(
Expression.Property(paramEmployee, selectedColumn),
Expression.Constant(employeeId)),
paramEmployee).Compile();
}
else
{
comparison = Expression.Lambda<Func<Employee, bool>>(
Expression.Equal(
Expression.Property(paramEmployee, selectedColumn),
Expression.Constant(searchValue)),
paramEmployee).Compile();
}
using (var context = new MyEntities())
{
var query = (from e in context.Employees
.Where(comparison)
select new EmployeeViewModel
{
// Populate view model from entity object here
});
return await Task.Run(() => new ObservableCollection<EmployeeViewModel>(query));
}
}
How do I change the above code to make comparison to be case-insensitive for searches on securityId or lastName (both are strings on the database)? securityId or lastName are covered by the else block above. I'm also open to refactoring the code if there's a better way. One thing I don't want to do is use a third-party library to write dynamic WHERE clauses.
Thank you.
If you want the filtering to be applied in the database and not in the memory, it's essential to use Expression<Func<Employee, bool>> in Where clause rather than Func<Employee, bool> as in your code. The case insensitive comparison can be simulating by using ToLower method.
Also, as others mentioned it would be better to eliminate Task.Run call by using ToListAsync method from System.Data.Linq.QueryableExtensions.
With that being said, the implementation could be like this:
using System.Data.Entity;
public async Task<ObservableCollection<EmployeeViewModel>>
SearchEmployeesAsync(string selectedColumn, string searchValue)
{
var parameter = Expression.Parameter(typeof(T), "e");
Expression left = Expression.PropertyOrField(parameter, selectedColumn);
object value = searchValue;
if (selectedColumn == "employeeId")
{
var employeeId = -1;
int.TryParse(searchValue, out employeeId);
value = employeeId;
}
else
{
// case insensitive
left = Expression.Call(left, "ToLower", Type.EmptyTypes);
value = searchValue.ToLower();
}
var comparison = Expression.Lambda<Func<T, bool>>(
Expression.Equal(left, Expression.Constant(value)),
parameter);
using (var context = new MyEntities())
{
var query = context.Employees
.Where(comparison)
.Select(e => new EmployeeViewModel
{
// Populate view model from entity object here
});
var result = await query.ToListAsync();
return new ObservableCollection<EmployeeViewModel>(result);
}
}
I would recommend doing it the easy way. And I question the Task.Run, but since that wasn't part of the question, I left it alone.
public async Task<ObservableCollection<EmployeeViewModel>>
SearchEmployeesAsync(string selectedColumn, string searchValue)
{
using (var context = new MyEntities())
{
var query = context.Employees.AsQueryable();
switch(selectedColumn)
{
case "employeeId":
var employeeId = -1;
int.TryParse(searchValue, out employeeId);
query = query.Where(e=>e.employeeId == employeeId);
break;
case "lastName":
query = query.Where(e=>e.lastName == searchValue);
break;
case "securityId":
query = query.Where(e=>e.securityId == searchValue);
break;
}
query = query.Select(e=> new EmployeeViewModel
{
// Populate view model from entity object here
});
return await Task.Run(() => new ObservableCollection<EmployeeViewModel>(query));
}
}
Related
I have a list of Countries in a dbContext using EfCore
I want to implement a generic way of filtering this list
bool FilterCountriesv1(Country s)
{
var result = s.Name.StartsWith("A") && s.Name.Length > 4;
return result;
}
private async Task<List<Country>> GetCountriesAsync(Func<Country, bool> func)
{
var allCountries = DbContext.Countries.AsQueryable();
var filteredCountries = allCountries.Where(x=>func(x)).AsQueryable();
var result = await filteredCountries.ToListAsync();
return result;
}
Where I use it like this
var countries = await GetCountriesAsync(FilterCountriesv1);
When I run this I get the error
The LINQ expression 'DbSet<Country>.Where(c => Invoke(__func_0, c[Country])
)' could not be translated.
Either rewrite the query in a form that can be translated,
or switch to client evaluation explicitly by inserting a call to either AsEnumerable(),
AsAsyncEnumerable()
Not sure how to resolve this?
I do not think you can use pure delegates when trying to execute against sql. EF cant translate you pure delegate to sql.
Try
Expression<Func<Country, bool>> expected = p=> p.Name.StartsWith("A") && p.Name.Length > 4;
As stated by #COLD TOLD you should use an expression
Change your code to retrieve data to this:
private async Task<List<Country>> GetCountriesAsync(Expression<Func<Country, bool>> expression)
{
var allCountries = DbContext.Countries.AsQueryable();
var filteredCountries = allCountries.Where(expression).AsQueryable();
var result = await filteredCountries.ToListAsync();
return result;
}
Change your filter to return an expression
Expression<Func<Country, bool>> FilterCountriesv1(Country s)
{
return (x) => x.Name.StartsWith("A") && x.Name > 4;
// if you want to use s as a filter you can do
// return (x) => x.Name.StartsWith(s.Name) && x.Name > 4
}
And call like this:
var countries = await GetCountriesAsync(FilterCountriesv1(new Country() { Name = "A"} ));
For conversion use this (install nuget package Serialize.Linq)
private string SerializeExpression(Expression expression)
{
string result;
using (MemoryStream stream = new MemoryStream())
using (StreamReader reader = new StreamReader(stream))
{
new ExpressionSerializer(new JsonSerializer()).Serialize(stream, expression);
stream.Position = 0;
result = reader.ReadToEnd();
}
return result;
}
private Expression<Func<T, bool>> DeserializeExpression<T>(string expression)
{
Expression<Func<T, bool>> result;
using (MemoryStream stream = new MemoryStream())
using (StreamWriter writer = new StreamWriter(stream))
{
writer.Write(expression);
writer.Flush();
stream.Position = 0;
result = new ExpressionSerializer(new JsonSerializer()).Deserialize(stream) as Expression<Func<T, bool>>;
}
return result;
}
And then you can do something like this
string expression = SerializeExpression(FilterCountriesv1(new Country() { Name = "A"}));
var countries = dbContext.Countries.Where(DeserializeExpression<Country>(expression)).ToList();
*Note that the JsonSerializer used for serializing and deserializing is the one under Serializers.Linq.Serializers and not the one under Newtonsoft.Json
The filter should be a string and convert it to the lambda expression, but make sure the property exists in the passed in model. Try like below:
public List<T> GetFilterResult<T>(IQueryable<T> currentlist, string filter)
{
var exp = DynamicExpressionParser.ParseLambda<T, bool>(ParsingConfig.Default, false, filter, currentlist);
var func = exp.Compile();
var result = currentlist.Where(func).AsQueryable().ToList();
return result;
}
string Filter()
{
string filter = "x => x.Name.StartsWith(\"A\") && x.Name.Length > 4";
return filter;
}
Use it like this:
var countries = _context.Countries.AsQueryable();
var result = GetFilterResult(countries, Filter());
Working on a asp net core 2.2 application. I want to dynamic order a query result.
this is the code I have:
public IActionResult OnGetRecords(int pagenum, int pagesize, string sortDataField, string sortOrder)
{
sortOrder = sortOrder ?? "asc";
var Mut = from M in _DB.Mutations
join S in _DB.Shifts on M.ShiftId equals S.ShiftId
join U in _DB.RoosterUsers on M.UserId equals U.RoosterUserId
select new MutationModel
{
MutId=M.MutationId,
Naam=U.FirstName + " " + U.LastName,
UserId=M.UserId,
MutationType =S.publicName,
DateVan=M.DateStartOn,
DateTot=M.DateTill
};
if (sortDataField != null)
{
if (sortOrder == "asc")
{
Mut = Mut.OrderBy(m => m.GetType().GetProperty(sortDataField).GetValue(m, null));
}
else
{
Mut = Mut.OrderByDescending(m => m.GetType().GetProperty(sortDataField).GetValue(m, null));
}
}
int total = Mut.Count();
var Tresult = Mut.Skip(pagenum * pagesize).Take(pagesize);
var uit = new
{
TotalRows = total,
Rows = Tresult
};
return new JsonResult(uit);
}
}
But it is not working, when I try to order on a field, the line:
Mut = Mut.OrderBy(m => m.GetType().GetProperty(sortDataField).GetValue(m, null));
is not giving an error but is returning an result with out records.
Is EF core different from the 'old' EF in this?
somebody knows how to do this in EF Core
m => m.GetType().GetProperty(sortDataField).GetValue(m, null)
Is not a valid expression for OrderByin this case that can be translated into valid SQL for EF to execute
You will need to use the sortDataField to build an expression dynamically to use with the OrderBy calls.
The following is done as an extension method for convenience
public static Expression<Func<TModel, object>> GetPropertyExpression<TModel>(this IEnumerable<TModel> model, string propertyName) {
// Manually build the expression tree for
// the lambda expression m => m.PropertyName.
// (TModel m) =>
var parameter = Expression.Parameter(typeof(TModel), "m");
// (TModel m) => m.PropertyName
var property = Expression.PropertyOrField(parameter, propertyName);
// (TModel m) => (object) m.PropertyName
var cast = Expression.Convert(property, typeof(object));
var expression = Expression.Lambda<Func<TModel, object>>(cast, parameter);
return expression;
}
It builds up the expression tree for sorting that can then be used like
if (sortDataField != null) {
//m => m.sortDataField
var keySelector = Mut.GetPropertyExpression(sortDataField);
if (sortOrder == "asc") {
Mut = Mut.OrderBy(keySelector);
} else {
Mut = Mut.OrderByDescending(keySelector);
}
}
to order the query
Sorry to be late with this. Thanks for your help. But the problem was something different. In Asp.net.core you send the result via return new JsonResult(uit); to the client.
The problem is that capitalized field names are changed to non capitalized names. so If you send them back to the server again with an Ajax call you have to Capitalize them again!
I try to perform a simple LIKE action on the database site, while having query building services based on generic types. I found out while debugging however, that performing EF.Functions.Like() with reflection does not work as expected:
The LINQ expression 'where __Functions_0.Like([c].GetType().GetProperty("FirstName").GetValue([c], null).ToString(), "%Test%")' could not be translated and will be evaluated locally..
The code that makes the difference
That works:
var query = _context.Set<Customer>().Where(c => EF.Functions.Like(c.FirstName, "%Test%"));
This throws the warning & tries to resolve in memory:
var query = _context.Set<Customer>().Where(c => EF.Functions.Like(c.GetType().GetProperty("FirstName").GetValue(c, null).ToString(), "%Test%"));
Does the Linq query builder or the EF.Functions not support reflections?
Sorry if the questions seem basic, it's my first attempt with .NET Core :)
In EF the lambdas are ExpressionTrees and the expressions are translated to T-SQL so that the query can be executed in the database.
You can create an extension method like so:
public static IQueryable<T> Search<T>(this IQueryable<T> source, string propertyName, string searchTerm)
{
if (string.IsNullOrEmpty(propertyName) || string.IsNullOrEmpty(searchTerm))
{
return source;
}
var property = typeof(T).GetProperty(propertyName);
if (property is null)
{
return source;
}
searchTerm = "%" + searchTerm + "%";
var itemParameter = Parameter(typeof(T), "item");
var functions = Property(null, typeof(EF).GetProperty(nameof(EF.Functions)));
var like = typeof(DbFunctionsExtensions).GetMethod(nameof(DbFunctionsExtensions.Like), new Type[] { functions.Type, typeof(string), typeof(string) });
Expression expressionProperty = Property(itemParameter, property.Name);
if (property.PropertyType != typeof(string))
{
expressionProperty = Call(expressionProperty, typeof(object).GetMethod(nameof(object.ToString), new Type[0]));
}
var selector = Call(
null,
like,
functions,
expressionProperty,
Constant(searchTerm));
return source.Where(Lambda<Func<T, bool>>(selector, itemParameter));
}
And use it like so:
var query = _context.Set<Customer>().Search("FirstName", "Test").ToList();
var query2 = _context.Set<Customer>().Search("Age", "2").ToList();
For reference this was the Customer I used:
public class Customer
{
[Key]
public Guid Id { get; set; }
public string FirstName { get; set; }
public int Age { get; set; }
}
Simple answer, no.
EntityFramework is trying to covert your where clause in to a SQL Query. There is no native support for reflection in this conversation.
You have 2 options here. You can construct your text outside of your query or directly use property itself. Is there any specific reason for not using something like following?
var query = _context.Set<Customer>().Where(c => EF.Functions.Like(c.FirstName, "%Test%"));
Keep in mind that every ExpresionTree that you put in Where clause has to be translated into SQL query.
Because of that, ExpressionTrees that you can write are quite limited, you have to stick to some rules, thats why reflection is not supported.
Image that instead of :
var query = _context.Set<Customer>().Where(c => EF.Functions.Like(c.GetType().GetProperty("FirstName").GetValue(c, null).ToString(), "%Test%"));
You write something like:
var query = _context.Set<Customer>().Where(c => EF.Functions.Like(SomeMethodThatReturnsString(c), "%Test%"));
It would mean that EF is able to translate any c# code to SQL query - it's obviously not true :)
I chucked together a version of the accepted answer for those using NpgSQL as their EF Core provider as you will need to use the ILike function instead if you want case-insensitivity, also added a second version which combines a bunch of properties into a single Where() clause:
public static IQueryable<T> WhereLike<T>(this IQueryable<T> source, string propertyName, string searchTerm)
{
// Check property name
if (string.IsNullOrEmpty(propertyName))
{
throw new ArgumentNullException(nameof(propertyName));
}
// Check the search term
if(string.IsNullOrEmpty(searchTerm))
{
throw new ArgumentNullException(nameof(searchTerm));
}
// Check the property exists
var property = typeof(T).GetProperty(propertyName);
if (property == null)
{
throw new ArgumentException($"The property {typeof(T)}.{propertyName} was not found.", nameof(propertyName));
}
// Check the property type
if(property.PropertyType != typeof(string))
{
throw new ArgumentException($"The specified property must be of type {typeof(string)}.", nameof(propertyName));
}
// Get expression constants
var searchPattern = "%" + searchTerm + "%";
var itemParameter = Expression.Parameter(typeof(T), "item");
var functions = Expression.Property(null, typeof(EF).GetProperty(nameof(EF.Functions)));
var likeFunction = typeof(NpgsqlDbFunctionsExtensions).GetMethod(nameof(NpgsqlDbFunctionsExtensions.ILike), new Type[] { functions.Type, typeof(string), typeof(string) });
// Build the property expression and return it
Expression selectorExpression = Expression.Property(itemParameter, property.Name);
selectorExpression = Expression.Call(null, likeFunction, functions, selectorExpression, Expression.Constant(searchPattern));
return source.Where(Expression.Lambda<Func<T, bool>>(selectorExpression, itemParameter));
}
public static IQueryable<T> WhereLike<T>(this IQueryable<T> source, IEnumerable<string> propertyNames, string searchTerm)
{
// Check property name
if (!(propertyNames?.Any() ?? false))
{
throw new ArgumentNullException(nameof(propertyNames));
}
// Check the search term
if (string.IsNullOrEmpty(searchTerm))
{
throw new ArgumentNullException(nameof(searchTerm));
}
// Check the property exists
var properties = propertyNames.Select(p => typeof(T).GetProperty(p)).AsEnumerable();
if (properties.Any(p => p == null))
{
throw new ArgumentException($"One or more specified properties was not found on type {typeof(T)}: {string.Join(",", properties.Where(p => p == null).Select((p, i) => propertyNames.ElementAt(i)))}.", nameof(propertyNames));
}
// Check the property type
if (properties.Any(p => p.PropertyType != typeof(string)))
{
throw new ArgumentException($"The specified properties must be of type {typeof(string)}: {string.Join(",", properties.Where(p => p.PropertyType != typeof(string)).Select(p => p.Name))}.", nameof(propertyNames));
}
// Get the expression constants
var searchPattern = "%" + searchTerm + "%";
var itemParameter = Expression.Parameter(typeof(T), "item");
var functions = Expression.Property(null, typeof(EF).GetProperty(nameof(EF.Functions)));
var likeFunction = typeof(NpgsqlDbFunctionsExtensions).GetMethod(nameof(NpgsqlDbFunctionsExtensions.ILike), new Type[] { functions.Type, typeof(string), typeof(string) });
// Build the expression and return it
Expression selectorExpression = null;
foreach (var property in properties)
{
var previousSelectorExpression = selectorExpression;
selectorExpression = Expression.Property(itemParameter, property.Name);
selectorExpression = Expression.Call(null, likeFunction, functions, selectorExpression, Expression.Constant(searchPattern));
if(previousSelectorExpression != null)
{
selectorExpression = Expression.Or(previousSelectorExpression, selectorExpression);
}
}
return source.Where(Expression.Lambda<Func<T, bool>>(selectorExpression, itemParameter));
}
I have a situation where I am using a linq provider that does not support the .Contains method to generate a WHERE IN clause in the query. I am looking for a way to generate the (Value = X OR Value = Y OR Value = Z) statement dynamically from the list of items to match. I haven't found a good example of building expression trees to do this.
Normally I would query this way:
var names = new string[] { "name1", "name2", "name3" }
var matches = query.Where(x => names.Contains(x.Name));
So far the closest thing I could find was to use the
Dynamic Linq Library and build a string to be interpreted but it feels a bit too hacky.
You don't even need an external library for something like this:
var names = new string[] { "name1", "name2", "name3" };
// Where MyClass is the type of your class
ParameterExpression par = Expression.Parameter(typeof(MyClass));
MemberExpression prop = Expression.Property(par, "Name");
Expression expression = null;
foreach (string name in names)
{
Expression expression2 = Expression.Equal(prop, Expression.Constant(name));
if (expression == null)
{
expression = expression2;
}
else
{
expression = Expression.OrElse(expression, expression2);
}
}
var query = ...; // Your query
if (expression != null)
{
// Where MyClass is the type of your class
var lambda = Expression.Lambda<Func<MyClass, bool>>(expression, par);
query = query.Where(lambda);
}
You can build a concatenation of Expression.OrElse, with the comparisons between the property Name and one of the strings.
In this particular case (3 strings), the resulting Expression, when looked from the debugger, is:
(((Param_0.Name == "name1") OrElse (Param_0.Name == "name2")) OrElse (Param_0.Name == "name3"))
Just improving the answer by xanatos:
var names = new string[] { "name1", "name2", "name3" };
var query = ...; // Your query
if (names.Any())
{
// Where MyClass is the type of your class
ParameterExpression par = Expression.Parameter(typeof(MyClass));
MemberExpression prop = Expression.Property(par, "Name");
var expression=names
.Select(v => Expression.Equal(prop, Expression.Constant(v)))
.Aggregate(Expression.OrElse);
// Where MyClass is the type of your class
var lambda = Expression.Lambda<Func<MyClass, bool>>(expression, par);
query = query.Where(lambda);
}
I'm trying to generate a LINQ OrderBy clause using lambda expressions with an input of the column name of an entity as a string (in the "sortOn" variable below).
The code below works fine for a sortOn value like "Code" generating the lambda
p => p.Code
But I would also like to sort on a child entity, where the lambda might be
p => p.Category.Description
So in this instance, I would just like to set sortOn = "Category.Description" and have the correct lamdba expression generated.
Is this possible? Any suggestions about the best way to do this would be welcomed.
This code works fine for the simple case:
var param = Expression.Parameter(typeof (Product), "p");
var sortExpression = Expression.Lambda<Func<Product, object>>(
Expression.Property(param, sortOn), param);
if (sortAscending ?? true)
{
products = products.OrderBy(sortExpression);
}
else
{
products = products.OrderByDescending(sortExpression);
}
The use-case for this problem is displaying a grid of data and being able to sort the data, simply by passing the column name to be sorted on back to the server. I'd like to make the solution generic, but have started using a particular type (Product in the example) for now.
This will generate proper lambda expression:
var sortOn = "Category.Description";
var param = Expression.Parameter(typeof(Product), "p");
var parts = sortOn.Split('.');
Expression parent = param;
foreach (var part in parts)
{
parent = Expression.Property(parent, part);
}
var sortExpression = Expression.Lambda<Func<Product, object>>(parent, param);
Here is an extension OrderBy method which works for any number of nested parameters.
public static IQueryable<T> OrderBy<T>(this IQueryable<T> query, string key, bool asc = true)
{
try
{
string orderMethodName = asc ? "OrderBy" : "OrderByDescending";
Type type = typeof(T);
Type propertyType = type.GetProperty(key)?.PropertyType; ;
var param = Expression.Parameter(type, "x");
Expression parent = param;
var keyParts = key.Split('.');
for (int i = 0; i < keyParts.Length; i++)
{
var keyPart = keyParts[i];
parent = Expression.Property(parent, keyPart);
if (keyParts.Length > 1)
{
if (i == 0)
{
propertyType = type.GetProperty(keyPart).PropertyType;
}
else
{
propertyType = propertyType.GetProperty(keyPart).PropertyType;
}
}
}
MethodCallExpression orderByExpression = Expression.Call(
typeof(Queryable),
orderMethodName,
new Type[] { type, propertyType },
query.Expression,
CreateExpression(type, key)
);
return query.Provider.CreateQuery<T>(orderByExpression);
}
catch (Exception e)
{
return query;
}
}
The CreateExpression method which is used in my solution is defined in this post.
The usage of the OrderBy extension method is as follows.
IQueryable<Foo> q = [Your database context].Foos.AsQueryable();
IQueryable<Foo> p = null;
p = q.OrderBy("myBar.name"); // Ascending sort
// Or
p = q.OrderBy("myBar.name", false); // Descending sort
// Materialize
var result = p.ToList();
The type Foo and its properties are also taken from the same post as method CreateExpression.
Hope you find this post helpful.
You can use the Dynamic LINQ Query Library to do this easily. Assuming you have an IQueryable<T> implementation of Product, you can easily do:
IQueryable<Product> products = ...;
// Order by dynamically.
products = products.OrderBy("Category.Description");
The blog post has a link to the libary, and you'll have to build/include the project in your solution yourself, but it works very well, and the parsing is very robust. It prevents you from having to write the parsing code yourself; even for something so simple, if the requirements expand, the library has you covered, whereas a homegrown solution does not.
It also has a number of other dynamic operators (Select, Where, etc.) so you can perform other dynamic operations.
There's no magic under the hood, it just parses the strings you pass it and then creates the lambda expressions based on the parsing results.
If you don't need expressions, how about:
products = products.Orderby(p1 => p1.Code).ThenBy(p2 => p2.Category.Description)
Hi you can also create an extension method like which can sort to any depth not only just child
public static IEnumerable<TSource> CustomOrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
List<string> list=new List<string>();
List<TSource> returnList=new List<TSource>();
List<int> indexList = new List<int>();
if (source == null)
return null;
if (source.Count() <= 0)
return source;
source.ToList().ForEach(sc=>list.Add(keySelector(sc).ToString())); //Extract the strings of property to be ordered
list.Sort(); //sort the list of strings
foreach (string l in list) // extract the list of indexes of source according to the order
{
int i=0;
//list.ForEach(l =>
foreach (var s in source.ToList())
{
if (keySelector(s).ToString() == l)
break;
i++;
}
indexList.Add(i);
}
indexList.ForEach(i=>returnList.Add(source.ElementAt(i))); //rearrange the source according to the above extracted indexes
return returnList;
}
}
public class Name
{
public string FName { get; set; }
public string LName { get; set; }
}
public class Category
{
public Name Name { get; set; }
}
public class SortChild
{
public void SortOn()
{
List<Category> category = new List<Category>{new Category(){Name=new Name(){FName="sahil",LName="chauhan"}},
new Category(){Name=new Name(){FName="pankaj",LName="chauhan"}},
new Category(){Name=new Name(){FName="harish",LName="thakur"}},
new Category(){Name=new Name(){FName="deepak",LName="bakseth"}},
new Category(){Name=new Name(){FName="manish",LName="dhamaka"}},
new Category(){Name=new Name(){FName="arev",LName="raghaka"}}
};
var a = category.CustomOrderBy(s => s.Name.FName);
}
}
Its custom method and right now it works only for string property only however it can be reactified using generics to work for any primitive type. I hope this will help.