Expression predicate must be built separately with if and else blocks? - c#

I have 2 versions of codes for class method "GetUserRoles". Version 1 always works OK while Version 2 is not working if passed-in argument "exludeRoleNames" for that method is absent and exception "Null object reference" is thrown as a result. I would like to understand why Version 2 codes are not always working, and why Version 1 codes require building predicate with "if" and "else" blocks. Thank you in advance.
Version 1:
The following codes are always working. You could notice that there are separate "if" and "else" blocks to build local variable Expression<Func<UserRoleInApplication, bool>> predicate
public List<UserInRoleViewModel> GetUserRoles(long userId, params string[] exludeRoleNames)
{
List<UserInRoleViewModel> results = null;
IQueryable<UserInRoleViewModel> items = null;
Expression<Func<UserRoleInApplication, bool>> predicate = null;
if (exludeRoleNames.Count() <= 0)
{
predicate = x => true;
}
else
{
predicate = x => !exludeRoleNames.Contains(x.UserRole.RoleName);
}
items = from uir in _repository.GetQuery<UserInRole>(x => x.UserId == userId)
join ura in _repository.GetQuery<UserRoleInApplication>(predicate)
on uir.UserRoleInApplicationId equals ura.UserRoleInApplicationId
into g
from item in g
select new UserInRoleViewModel
{
UserInRoleId = uir.UserInRoleId,
UserId = uir.UserId,
UserRoleInApplicationId = uir.UserRoleInApplicationId
};
if (items != null && items.Any())
{
results = new List<UserInRoleViewModel>();
results = items.ToList();
}
return results;
}
Version 2:
However, the following codes throw exception "Null object reference" when calling method GetUserRoles such as GetUserRoles(long userId) without passed-in argument "exludeRoleNames". You could notice that there are no separate "if" and "else" blocks for the codes to form local variable Expression<Func<UserRoleInApplication, bool>> predicate:
public List<UserInRoleViewModel> GetUserRoles(long userId, params string[] exludeRoleNames)
{
List<UserInRoleViewModel> results = null;
IQueryable<UserInRoleViewModel> items = null;
// Note: one line code and no separate "if" and "else" blocks *************
Expression<Func<UserRoleInApplication, bool>> predicate = x => exludeRoleNames.Count() <= 0 ? true : !exludeRoleNames.Contains(x.UserRole.RoleName);
items = from uir in _repository.GetQuery<UserInRole>(x => x.UserId == userId)
join ura in _repository.GetQuery<UserRoleInApplication>(predicate)
on uir.UserRoleInApplicationId equals ura.UserRoleInApplicationId
into g
from item in g
select new UserInRoleViewModel
{
UserInRoleId = uir.UserInRoleId,
UserId = uir.UserId,
UserRoleInApplicationId = uir.UserRoleInApplicationId
};
if (items != null && items.Any()) // Note: throw exception "Null object reference" if parameter "exludeRoleNames" is absent on calling method GetUserRoles such as GetUserRoles(long userId);
{
results = new List<UserInRoleViewModel>();
results = items.ToList();
}
return results;
}

Try changing this:
Expression<Func<UserRoleInApplication, bool>> predicate = x => exludeRoleNames.Count() <= 0 ? true : !exludeRoleNames.Contains(x.UserRole.RoleName);
To this:
Expression<Func<UserRoleInApplication, bool>> predicate = x => true;
if(exludeRoleNames != null)
{
foreach(string exl in exludeRoleNames)
{
string temp = exl;
predicate = predicate.Or(x=>x.UserRole.RoleName == temp);
}
}
The problem is that you're trying to call Count() on exludeRoleNames - which is null. So, rather than check Count(), compare it to null. If it is null, then you can treat it as an empty array. If it isn't null, then check it's contents.
The other problem is that you can't use string[].Contains in a query context. Entity Framework (which I assume you are using) doesn't support that. So, you have to build out the predicate.

I re-write the following codes and they are working. In case of absent parameter "exludeRoleNames", the codes creat an empty string array:
public List<UserInRoleViewModel> GetUserRoles(long userId, params string[] exludeRoleNames)
{
List<UserInRoleViewModel> results = null;
IQueryable<UserInRoleViewModel> items = null;
exludeRoleNames = !exludeRoleNames.Any() ? new string[] { } : exludeRoleNames; // a MUST
Expression<Func<UserRoleInApplication, bool>> predicate = x => !exludeRoleNames.Contains(x.UserRole.RoleName);
items = from uir in _repository.GetQuery<UserInRole>(x => x.UserId == userId)
join ura in _repository.GetQuery<UserRoleInApplication>(predicate)
on uir.UserRoleInApplicationId equals ura.UserRoleInApplicationId
into g
from item in g
select new UserInRoleViewModel
{
UserInRoleId = uir.UserInRoleId,
UserId = uir.UserId,
UserRoleInApplicationId = uir.UserRoleInApplicationId
};
if (items != null && items.Any())
{
results = new List<UserInRoleViewModel>();
results = items.ToList();
}
return results;
}

Related

An exception was thrown while attempting to evaluate a LINQ query parameter expression

I got error in the tittle. Probably i must control string.IsNullOrEmpty(searchString) but i didn't it. My code bellow. Please help me
Thank you everyone i solved this problem. Problem was not here. Prooblem in my route codes. for search is different i would write my route codes this
endpoints.MapControllerRoute(
name:"search",
pattern: "{search}",
defaults: new {controller="Shop",action="search"}
);
but not in from of pattern: "{search}
should be this pattern: "search"
Thank you to everyone who helped
public List<Product> GetSearchResult(string searchString)
{
using (var context = new ShopContext())
{
var products = context
.Products
.Where(i=> i.IsApproved && (i.Name.ToLower().Contains(searchString.ToLower()) || i.Description.ToLower().Contains(searchString.ToLower())))
.AsQueryable();
return products.ToList();
}
}
I would break this up as follows:
public List<Product> GetSearchResult(string searchString)
{
// What do you want to do if searchString is null or blank? (Pick one:)
// You could send back an empty result...
if (String.IsNullOrWhiteSpace(searchString))
return null;
// Or you could convert it to a blank string
if (searchString == null)
searchString = "";
List<Product> products = new List<Product>();
using (var context = new ShopContext())
{
products = context.Products.ToList();
}
// Always check for null and empty after going to the DB
if (products == null || products.count = 0)
return null;
// If we are still here, then we can finally do the search
List<Product> results = products.Where(i=> i.IsApproved &&
(i.Name != null && i.Name.ToLower().Contains(searchString.ToLower()) ||
(i.Description != null && i.Description.ToLower().Contains(searchString.ToLower())));
return results;
}
Note: I've not tested this and there may be syntax errors in the last LINQ statement with all of the ('s and )'s.
EDIT:
The example above will pull back ALL records in the Product table and then filter the results in-memory. If you want to avoid that, then I think this should work:
public List<Product> GetSearchResult(string searchString)
{
// What do you want to do if searchString is null or blank? (Pick one:)
// You could send back an empty result...
if (String.IsNullOrWhiteSpace(searchString))
return null;
// Or you could convert it to a blank string
if (searchString == null)
searchString = "";
using (var context = new ShopContext())
{
List<Product> products = context.Products.Where(i=> i.IsApproved &&
(i.Name != null && i.Name.ToLower().Contains(searchString.ToLower()) ||
(i.Description != null && i.Description.ToLower().Contains(searchString.ToLower()))).ToList();
return products;
}
}
The key difference between this and your OP is that we are checking for null's on Name and Description -- and I believe this does it in a way that EF can translate into a query.
Not a lot of information at hand but i assume the LINQ fails because a string is null.
Also why do you create a queryable of your results and then produce a list?
public List<Product> GetSearchResult(string searchString) {
static bool Contains(string a, string b) {
return a.ToLower().Contains(b.ToLower());
}
using (var context = new ShopContext()) {
return context
.Products
.Where(i => i.IsApproved)
.Where(i => i.Name is null ? false : Contains(i.Name, searchString)
|| i.Description is null ? false : Contains(i.Description, searchString))
.ToList();
}
}

Clean way to check if all properties, except for two, matches between two objects? [duplicate]

This question already has answers here:
Comparing object properties in c# [closed]
(20 answers)
Closed 4 years ago.
I have a database containing components with about 20 properties. To find out if an update is needed I want to check if all properties for the two objects, except DateCreated and Id, matches.
If all matches no update, if not, update db.
Component comp_InApp = new Component()
{
Id = null,
Description = "Commponent",
Price = 100,
DateCreated = "2019-01-30",
// Twenty more prop
};
Component comp_InDb = new Component()
{
Id = 1,
Description = "Component",
Price = 100,
DateCreated = "2019-01-01",
// Twenty more prop
};
// Check if all properties match, except DateCreated and Id.
if (comp_InApp.Description == comp_InDb.Description &&
comp_InApp.Price == comp_InDb.Price
// Twenty more prop
)
{
// Everything up to date.
}
else
{
// Update db.
}
This works, but it's not a very clean way with 20 properties. Is there a better way of achieiving the same result in a cleaner way?
I am using DeepEqual when I don't want/don't have the time to write myself Equals and GetHashCode methods.
You can install it simply from NuGet with:
Install-Package DeepEqual
and use it like:
if (comp_InApp.IsDeepEqual(comp_InDb))
{
// Everything up to date.
}
else
{
// Update db.
}
But keep in mind that this will only work for your case when you want to explicitly compare objects, but not for the case when you want to remove an object form a List or cases like this, when Equals and GetHashCode are invoked.
One way, create a class that implements IEqualityComparer<Component> to encapsulate this logic and to avoid that you have modify the class Comparer itself(if you don't want this Equals logic all time). Then you can use it for a simple Equals of two instances of Component and even for all LINQ methods that accepts it as additional argument.
class ComponentComparer : IEqualityComparer<Component>
{
public bool Equals(Component x, Component y)
{
if (object.ReferenceEquals(x, y)) return true;
if (x == null || y == null) return false;
return x.Price == y.Price && x.Description == y.Description;
}
public int GetHashCode(Component obj)
{
unchecked
{
int hash = 17;
hash = hash * 23 + obj.Price.GetHashCode();
hash = hash * 23 + obj.Description?.GetHashCode() ?? 0;
// ...
return hash;
}
}
}
Your simple use-case:
var comparer = new ComponentComparer();
bool equal = comparer.Equals(comp_InApp, comp_InDb);
It works also if you have two collections and want to know the difference, for example:
IEnumerable<Component> missingInDb = inAppList.Except( inDbList, comparer );
Here is a solution with Reflection:
static bool AreTwoEqual(Component inApp, Component inDb)
{
string[] propertiesToExclude = new string[] { "DateCreated", "Id" };
PropertyInfo[] propertyInfos = typeof(Component).GetProperties()
.Where(x => !propertiesToExclude.Contains(x.Name))
.ToArray();
foreach (PropertyInfo propertyInfo in propertyInfos)
{
bool areSame = inApp.GetType().GetProperty(propertyInfo.Name).GetValue(inApp, null).Equals(inDb.GetType().GetProperty(propertyInfo.Name).GetValue(inDb, null));
if (!areSame)
{
return false;
}
}
return true;
}
You can use a Reflection but it may slow your application. An alternative way of creating that comparator is to generate it with Linq Expressions. Try this code:
public static Expression<Func<T, T, bool>> CreateAreEqualExpression<T>(params string[] toExclude)
{
var type = typeof(T);
var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => !toExclude.Contains(p.Name))
.ToArray();
var p1 = Expression.Parameter(type, "p1");
var p2 = Expression.Parameter(type, "p2");
Expression body = null;
foreach (var property in props)
{
var pare = Expression.Equal(
Expression.PropertyOrField(p1, property.Name),
Expression.PropertyOrField(p2, property.Name)
);
body = body == null ? pare : Expression.AndAlso(body, pare);
}
if (body == null) // all properties are excluded
body = Expression.Constant(true);
var lambda = Expression.Lambda<Func<T, T, bool>>(body, p1, p2);
return lambda;
}
it will generate an expression that looks like
(Component p1, Component p2) => ((p1.Description == p2.Description) && (p1.Price == p2.Price))
Usage is simple
var comporator = CreateAreEqualExpression<Component>("Id", "DateCreated")
.Compile(); // save compiled comparator somewhere to use it again later
var areEqual = comporator(comp_InApp, comp_InDb);
EDIT: to make it more type safe you can exclude properties using lambdas
public static Expression<Func<T, T, bool>> CreateAreEqualExpression<T>(
params Expression<Func<T, object>>[] toExclude)
{
var exclude = toExclude
.Select(e =>
{
// for properties that is value types (int, DateTime and so on)
var name = ((e.Body as UnaryExpression)?.Operand as MemberExpression)?.Member.Name;
if (name != null)
return name;
// for properties that is reference type
return (e.Body as MemberExpression)?.Member.Name;
})
.Where(n => n != null)
.Distinct()
.ToArray();
var type = typeof(T);
var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => !exclude.Contains(p.Name))
.ToArray();
/* rest of code is unchanged */
}
Now when using it we have an IntelliSense support:
var comparator = CreateAreEqualExpression<Component>(
c => c.Id,
c => c.DateCreated)
.Compile();

dynamic orderBy using linq dynamic

I am trying to convert this Func to use string values using linq.dynamic.
Currently I have
Func<IQueryable<Customer>, IOrderedQueryable<Customer>> orderBy = o => o.OrderBy(c => c.Postcode);
But I want to do
string sortItem = "customer";
string order = "ASC"
Func<IQueryable<Customer>, IOrderedQueryable<Customer>> orderBy = o => o.OrderBy(sortItem + " " + order);
I am using the Linq.Dynamic library but I am unable to get it to work with the function.
Any help...
Like the other answer suggests, this may not be possible. However, I wanted to post some code which I wrote recently to do something similar:
// single column sorting support
var sortColumnIndex = Convert.ToInt32(Request["iSortCol_0"]);
Func<LegalComplianceDatatable, string> orderingFunction = (c => sortColumnIndex == 0 ? c.HomeCountry :
sortColumnIndex == 1 ? c.HostCountry :
sortColumnIndex == 2 ? c.YearOneRate :
sortColumnIndex == 3 ? c.YearOtherRate :
sortColumnIndex == 4 ? c.RateType :
c.HostCountry);
if (Request["sSortDir_0"] == "desc")
{
filteredResults = filteredResults.OrderByDescending(orderingFunction);
}
else
{
filteredResults = filteredResults.OrderBy(orderingFunction);
}
I haven't used Linq.Dynamic but you can achieve this if you are comfortable building your own expression tree. For example:
public static class IQueryableExtension
{
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> query, string propertyName)
{
var memberProp = typeof(T).GetProperty(propertyName);
var method = typeof(IQueryableExtension).GetMethod("OrderByInternal")
.MakeGenericMethod(typeof(T), memberProp.PropertyType);
return (IOrderedQueryable<T>)method.Invoke(null, new object[] { query, memberProp });
}
public static IOrderedQueryable<T> OrderByInternal<T, TProp>(IQueryable<T> query, PropertyInfo memberProperty)
{
if (memberProperty.PropertyType != typeof(TProp)) throw new Exception();
var thisArg = Expression.Parameter(typeof(T));
var lamba = Expression.Lambda<Func<T, TProp>>(Expression.Property(thisArg, memberProperty), thisArg);
return query.OrderBy(lamba);
}
}
And you can use this like:
IQueryable<Customer> query; // Some query
query = query.OrderBy("Name"); // Will return an IOrderedQueryable<Customer>
This is the logic for ascending sort, without checking (you will need to make sure the property exists, and so on) and some things can be optimized (the reflected method invocation for example). It should get you started.

Wildcard search in using Lambda in EF

I have a search with optional arguments:
string id1 = HttpContext.Current.Request["id1"];
string id2 = HttpContext.Current.Request["id2"];
List<Foo> list = context.Foo.Where(l =>
(string.IsNullOrEmpty(id1) || l.Id1.Contains(id1)) &&
(string.IsNullOrEmpty(id2) || l.Id2.Contains(id2)))
.Take(10)
.ToList());
I would like to extend it so that if the string starts with a *, the EndsWith() - method should be used.
For example, if the first searchstring is *123, I want to do a l.Id1.EndsWith("123").
Is there any way to extend my current code, or should I use a different approach?
I'm quite sure if this is what you were intending, but you can break your logic up by using IQueryable(T). The query won't be executed until you try to use the collection.
public IList<Foo> Search(DbContext<Foo> context, string id1, string id2)
{
Func<Foo, bool> predicate = l =>
(string.IsNullOrEmpty(id1) || l.Id1.Contains(id1))
&& (string.IsNullOrEmpty(id2) || l.Id2.Contains(id2)));
IQueryable<Foo> list = context.Foo.Where(predicate);
if(id1.StartsWith("*") && id1.Length > 1)
{
var searchTerm = id1.Substring(1, id1.Length);
list = list.Where(l => l.EndsWith(searchTerm));
}
return list.Take(10).ToList(); // Execution occurs at this point
}
Building up the query:
public void BasicSearch(IQueryable<foo> list, string id1, string id2)
{
Func<Foo, bool> predicate = l =>
(string.IsNullOrEmpty(id1) || l.Id1.Contains(id1))
&& (string.IsNullOrEmpty(id2) || l.Id2.Contains(id2)));
list.Where(predicate);
}
public void WildcardSearch(IQueryable<Foo> list, string id1)
{
if(!id1.StartsWith("*") || id1.Length <= 1) return;
var searchTerm = id1.Substring(1, id1.Length);
list.Where(l => l.EndsWith(searchTerm));
}
IQueryable<Foo> list = context.Foo;
BasicSearch(list, id1, id2);
WildcardSearch(list, id1);
var result = list.Take(10); // Execution occurs at this point

Problems with LINQ WhereClause

Many thanks to leppie:
Currently I got
Expression<Func<vwMailMerge,bool>> whereClause= null;
List<vwMailMerge> mailMergeItems = null;
int personType = mailMergeSettings.PersonType.ToInteger();
if (personType > 0)
{
whereClause = this.MailMergeWhereClause(whereClause, f => f.MemberTypeId == personType);
}
if (mailMergeSettings.PersonIds != null)
{
var personIds = mailMergeSettings.PersonIds.ToGuidArray();
if (personIds != null && personIds.Length > 0)
{
var personList = personIds.ToList();
whereClause = this.MailMergeWhereClause(whereClause, f => personList.Contains(f.UserId));
}
}
mailMergeItems = this.ObjectContext.vwMailMerges.Where(whereClause).ToList();
private Expression<Func<vwMailMerge, bool>> MailMergeWhereClause(params Expression<Func<vwMailMerge, bool>>[] wheres)
{
if (wheres.Length == 0)
{
return x => true;
}
Expression result;
if (wheres[0] == null)
{
result = wheres[1].Body;
return Expression.Lambda<Func<vwMailMerge, bool>>(result, wheres[1].Parameters);
}
else
{
result = wheres[0].Body;
for (int i = 1; i < wheres.Length; i++)
{
result = Expression.And(result, wheres[i].Body);
}
return Expression.Lambda<Func<vwMailMerge, bool>>(result, wheres[0].Parameters);
}
}
}
When it gets to "mailMergeItems =" it drops and gives error: "The parameter 'f' was not bound in the specified LINQ to Entities query expression."
I've noticed that when checking only for people, or only for membertypeId, it works properly.. but combined the 2nd gives a error on it's "f=>" I think.
You cant use Func, you need to use Expression<Func>.
The + can be done via Expression.And.
Update (not tested):
Expression<Func<vwMailMerge, bool>> whereClause = null;
...
Expression<Func<vwMailMerge, bool>> MailMergeWhereClause(
params Expression<Func<vwMailMerge, bool>>[] wheres)
{
if (wheres.Length == 0) return x => true;
Expression result = wheres[0].Body;
for (int i = 1; i < wheres.Length; i++)
{
//probaby needs a parameter fixup, exercise for reader
result = Expression.And(result, wheres[i].Body);
}
return Expression.Lambda<Func<vwMailMerge,bool>>(result, wheres[0].Parameters);
}
Update 2:
The above approach fails as I expected. It might be easy to solve on .NET 4 using the ExpressionVistor class. For .NET 3.5 (or if aforementioned is too hard) the following should work.
The approach is the append the where clauses in the IQueryable directly so you end up with:
somequery.Where(x => x.foo).Where(x => x.bar).Where(x => x.baz)
IOW, you can just add them as required, but it will require some changes to the logic/flow of the code you pasted.
You could reformat your question better with the code tool.
However it looks like you could approach the problem in this way to avoid all those func expressions floating around:
this.ObjectContext.vwMailMerges.Where(mm=>IsValidMailMerge(mm,personType)).ToList()
private bool IsValidMailMerge(YourType mailmerge, YourType2 personType)
{
if(...) // type specific criteria here
return true;
else
return false;
}

Categories