Generic Query With PredicateBuilder in Linqkit - c#

I've been using LinqKit to create generic queries for quite some time.
One thing that has always bothered me is the fact that you always have to test whether the value sent in the filter is valid.
For example: Suppose I have a string filter. Conditions can be Equal, StartsWith, EndsWith and Contains.
My method would look something like this:
public List<MyModel> Get(MyModelFilter filter)
{
if (string.IsNullOrEmpty(filter.prop))
{
predicate = predicate.And(_myModel => myModel.Prop.Contains(filter.prop));
}
// Plus a giant amount of if's with multiple filters
return DbSet.AsExpandable()
.Where(predicate)
.ToList();
}
To end this bunch of If's, I decided to create a generic method to apply the filter to the properties.
My idea is to pass the property where the filter will be applied, and the filter definition, and encapsulate the Expression creation logic
It would be something of the type:
public List<MyModel> Get(MyModelFilter filter)
{
predicate = predicate.And(_myModel => myModel.Prop, filter.PropFilterDefinition);
// Goodnye If's, Only others filter impl
return DbSet.AsExpandable()
.Where(predicate)
.ToList();
}
For this, I've created some extension methods to handle this
public static Expression<Func<TPredicate, bool>> And<TPredicate>(
this ExpressionStarter<TPredicate> predicate,
Func<TPredicate, string> property, StringFilterDefinition filter,
bool ignoreNull = true)
{
if (InvalidStringFilter(filter, ignoreNull))
{
return predicate;
}
// This is LinqKit's And Extension Method
return predicate.And(BuildPredicate(property, filter));
}
private static Expression<Func<TPredicate, bool>> BuildPredicate<TPredicate>(
Func<TPredicate, string> property,
StringFilterDefinition filter)
{
if (filter.Filter == StringFilterComparators.Equal)
{
return x => property.Invoke(x) == filter.Value;
}
if (filter.Filter == StringFilterComparators.BeginsWith)
{
return x => property.Invoke(x).StartsWith(filter.Value);
}
if (filter.Filter == StringFilterComparators.EndsWith)
{
return x => property.Invoke(x).EndsWith(filter.Value);
}
return x => property.Invoke(x).Contains(filter.Value);
}
private static bool InvalidStringFilter(
StringFilterDefinition filter,
bool ignoreNullValue = true)
{
if (filter?.Filter == null)
{
return true;
}
return ignoreNullValue && string.IsNullOrEmpty(filter.Value);
}
The problem is that the filter is not applied, and the answer is in Invoke right up there. EF can not translate the above expression to SQL.
The EF error is
Microsoft.EntityFrameworkCore.Query.Internal.SqlServerQueryCompilationContextFactory[8]
The LINQ expression '(__property_0.Invoke([x]) == __filter_Value_1)'
could not be translated and will be evaluated locally. To configure
this warning use the DbContextOptionsBuilder.ConfigureWarnings API
(event id 'RelationalEventId.QueryClientEvaluationWarning').
ConfigureWarnings can be used when overriding the
DbContext.OnConfiguring method or using AddDbContext on the
application service provider.
The question is:
How can I make this construction work?
Also, any suggestions on how best this?

You seem to forgot that besides the PredicateBuilder, the really useful feature provided by LINQKit AsExpandable, Expand and Invoke custom extension methods is to be able to correctly embed expressions inside the expression tree.
In order to utilize that feature, you should use Expression<Func<...>> instead of Func<...>. In the posted code, replace all occurrences of Func<TPredicate, string> with Expression<Func<TPredicate, string>> and the issue should be solved.

Related

Using IEnumerable FirstOrDefault within a List extension method?

Is it possible to use IEnumerable FirstOrDefault within a List extension method? I am getting the error 'List does not contain a definition for FirstOrDefault'?
using LanguageExt;
using System;
using System.Linq;
public static class Question
{
public static Option<T> Lookup<T>(this List<T> enumerable, Func<T, bool> predicate)
{
if (enumerable == null)
{
return Option<T>.None;
}
var val = enumerable.FirstOrDefault(predicate);
return val == null ? Option<T>.None : Option<T>.Some(val);
}
public static void Run()
{
bool isOdd(int i) => i % 2 == 1;
var none = new List<int>().Lookup(isOdd); // => None
var some = new List<int> { 1 }.Lookup(isOdd); // => Some(1)
}
}
Normally it is possible to use FirstOrDefault on any enumerable sequence, so also on Lists.
The following works:
Func<int, bool> predicate = isOdd;
List<int> integerSequence = new List<int>();
var a = integerSequence.FirstOrDefault();
var b = integerSequence.Where(i => predicate(i)).FirstOrDefault();
var c = integerSequence.FirstOrDefault(i => predicate(i));
So I can't reproduce your problem. I see other problems though.
If you don't understand the this keyword, read Extension Methods demystified. Whenever you think: damn, I wish they had thought of this LINQ method, consider creating an extension method.
So your Lookup method is an extension method that takes a list and a predicate as input.. Since it seems that it can work on any enumerable sequence, let's not limit ourselves to List<T>, let's accept any enumerable sequence, like Arrays, Dictionaries, Lookup-tables, etc.
Furthermore, LINQ methods are most reusable if you let them return a sequence or result items, and let your called decide whether he wants all items, or only the FirstOrDefault, or maybe the Last, or Average.
So if you decide to create an extension method that takes an enumerable sequence as input, let it return an Enumerable sequence whenever possible, even if the sequence is empty: avoid returning null, if you mean: there are no elements matching what you want. Because that would mean that callers should have to check the result before they can continue concatenating other LINQ methods.
Finally: LINQ methods usually don't except null sources as input, they throw exceptions. LINQ methods expect callers to make sure the input is not null. Of course you are free to deviate from this, but callers don't expect it when using LINQ like methods.
With these guidelines in mind, consider to change your extension method.
Apparently, if an element in your input sequence equals null, you want to return Option<T>.None, otherwise you want Option<T>.Some(val)
public static Option<T> ToOption<T>(this T sourceElement)
{
return sourceElement?.Option<T>.Some(sourceElement) ?? Option<T>.None;
}
In words: if sourceElement equals null, return Option<T>.Some(sourceElement), otherwise return Option<T>.None
In LINQ it is quite normal to create extension methods with equality comparers, this will make your methods even more reusable. Usually this will only need two or three lines of code:
public static Option<T> ToOption<T>(this T sourceElement)
{
return ToOption(sourceElement, null);
}
public static Option<T> ToOption<T>(
this T sourceElement,
IEqualityComparer<T> comparer)
{
if (comparer == null) comparer = EqualityComparer<T>.Default;
if (comparer.Equals(sourceElement, null)
return Option<T>.None;
else
return Option<T>.Some(sourceElement);
}
In the methods below I won't mention this possibility over and over again.
public static IEnumerable<Option<T>> ToOptions<T>(this IEnumerable<T> source)
{
// TODO: throw exception if input null
return source.Select(sourceElement => sourceElement.ToOption());
}
public static IEnumerable<Option<T>> ToOptions<T>(
this IEnumerable<T> source,
Func<T, bool> predicate)
{
// No need: check if source == null, this is done in the other ToOptions
// TODO: exception if predicate equals null
return source.Where(sourceElement => predicate(sourceElement))
.ToOptions();
}
Simple usage:
IEnumerable<Customer> customers = ...
MyClass myFirstEligableCustomer= customers
.ToOptions(customer => customer.Address.City == "New York")
.FirstOrDefault();
Or intertwined with other LINQ methods:
var result = customers.
.GroupBy(customer => customer.Birthday.Year)
.OrderBy(customerGroup => customerGroup.Key)
.ToOptions(customerGroup => customerGroup.Key)
.ToDictionary(customerGroup => customerGroup.Key, // Key
customerGroup => customerGroup.ToList); // Value
The result is a Dictionary, where the key is the Year of a birthday, and the value is the list of all Option<Customer> that are born in this year.
You see: most methods are barely more than one-liners. They are easy to reuse, easy to change, easy to test.

Expression for OrderBy linq with inner expression

I have the following expresssion for sorting:
this.Students = this.Students.OrderBy(x => x.ExamData.OrderByDescending(p => p.ExamDate).ThenByDescending(p => p.ExamId).FirstOrDefault().TotalMarks);
While my idea is to abstract the Expression for
x => x.ExamData.OrderByDescending(p => p.ExamDate).ThenByDescending(
p => p.ExamId).FirstOrDefault().TotalMarks
to be made as an lambda Expression so that I can use like
this.Students = this.Students.OrderBy(sortExpression);
It's because I have many sort fields like the TotalMarks defined above, and I want to just create the Expression from the sort field and then call the OrderBy.
I know from this link, we can create an expression where child property is used, but not getting with the inner expressions.
Currently I have given a switch case and written the same stuff in each case like
this.Students = this.Students.OrderBy(x => x.ExamData.OrderByDescending(p => p.ExamDate).ThenByDescending(p => p.ExamId).FirstOrDefault().SubjectName);
So my idea is to create kindof ExpressionBuilder with a static method which builds the expression passing on the fieldName, like
public static Expression BuildSortExpression(string fieldName) {}
You can easily pull most of the logic into a method:
private int sortExpression(Student x) {
return x.ExamData.OrderByDescending(p => p.ExamDate).ThenByDescending(p => p.ExamId).FirstOrDefault().TotalMarks;
}
Assuming TotalMarks is an int.
Then you would just need to use:
this.Students.OrderBy(x => sortExpression(x));
or add it as a property of Student.
Warning:
If you are using this with an ORM (linq to SQL, Entity framework, etc), this will not execute as efficiently as the previous code!
Trying to create a reusable expression variable will end up being a lot more work than just creating your own extension method to do the whole ordering:
public static IQueryable<Student> OrderByMarks(this IQueryable<Student> students)
{
return students.OrderBy(student => student.ExamData
.OrderByDescending(exam => exam.ExamDate)
.ThenBy(exam => exam.ExamId)
.FirstOrDefault().TotalMarks);
}
Then you can use it like so:
this.Students = this.Students.OrderByMarks();
Got a solution for this using the idea by Ben.
Created a
Dictionary<string, Func<Student, Object>>
with the sort fields as key and the func as
new Func<Student, object>((Student student) => { return GetLatestExam(student).TotalMarks; })
And the GetLatestExam static method as
private static Study GetLatestExam(Student student)
{
return student.ExamData.OrderByDescending(p => p.ExamDate).ThenByDescending(p => p.ExamId).FirstOrDefault();
}
Then in the actual sort, I just have to call like this:
public void Sort(string sortField, bool sortAscending)
{
// based on the sort field, get the sorting expression and execute.
if(sortAscending)
{
this.Students= this.Students.OrderBy(student=>this._customSortColumns[sortField](student));
}
else
{
this.Patients = this.Patients.OrderByDescending(student=>this._customSortColumns[sortField](student));
}
}

Passing a where clause in a predicate expression

I have the following predicate expression.
public IQueryable<T> Find(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
{
return //boolean
}
Here is where I am calling the method:
public void TestMethod1()
{
author.AuthorName = authorName;
using (var context = new AspBlogRepository<Author>())
{
if (context.Find(e => e.AuthorName == authorName))
{
//do nothing
}
context.Add(author);
}
}
I get an error saying that I you cannot convert an IQueryable to bool. I just want to be able to use my predicate expression to see if the author is already in the database.
Any help would be much appreciated.
Thanks!
public IQueryable<T> Find(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
{
return //boolean
}
The problem is here, if you indeed return boolean. If this method is inside your context, you would first need to resolve Author and then filter the Authors in your repository.
If this is an EF context, and you have access to the EF context in your repository, you should be able to get the db set like this:
var entitySet = context.Set<T>();
Then, you can run your predicate against the entitySet returning the filtered results.
Error says it all. context.Find(e => e.AuthorName == authorName) is returning IQueryable<T>. If is expecting a bool
So your usage of if (context.Find(e => e.AuthorName == authorName)) is wrong. Change it to
if (context.Find(e => e.AuthorName == authorName).Any())
{
}
context.Find returns an IQueryable<T>. Your code could instead look like:
if (context.Find(e => e.AuthorName == authorName).Count() == 1)
{
//do nothing
}

Reusable LINQ query except for where clause

I've got a collection of movies which have various properties (title, release year, rating, etc) that I need to search for using a LINQ query as follows:
public BindingList<Movie> SearchByTitle(string title)
{
var matches = from movies in movieCollection
where movies.Title == title
select movies;
// do some other stuff with the matches
}
But I don't want a separate method to search for each property since the only thing that changes between searches is the where section. For example where movies.Rating == rating or where movies.ReleaseYear == releaseYear. How do I make the search method reusable for all different kinds of searches by passing in some sort of Expression or Func as the where section?
How do I make the search method reusable for all different kinds of searches by passing in some sort of Expression or Func as the where section?
Your query really isn't anything other than the where clause. But you can easily make the where part configurable... just not using query expressions.
public BindingList<Movie> SearchByTitle(Expression<Func<Movie, bool>> predicate)
{
var matches = movies.Where(predicate);
// Do common stuff with the matches.
}
EDIT: I was assuming that movies was an IQueryable<T>, given that you were talking about Expression. If it's just an IEnumerable<T>, you want:
public BindingList<Movie> SearchByTitle(Func<Movie, bool> predicate)
{
var matches = movies.Where(predicate);
// Do common stuff with the matches.
}
You can use an extension method (define this in a static class)
public static IQueryable<T> AddSearchParameter<T>(this IQueryable<T> query, bool condition, System.Linq.Expressions.Expression<Func<T, bool>> predicate)
{
if (condition)
{
query = query.Where(predicate);
}
return query;
}
So for example:
public BindingList<Movie> Search(string title, int? year, int? rating)
{
var matches = movieCollection.AddSearchParameter(!string.IsNullorEmpty(title), m=>m.Title == title);
matches = matches.AddSearchParameter(year.HasValue, m=>m.Year == year.Value);
matches = matches.AddSearchParameter(rating.HasValue, m=>m.rating >= rating.Value);
// do some other stuff with the matches
}
If you're using this against a database it will not actually execute the query until you enumerate so this will not make multiple calls to your database.
You could use a CompiledQuery.
Check this very interesting answer on SO.
Hope it helps.

Why IQueryable executes in Where clause?

I have
public IQueryable<Guid> AccessibleCities
{
get
{
return CityRepository
.FindAll(a => <CONDITIONS>);
}
}
CityRepository.FindAll is implemented as:
public virtual IQueryable<TLookup> FindAll(Expression<Func<TLookup, bool>> predicate)
{
return DataContext.GetSet<TLookup>().Where(predicate);
}
And I call this
anotherRepository
.FindAll(a => AccessibleCities.Any(b => ANOTHER CONDITION));
When I call the last one, it generates two queries instead of adding AccessibleCities as query.
Please help me :)
Your final query just doesn't work that way; it doesn't concatenate by default.
Try the PredicateBuilder class. That looks like it should work for what you're trying to achieve.

Categories

Resources