Passing a where clause in a predicate expression - c#

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
}

Related

Entity Framework Search Like or Equal

I'm using EntityFrameworkCore and am trying to create a simplified instance of searching for either 'equal to' or 'like' based whether the search object contians the wildcard character.
Here's the base of what I'm working with
public class Person
{
public string Name;
public string MothersName;
public string FathersName;
}
public class SearchPerson
{
public string Name;
}
public class Program
{
public void FindPerson(SearchPerson searchPerson)
{
if (!string.IsNullOrEmpty(searchPerson.Name))
{
if (searchPerson.Name.Contains("%"))
{
EFPersonObject.Where(m => EF.Functions.Like(m.Name, searchPerson.Name));
}
else
{
EFPersonObject.Where(m => m.Name == searchPerson.Name);
}
}
}
}
If my SearchPerson class extends to 5 or 10 or 15 possible search params, there is a lot of repeated code. I should be able to implement some reflection in an extension and using Jim C's response here, get and pass the name of the property and simplify a lot of it down to one line
public static class SearchExtension
{
public static void FindLike<T>(this DbSet<T> model, PropertyInfo info, string searchValue) where T : class
{
if (!string.IsNullOrEmpty(searchValue))
{
if (searchValue.Contains("%"))
{
model.Where(m => EF.Functions.Like(typeof(T).GetProperty(info.Name).GetValue(model, null).ToString(), searchValue));
}
else
{
model.Where(m => typeof(T).GetProperty(info.Name).GetValue(model, null).ToString() == searchValue);
}
}
}
}
Usage:
EFPersonObject.FindLike(typeof(Person).GetProperty(RemoteMgr.GetPropertyName(()=>typeof(Person).Name)), searchPerson.Name);
(I haven't tested it yet, but if it isn't right, it should be close), but I'm going to assume I'm going to take a performance hit. Is there another way to implement this where reflection isn't needed to avoid the performance hit?
Using reflection (and other non SQL translatable) calls inside the query expression tree is not a good idea. In EF Core 1x and 2.x it will cause client evaluation, and EF Core v3+ will throw exception similar to EF 6.
LINQ to Entities best work with expressions. And once you need expression, you'd better make your custom extension method receive lambda expression directly rather than PropertyInfo obtained via lambda expression as in the linked topic.
Here is a sample implementation of the above:
public static partial class QueryableExtensions
{
public static IQueryable<T> WhereMatch<T>(this IQueryable<T> source, Expression<Func<T, string>> expr, string searchValue)
{
if (string.IsNullOrEmpty(searchValue))
return source;
else if (searchValue.Contains("%"))
return source.Where(expr.Map(value => EF.Functions.Like(value, searchValue)));
else
return source.Where(expr.Map(value => value == searchValue));
}
static Expression<Func<TSource, TTarget>> Map<TSource, TIntermediate, TTarget>(this Expression<Func<TSource, TIntermediate>> source, Expression<Func<TIntermediate, TTarget>> target)
=> Expression.Lambda<Func<TSource, TTarget>>(Expression.Invoke(target, source.Body), source.Parameters);
}
The main method is WhereMatch. It uses a small Expression helper method called Map for composing lambda expressions from other lambda expressions.
Sample usage would be:
// SearchPerson searchPerson
// DbContext db
var query = db.Set<Person>()
.WhereMatch(p => p.Name, searchPerson.Name)
.WhereMatch(p => p.MothersName, searchPerson.MothersName)
.WhereMatch(p => p.FathersName, searchPerson.FathersName);
For Equality comparison you should use ==:
EFPersonObject.Where(m => m.Name == searchPerson.Name);
For LIKE :
like 'something%': (StartsWith Method)
EFPersonObject.Where(m => m.Name.StartsWith(searchPerson.Name));
like '%something': (EndsWith Method)
EFPersonObject.Where(m => m.Name.EndsWith(searchPerson.Name));
like '%something%': (Contains Method)
EFPersonObject.Where(m => m.Name.Contains(searchPerson.Name));

Generic Query With PredicateBuilder in Linqkit

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.

Query DocumentDB based on lambda expression func<TEntity, dynamic>

The requirement is to pass any custom expression query to the documentDB and fetch records based on it.
public Task<dynamic> ExecuteQuery(Func<TEntity, dynamic> lambda)
{
dynamic result = client.CreateDocumentQuery<dynamic>(documentCollectionUri).Where(lambda); //compile error
return result;
}
above function can be called like this:
var res = await _locationDbRepository.ExecuteQuery(x => x.Name == "raja" && x.Address == "abc");
Clearly, it will give compile time error because Where() is expecting a predicate but, I need to pass a func. I don't want to pass predicate because it always return a boolean but func can return anything.
A SQL query can be passed to documentDB but then I have to convert lambda expression to SQL query using a 3rd party library which I don't want to use.
The above approach to query documentdb is not working so Any other way you can suggest based on lambda expression?
So, I figured out the solution by passing Expression<Func<TEntity, bool>> as parameter and it's returning expected results based on the lambda expression.
public async Task<IEnumerable<TEntity>> GetByExpression(Expression<Func<TEntity, bool>> expression)
{
IEnumerable<TEntity> IEnumerable;
List<TEntity> List = new List<TEntity>();
try
{
IDocumentQuery<TEntity> Queryable = client.CreateDocumentQuery<TEntity>(documentCollectionUri)
.Where(expression)
.AsDocumentQuery();
while (Queryable.HasMoreResults)
{
foreach (TEntity t in await Queryable.ExecuteNextAsync<TEntity>())
{
List.Add(t);
}
}
}
catch (DocumentClientException ex)
{
throw ex;
}
IEnumerable = List;
return IEnumerable;
}
It can be called like this:
dynamic results = SomeObj.GetByExpression(c=>c.Name== "raja" || c.Rank==12);

How do I make an anonymous method run in LINQ to Entities?

I'm trying to build a generic method that EF4.1 to look in both the Database and the Local memory for a particular row in a table that matches a particular criteria.
So far, this is what I have this.
This is the caller.
dbEntities.MyTables.LocalAndDb(delegate(MyTable s)
{ return s.Description.Contains("test"); });
This is LocalAndDb
public static object LocalAndDb<T>(this DbSet<T> myTable, Func<T, bool> function) where T : class
{
// look in local
var item = myTable.Local.Where(o => function((T)o)).FirstOrDefault()
// if not exist, look in the database
if (item == null)
{
Expression<Func<T, bool>> predicate = (u) => function(u);
item = myTable.Where(predicate).FirstOrDefault();
}
return item;
}
The problem is with this line.
item = myTable.Where(predicate).FirstOrDefault();
When it calls the database, it throws this error.
"The LINQ expression node type 'Invoke' is not supported in LINQ to Entities."
I imagine it's because I'm passing in an anonymous method and it doesn't know how to turn this into SQL. I thought converting it to an Expression object would do the trick but it's still not working for me.
What do I need to do to make a anonymous method become something that LINQ can turn into SQL?
To make this work, you need to pass the lambda expression to LocalAndDb as an expression tree (so that LINQ to Entities can analyze the code and translate it to SQL):
public static object LocalAndDb<T>(this DbSet<T> myTable,
Expression<Func<T, bool>> expr) where T : class {
// ...
if (item == null) {
item = myTable.Where(expr).FirstOrDefault();
}
return item;
}
Then, of course, the problem is that you cannot execute the expression tree when checking the in-memory data. One way to solve this is to use the Compile method of Expression<T>, but that will be a bit inefficient (depending on your scenario).
Another option is to just pass the condition as both function and expression tree:
public static object LocalAndDb<T>(this DbSet<T> myTable,
Func<T, boo> function, Expression<Func<T, bool>> expr) where T : class {
var item = myTable.Local.Where(o => function((T)o)).FirstOrDefault();
if (item == null) {
item = myTable.Where(expr).FirstOrDefault();
}
return item;
}
table.LocalAndDb(t => t.Foo > 10, t => t.Foo > 10);
This is a bit ugly, but it doesn't require inefficient compilation at runtime. If you want a slightly more sophisticated solution, then you can define your own type to keep pre-compiled functions:
class Precompiled<T1, T2> {
public Precompiled(Expression<Func<T1, T2>> expr) {
this.Expression = expr;
this.Function = expr.Compile();
}
public Expression<Func<T1,T2>> Expression { get; private set; }
public Func<T1,T2> Function { get; private set; }
}

Linq IQueryable short circuiting an empty search parameter

I have a generic repository with the following method
IQueryable<T> GetAllByFilter(Expression<Func<T, bool>> expression);
I'm now trying to provide a search feature through the front end, where one or more parameters might have been entered or left blank. I'm having problems short-circuiting the expression for empty parameters.
The problem can be demonstrated by calling the following example on the repository:
public IEnumerable<Foo> Search(string param)
{
var filteredFoos = _fooRepository.GetAllByFilter(
f => string.IsNullOrEmpty(param) || f.Something == param );
return filteredFoos.ToList(); // throws exception
}
Enumerating the query with ToList() throws a System.NullReferenceException if param is null.
I neither understand this nor know how to fix it, so any pointers appreciated. Thanks.
UPDATE: in response to the comments below, I added a null check. My actual code looks like this now
var test1 = _repository.GetAllByFilter(
r => r != null &&
(string.IsNullOrEmpty(param)
|| (r.Field != null && r.Field.IndexOf(param.Trim()) != -1)));
var test2 = test1.ToList(); // exception here
I'm still not seeing where the problem could be.
EDIT: in response to comment, the generic repository GetAllByFilter code:
public IQueryable<T> GetAllByFilter(Expression<Func<T, bool>> expression)
{
return _dataContext.GetTable<T>().Where(expression);
}
note that if I run a simple GetAll query
public IQueryable<T> GetAll()
{
return _dataContext.GetTable<T>();
}
on the same table, no null records are returned (as expected).
keep it simple:
public IEnumerable<Foo> Search(string param)
{
if (string.IsNullOrEmpty(param))
{
return this.fooRepository.GetAll().ToArray();
}
return this.fooRepository.GetAllByFilter(o => o.Field.Contains(param.Trim())).ToArray();
}
cake.
public IEnumerable<Foo> Search(string param)
{
Expression<Func<Foo, bool>> shortCircuit = a => true;
Expression<Func<Foo, bool>> normal = a => a.Something == param;
var filteredFoos = _fooRepository.GetAllByFilter(
string.IsNullOrEmpty(param) ? shortCircuit : normal);
return filteredFoos.ToList(); // no more exception.
}
You just gotta remember, you can't throw anything into those IQueryable methods and expect them to understand. You can probably make shortCircuit expression to static.

Categories

Resources