I'm applying a .Where()-restriction on an IQueryOver<T,T> in FluentNH, as such:
.Where(x => x.Col1 == null || x.Col1 == "");
Which generates the following SQL:
WHERE (Col1 IS NULL OR Col1 = NULL)
How can I make NH understand that empty string means empty string?
You can write your Where clause like this:
.Where(Restrictions.On<ClassType>(obj => obj.Col1).IsNull ||
Restrictions.On<ClassType>(obj => obj.Col1).IsLike(#""))
Alternatively, if you're doing this on several queries you should consider creating a query extension:
public static class QueryExtention {
public static IQueryOver<E, F> WhereStringIsNullOrEmpty<E, F>(this IQueryOver<E, F> query, Expression<Func<E, object>> expression) {
var property = Projections.Property(expression);
var criteria = Restrictions.Or(Restrictions.IsNull(property),
Restrictions.Eq(property, string.Empty));
return query.Where(criteria);
}
}
Then you should be able to create something like:
.QueryOver<ClassType>()
.WhereStringIsNullOrEmpty(obj => obj.Col1)
Related
I'm trying to create a complex Linq query that goes like this:
Get all organisations which have employees that match the given filter parameters.
Example filter:
Firstname: John
Name: Smith
My first attempt:
if (!filter.Name.IsNullOrWhiteSpace())
{
query = query.Where(o => o.Persons.Any(p => p.Name.ToLower().Contains(filter.Name.ToLower())));
}
if (!filter.Firstname.IsNullOrWhiteSpace())
{
query = query.Where(o => o.Persons.Any(p => p.Firstname.ToLower().Contains(filter.Firstname.ToLower())));
}
if (!filter.ContactNumber.IsNullOrWhiteSpace())
{
query = query.Where(o => o.Persons.Any(p => p.ContactNumber.contains(filter.ContactNumber)));
}
The problem with this approach is that when there is someone with the firstname John (ex. John Johnson) in organisation A, and someone with the last name Smith (Jenny Smith) in organisation A. The organisation (A) that contains those two persons gets returned. Which it shouldn't. I only want organisations that have people with the firstname "john" AND the lastname "Smith"
I found a working, but dirty and non-scalable approach:
if (!filter.ContactNumber.IsNullOrWhiteSpace())
{
if (!filter.Name.IsNullOrWhiteSpace() && !filter.Firstname.IsNullOrWhiteSpace())
{
query = query.Where(o => o.Persons.Any(p => p.ContactNumber.contains(filter.ContactNumber)
&& p.Name.ToLower().Contains(filter.Name.ToLower())
&& p.Firstname.ToLower().Contains(filter.Firstname.ToLower())));
}
else if (!filter.Name.IsNullOrWhiteSpace())
{
query = query.Where(o => o.Persons.Any(p => p.ContactNumber.contains(filter.ContactNumber)
&& p.Name.ToLower().Contains(filter.Name.ToLower())));
} else if (!filter.Firstname.IsNullOrWhiteSpace())
{
query = query.Where(o => o.Persons.Any(p => p.ContactNumber.contains(filter.ContactNumber)
&& p.Firstname.ToLower().Contains(filter.Firstname.ToLower())));
} else
{
query = query.Where(o => o.Persons.Any(p => p.ContactNumber.contains(filter.ContactNumber));
}
} else if(!filter.Name.IsNullOrWhiteSpace())
{
if (!filter.Firstname.IsNullOrWhiteSpace())
{
query = query.Where(o => o.Persons.Any(p => p.Firstname.ToLower().Contains(filter.Firstname.ToLower()) && p.Name.ToLower().Contains(filter.Name.ToLower())));
} else
{
query = query.Where(o => o.Persons.Any(p => p.Name.ToLower().Contains(filter.Name.ToLower())));
}
} else if (!filter.Firstname.IsNullOrWhiteSpace())
{
query = query.Where(o => o.Persons.Any(p => p.Firstname.ToLower().Contains(filter.Firstname.ToLower())));
}
As you can see this not a very clean solution.
I also tried using method calls inside the expression but Linq couldnt translate that. Is there any way I can can make a list of predicate expressions an merge them to one? Or is there a another, better solution?
By the way, since I need a paginated list, it all has to be in one query.
For your information, this is what my filter class looks like. It is just a class send from my front-end with all the fields that need to be filtered.
public class ContactFilter
{
public string Name{ get; set; }
public string Firstname{ get; set; }
public string ContactNummer { get; set; }
}
One of the easiest solution is using LINQKit library:
var predicate = PredicateBuilder.New<Person>();
if (!filter.Name.IsNullOrWhiteSpace())
{
predicate = predicate.And(p => p.Name.ToLower().Contains(filter.Name.ToLower()));
}
if (!filter.Firstname.IsNullOrWhiteSpace())
{
predicate = predicate.And(p => p.Firstname.ToLower().Contains(filter.Firstname.ToLower()));
}
if (!filter.ContactNumber.IsNullOrWhiteSpace())
{
predicate = predicate.And(p => p.ContactNumber.contains(filter.ContactNumber));
}
Expression<Func<Person, bool>> exp = predicate;
query = query
.AsExpandable()
.Where(o => o.Persons.Any(exp.Compile()));
Is there any way I can can make a list of predicate expressions an merge them to one?
Yes, and that's the approach I'd prefer in this situation.
First build the list:
var filterExpressions = new List<Expression<Func<Person, bool>>();
if (!filter.Name.IsNullOrWhiteSpace())
{
filterExpressions.Add(p => p.Name.ToLower().Contains(filter.Name.ToLower()));
}
if (!filter.Firstname.IsNullOrWhiteSpace())
{
filterExpressions.Add(p => p.Firstname.ToLower().Contains(filter.Firstname.ToLower()));
}
if (!filter.ContactNumber.IsNullOrWhiteSpace())
{
filterExpressions.Add(p => p.ContactNumber.contains(filter.ContactNumber));
}
From there, you can use this implementation to And arbitrary Expressions together. You'll also need to decide what to do if there are no filters to apply (I'll use a default of no filter, but you may want to do something else).
var predicate = filterExpressions.DefaultIfEmpty(p => true)
.Aggregate((a, b) => a.And(b));
Now we get to the hard part. We have an expression that represents the lambda you want to pass to a call to Any. It would be nice if we could just do:
query = query.Where(o => o.Persons.Any(predicate));
But sadly, this won't work because the type of o.Persons isn't an IQueryable. So now we have an expression that we want to embed in another expression in which the inner expression needs to be a lambda. Fortunately this isn't too complicated:
public static Expression<Func<TSource, TResult>> EmbedLambda
<TSource, TResult, TFunc1, TFunc2>(
this Expression<Func<TFunc1, TFunc2>> lambda,
Expression<Func<TSource, Func<TFunc1, TFunc2>, TResult>> expression)
{
var body = expression.Body.Replace(
expression.Parameters[1],
lambda);
return Expression.Lambda<Func<TSource, TResult>>(
body, expression.Parameters[0]);
}
(Using a helper class from the above link)
Now we just need to call the method. Note we won't be able to rely entirely on type inference due to the way this all works out, so some types need to be specified explicitly.
query = query.Where(predicate.EmbedLambda((UnknownType o, Func<Person, bool> p) => o.Persons.Any(p)));
I am trying to write a custom Linq extension, which can also be performed within the DB.
Basically, what I want to do is collectionOfStrings.Where(x => !x.IsNullOrWhiteSpace) Unfortunately it's not supported.
=============== What I've tried so far =============
This part is only interesting to those, who might come up with another idea apart from the one below.
There is a workaround by going this way collection.Where(x => x != null && x.Trim() != string.Empty), but since I use it frequently, it's not the best solution.
The prettiest solution would be, to find a way to write a string extension IsNullOrWhiteSpaceDB, which works or to kind of add the IsNullOrWhiteSpace method to the database programmatically to ensure support.
That's been my attempt to create a working IsNullOrWhiteSpace method, but it's not supported too:
public static bool IsNullOrWhiteSpaceDB(this string? str) =>
str == null || str.Trim() == String.Empty;
So I've started writing a predicate, which is working fine:
public IQueryable<string> GetAll() =>
GetAll().Select(x => x.property).Where(StringIsNotNullOrWhiteSpace).Distinct();
private static Expression<Func<string?, bool>> StringIsNotNullOrWhiteSpace =>
x => x != null && x.Trim() != string.Empty;
=============== The current problem =============
Neverthless I'd actually like to be able to run it on another collection than on a collection of strings. So I tried to build a custom linq extension (inspired by this solution (https://stackoverflow.com/a/40924558/9487478)):
public class QueryVisitor : ExpressionVisitor
{
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.IsStatic && node.Method.Name == "IsNullOrWhiteSpace")
{
//!(b.Diameter == null || b.Diameter.Trim() == string.Empty)
var arg = node.Arguments[0];
var argTrim = Expression.Call(arg, typeof(string).GetMethod("Trim", Type.EmptyTypes));
var exp = Expression.MakeBinary(ExpressionType.Or,
Expression.MakeBinary(ExpressionType.Equal, arg, Expression.Constant(null, arg.Type)),
Expression.MakeBinary(ExpressionType.Equal, argTrim, Expression.Constant(string.Empty, arg.Type))
);
return exp;
}
return base.VisitMethodCall(node);
}
}
public static class EfQueryableExtensions
{
public static IQueryable<T> WhereEx<T>(this IQueryable<T> queryable, Expression<Func<T, bool>> where)
{
var visitor = new QueryVisitor();
return queryable.Where(visitor.VisitAndConvert(where, "WhereEx"));
}
}
And that's what my custom extension actually looks like:
public static class QueryHelper
{
public static IQueryable<T> WhereIsNotNullOrWhiteSpace<T>(this IQueryable<T> query, Expression<Func<T, string?>> expression)
{
var arg = expression.Body;
var argTrim = Expression.Call(arg, typeof(string).GetMethod("Trim", Type.EmptyTypes));
var exp = Expression.MakeBinary(ExpressionType.And,
Expression.MakeBinary(ExpressionType.NotEqual, arg, Expression.Constant(null, arg.Type)),
Expression.MakeBinary(ExpressionType.NotEqual, argTrim, Expression.Constant(string.Empty, arg.Type))
);
var lambda = Expression.Lambda<Func<T, bool>>(exp, expression.Parameters);
var result = query.Where(lambda);
return result;
}
}
After the query.Where(lambda) is executed there is an inner exception within the result:
NHibernate.Hql.Ast.ANTLR.QuerySyntaxException: A recognition error occurred.
The "original version" throws the same error too, so I thought it might be the created expression ((x == null) Or (x.Trim() == "")) (copied from the debugger). For me it looks actually quite good and I don't understand the cause of the error.
Any ideas? I would be pleased!
You don't need to build an expression for this. You just need an extension method that takes and returns IQueryable<string>.
public static class Extensions
{
public static IQueryable<string> IsNullOrWhiteSpaceDB(this IQueryable<string> input)
{
return input.Where(x => x != null && x.Trim() != string.Empty);
}
}
I have created PR for LINQKit which should simplify your life https://github.com/scottksmith95/LINQKit/pull/127
Idea is to add ExpandableAttribute to such methods which points to static function with expression for substitution.
public static class Extensions
{
[Expandable(nameof(IsNotNullOrWhiteSpaceDBImpl))]
public static bool IsNotNullOrWhiteSpaceDB(string str)
=> throw new NotImplementedException();
public static Expression<Func<string, bool>> IsNotNullOrWhiteSpaceDBImpl()
=> x => x != null && x.Trim() != string.Empty;
}
So query should use AsExpandable() at least once. Put this call somewhere in repository.
db.Users.AsExpandable()
.Where(u => u.FirstName.IsNotNullOrWhiteSpaceDB() || u.MiddleName.IsNotNullOrWhiteSpaceDB())
I have a method on the back end, that gets values related to a foreign key of the table.
Those foreign keys can be nullable, but one of those keys always will have value.
Here is method
public async Task<ListResultDto<QuoteListDto>> GeQuotesTabData(int? landlordId, int? agentId,
int? propertyTenantId)
{
if (landlordId.HasValue)
{
var query = _quoteRepository.GetAll()
.Where(x => x.LandlordId == landlordId);
}
if (agentId.HasValue)
{
var query = _quoteRepository.GetAll()
.Where(x => x.AgentId == agentId);
}
if (propertyTenantId.HasValue)
{
var query = _quoteRepository.GetAll()
.Where(x => x.PropertyTenantId == propertyTenantId);
}
return new ListResultDto<QuoteListDto>(await query.ProjectTo<QuoteListDto>(ObjectMapper)
.OrderBy(x => x.Id).ToListAsync());
}
At this row, I get an error Cannot resolve symbol query
return new ListResultDto<QuoteListDto>(await query.ProjectTo<QuoteListDto>(ObjectMapper)
.OrderBy(x => x.Id).ToListAsync());
How do I need to rewrite my method?
Declare and initialise your variable. Additionally I would re-write you method like so:
public async Task<ListResultDto<QuoteListDto>> GeQuotesTabData(int? landlordId, int? agentId,
int? propertyTenantId)
{
var query = _quoteRepository.GetAll();
if (landlordId.HasValue)
{
query = query.Where(x => x.LandlordId == landlordId);
}
if (agentId.HasValue)
{
query = query.Where(x => x.AgentId == agentId);
}
if (propertyTenantId.HasValue)
{
query = query .Where(x => x.PropertyTenantId == propertyTenantId);
}
return new ListResultDto<QuoteListDto>(await query.ProjectTo<QuoteListDto>(ObjectMapper)
.OrderBy(x => x.Id).ToListAsync());
}
Also taken from this answer, you can create a WhereIf extension to clean up the if statements.
public static IQueryable<TSource> WhereIf<TSource>(
this IQueryable<TSource> source,
bool condition,
Expression<Func<TSource, bool>> predicate)
{
if (condition)
return source.Where(predicate);
else
return source;
}
Making your code look like this:
public async Task<ListResultDto<QuoteListDto>> GeQuotesTabData(int? landlordId, int? agentId,
int? propertyTenantId)
{
var list = await _quoteRepository.GetAll()
.WhereIf(landlordId.HasValue, x => x.LandlordId == landlordId)
.WhereIf(agentId.HasValue, x => x.AgentId == agentId)
.WhereIf(propertyTenantId.HasValue, x => x.PropertyTenantId == propertyTenantId)
.ProjectTo<QuoteListDto>(ObjectMapper)
.OrderBy(x => x.Id)
.ToListAsync();
return new ListResultDto<QuoteListDto>(list);
}
Your problem is variable scope. When you define a variable it is only visible in the scope you define it in.
You define three different query variables in a local scope. None of them are accessible where you try to use it.
You need to define it before using it, something like this:
public async Task<ListResultDto<QuoteListDto>> GeQuotesTabData(int? landlordId, int? agentId,
int? propertyTenantId)
{
IQueryable<Quote> query = null;
if (landlordId.HasValue)
{
query = _quoteRepository.GetAll().Where(x => x.LandlordId == landlordId);
}
if (agentId.HasValue)
{
query = _quoteRepository.GetAll().Where(x => x.AgentId == agentId);
}
if (propertyTenantId.HasValue)
{
query = _quoteRepository.GetAll().Where(x => x.PropertyTenantId == propertyTenantId);
}
return new ListResultDto<QuoteListDto>(await query.ProjectTo<QuoteListDto>(ObjectMapper)
.OrderBy(x => x.Id).ToListAsync());
}
Of course all of your queries should be of the same type. Otherwise you will have to define and execute them in the local scopes.
You should probably also add some error handling of the case where query is null, when you try to use it.
I have this code
// IQueryable<General> query
if (columnName == "Column1")
{
query = query.Where(x => x.Column1 == searchValue);
}
else if (columnName == "Column2")
{
query = query.Where(x => x.Column2 == searchValue);
}
else if (columnName == "Column3")
{
query = query.Where(x => x.Column3 == searchValue);
}
else if (columnName == "Column4")
{
query = query.Where(x => x.Column4 == searchValue);
}
// next zilions columns to come
// ...
and my question is. How can i past x.Column as a parameter inside ".Where" condition ?
You can create a predicate manually. Use this method:
public static Expression<Func<General, bool>> CreatePredicate(string columnName, object searchValue)
{
var xType = typeof(General);
var x = Expression.Parameter(xType, "x");
var column = xType.GetProperties().FirstOrDefault(p => p.Name == columnName);
var body = column == null
? (Expression) Expression.Constant(true)
: Expression.Equal(
Expression.PropertyOrField(x, columnName),
Expression.Constant(searchValue));
return Expression.Lambda<Func<General, bool>>(body, x);
}
Now you can apply your predicate:
IQueryable<General> query = //
var predicate = CreatePredicate(columnName , searchValue);
query = query.Where(predicate);
You could use reflection and extension methods. As a rough example:
public class Foo
{
public int Column1 { get; set; }
public int Column2 { get; set; }
...
}
public static class FooExtensions
{
// I would use the actual type here instead of object if you know the type.
public static object GetProperyValue(this Foo foo, string columnName)
{
var propertyInfo = foo.GetType().GetProperty(columnName);
var value = propertyInfo.GetValue(foo);
// as well as cast value to the type
return value;
}
}
...
query = query.Where(x => x.GetProperyValue(columnName) == searchValue);
...
As a side note, that is not a well-designed query because every time you add a column to your model, you'd need to update your if-else. It violates the O in SOLID.
You can either use Reflection to retrieve the property via name
x.GetType().GetProperty(propertyName,BindingFlags).SetValue(x,value)
// propertyName = "Column1" for example
// BindingFlags are most likely Instance, Public and Property (IIRC)
or pass in the PropertyInfo directly into the method as a parameter. Your choice depends on the abstraction level you want to expose to consumers of your method.
I'm trying to construct an expression that ultimately results in a query like
SELECT p.*
FROM MyEntity p
WHERE EXISTS(SELECT *
FROM filters
WHERE (filter.type = 1
AND filter.objectid = p.id
AND filter.value = 1
OR filter.type = 1
AND filter.objectid = p.id
AND filter.value = 2))
AND EXISTS(...)
Obviously it won't look exactly like that, but that's the general idea.
I'm using PredicateBuilder to build the query based on the filters passed in, so I have something like this:
var query = context.Set<MyEntity>().AsExpandable();
var predicate = PredicateBuilder.New<MyEntity>(true);
//loop through the group filters. The filters in a group have an or relationship
foreach (FilterGroup group in filters)
{
predicate = predicate.And(
p => context.Set<FilteringValue>().AsExpandable().Any(getFilteringPredicate(p,group ))
);
}
return query.Where(predicate);
And the getFilteringPredicateMethod:
Expression<Func<FilteringValue,bool>> getFilteringPredicate(MyEntity p, FilterGroup filters) {
var fPredicate = PredicateBuilder.New<FilteringValue>(true);
foreach(var filter in filters.FilterList)
{
fPredicate= fPredicate.Or(fv => fv.objectid == p.Id && fv.Type== 1 && fv.value == filter.Value);
}
return fPredicate
}
This seems relatively simple, however I'm getting the error
variable 'p' of type 'Models.MyEntity' referenced from scope '', but it is not defined.
Is there no way to pass the product object into the getFilteringPredicate() method? MyEntity and Filter are not related in Entity Framework.
So... I think I finally got it, you want to relate two expression parameters and build up a composite query (what I mean by the informal definition of "composite" is a subquery having a reference to the main query parameter(s)):
Unfortunately, LinqKit does not support multi-parameter expressions 'AFAIK', which is something that would be a perfect match for your case:
Well, anyway... here it goes. By the way FilteringValues and MyEntities are just two DbSets, I just happen to be using LinqPad to test this out ATM (Questions?):
void Main(string[] args)
{
var entityQuery = MyEntities.AsExpandable();
var filterGroups = GetFilterGroups();
// Initialize with TRUE since no group filter implies Everything matches
var predicate = PredicateBuilder.New<MyEntity>(true);
var filteringValueQuery = FilteringValues.AsExpandable();
foreach (var g in filterGroups)
{
if (!g.FilterList.Any())
{
// If we have no filters in the group, skip
continue;
}
var expressionForGroupFilters = BuildExpressionForGroupFilters(g.FilterList);
predicate = predicate.And(entity => filteringValueQuery.Any(filteringValue => expressionForGroupFilters.Invoke(entity, filteringValue)));
}
entityQuery = entityQuery.Where(predicate);
var data = entityQuery.ToList();
data.Dump();
}
public static Expression<Func<MyEntity, FilteringValue, bool>> BuildExpressionForSingleFilter(Filter groupFilter)
{
var value = groupFilter.Value;
return (entity, filteringValue) =>
filteringValue.Type == 1
&& filteringValue.ObjectId == entity.Id
&& filteringValue.Value == value;
}
public static Expression<Func<MyEntity, FilteringValue, bool>> BuildExpressionForGroupFilters(IReadOnlyCollection<Filter> groupFilters)
{
Expression<Func<MyEntity, FilteringValue, bool>> result = null;
foreach (var groupFilter in groupFilters)
{
var expression = BuildExpressionForSingleFilter(groupFilter);
if (result == null)
{
result = expression;
continue;
}
var tempResult = result.Expand();
result = (entity, filteringValue) => tempResult.Invoke(entity, filteringValue) || expression.Invoke(entity, filteringValue);
}
return result.Expand();
}
public static FilterGroup CreateFilterGroupWithValues(params int[] values)
{
var filterList = values
.Select(x => new Filter { Value = x })
.ToList();
return new FilterGroup { FilterList = filterList };
}
public static IEnumerable<FilterGroup> GetFilterGroups()
{
return new[] {CreateFilterGroupWithValues(0, 2, 4), CreateFilterGroupWithValues(1)};
}
public class Filter
{
public int Value { get; set; }
}
public class FilterGroup
{
public FilterGroup()
{
FilterList = new List<Filter>();
}
public List<Filter> FilterList { get; set; }
}