I have a generic repository in which I'm trying to include a function that accepts a variable list of child tables to eagerly load. The function looks thus:
public IQueryable<T> FindBy(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includeEntities)
{
IQueryable<T> query = this._dbSet.Where(e => !e.Deleted).Where(predicate);
foreach (var entity in includeEntities)
{
query.Include(entity);
}
return query;
}
It works but I'm concerned about the object reference.
Using the function thus:
var foundEntities = Repository.Entities.FindBy(i => i.Id == targetId, i => i.Orders, i => i.Invoices);
The params passed in the includeEntites array are of type System.Linq.Expressions.PropertyExpression which is unfortunately an internal class so I can't make the function signature:
public IQueryable<T> FindBy(Expression<Func<T, bool>> predicate, params Expression<Func<T, System.Linq.Expressions.PropertyExpression>>[] includeEntities)
as I'd like. Any thoughts?
Related
I was using EF.Functions.Like method for my query on my ASP.NET Core 3.1 SPI but as soon as I upgraded it to .NET 6 along with with all nugets, this particular query started to throw following error
InvalidOperationException : The Like method is not supported because the query has switched to client-evaluation
I have done some research and I know InMemory databases can cause this issue, but thats not the case I've tried localhosting the API and also published SPI and in both cases the database is always on SQL Server (not local on my machine) so its not an inmemory database, also the fact it worked fine on asp.net core 3.1
Example query
Query Method
public static async Task<IQueryable<Evidence>> QueryEvidencesAsync(this EvidenceContext Context, EvidenceQuery query)
{
var predicate = PredicateBuilder.True<Evidence>();
predicate = predicate.And(x => x.IsActive && x.EvidenceStatus != EvidenceStatus.ReleasedToOwner && x.EvidenceStatus != EvidenceStatus.Transfer);
if (query.EvidenceStatus is not EvidenceStatus.Disposed)
{
predicate = predicate.And(x => x.IsActive && x.EvidenceStatus != EvidenceStatus.Disposed);
}
if (query.EvidenceStatus is not EvidenceStatus.Incoming)
{
predicate = predicate.And(x => x.IsActive && x.EvidenceStatus != EvidenceStatus.Incoming);
}
var predicateAndOr = query.IsOr ? PredicateBuilder.False<Evidence>() : PredicateBuilder.True<Evidence>();
if (!string.IsNullOrWhiteSpace(query.CaseNumber))
{
predicateAndOr = predicateAndOr.AndOr(x => EF.Functions.Like(x.CaseFileNumber, $"%{query.CaseNumber}%"), query.IsOr);
}
predicate = predicate.And(predicateAndOr);
var predicateCompiled = predicate.Compile();
return query.IncludeRequests
? Context.Evidences.Include(x => x.Requests).AsNoTracking().Where(predicateCompiled).AsQueryable().OrderByDescending(x => x.Modified)
: Context.Evidences.Where(predicateCompiled).AsQueryable().OrderByDescending(x => x.Modified);
}
PredicateBuilder
public static class PredicateBuilder
{
public static Expression<Func<T, bool>> True<T>()
{
return (T _) => true;
}
public static Expression<Func<T, bool>> False<T>()
{
return (T _) => false;
}
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
{
InvocationExpression right = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
return Expression.Lambda<Func<T, bool>>(Expression.OrElse(expr1.Body, right), expr1.Parameters);
}
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
{
InvocationExpression right = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, right), expr1.Parameters);
}
public static Expression<Func<T, bool>> AndOr<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2, bool or = false)
{
if (!or)
{
return expr1.And(expr2);
}
return expr1.Or(expr2);
}
}
in this particular example, query.IsOr will always be false so predicate will always build with And predicate. Also query.CaseNumber will have the string value for caseNumber
I have removed many more conditions and if statement all of them just checked for one property and add that check to predicate, but as I said they are not relevant here as in this scenario only the if statements I've shown here are true and hence validated.
You're building a predicate as an Expression<Func<Evidence, bool>>, but then you're .Compile()ing it into a Func<Evidence, bool>. That means when you call .Where() you're no longer calling Queryable.Where<T>(this IQueryable<T> source, Expression<Func<Evidence, bool>> criteria), but rather Enumerable.Where(this IEnumerable<T> source, Func<Evidence, bool> criteria).
The difference there is that the method you're calling will treat the query as an IEnumerable<T>, and rather than allowing Entity Framework to issue a query against the database with your criteria as a WHERE clause, it will bring all of the objects back from the database, and then pass them into the criteria function (which has been compiled into memory) to determine which ones to keep.
In other words, your code in the past was terribly inefficient, and you didn't realize it. EF.Functions.Like was never meant to be invoked: it was only supposed to be evaluated as part of a query. Earlier versions of EF.Functions.Like did their best to mimic the behavior of SQL's LIKE when they were called, whereas newer versions will just throw an exception to warn you that you're doing it wrong.
You need to avoid .Compile(), and pass predicate directly into your .Where() call.
predicate = predicate.And(predicateAndOr);
return query.IncludeRequests
? Context.Evidences.Include(x => x.Requests).AsNoTracking().Where(predicate).OrderByDescending(x => x.Modified)
: Context.Evidences.Where(predicate).OrderByDescending(x => x.Modified);
That may lead to other issues, but it'll get you one step closer to doing it right.
I have an interface that defines a repository from the Repository pattern:
interface IRepository
{
List<Customer> GetAllCustomers(Expression<Func<Customer, bool>> expression);
}
I've implemented it against Entity Framework:
class EntityFrameworkRepository
{
public List<Customer> GetAllCustomers(Expression<Func<Customer, bool>> expression)
{
return DBContext.Customers.Where(expression).ToList();
}
}
That seems to work well, it allows me to do something like:
var customers = entityFrameworkRepository.Where(
customer => String.IsNullOrEmpty(customer.PhoneNumber)
);
Now I'd like to have an InMemoryRepository for testing and demo purposes. I attempted to create one:
class InMemoryRepository
{
Dictionary<int, Customer> Customers {get; set;} = new Dictionary<int, Customer>();
public List<Customer> GetAllCustomers(Expression<Func<Customer, bool>> expression)
{
//what do I put here?
}
}
As you can see in the above code, I'm stumped on what to do for InMemoryRepository.GetAllCustomers implementation. What should I do there to filter the Customers by the provided expression and return the results?
I tried:
return Customers.Where(expression));
But obviously it's expecting a Func<KeyValuePair<int, Customer>, bool> so I get a compilation error:
Error CS1929 'Dictionary' does not contain a definition for 'Where' and the best extension method overload 'Queryable.Where(IQueryable, Expression>)' requires a receiver of type 'IQueryable' DataAccess.InMemory
Try .AsQueryable() method:
return Customers.Values.AsQueryable().Where(expression);
Example
Expression<Func<Products, bool>> expresionFinal = p => p.Active;
if (mydate.HasValue)
{
Expression<Func<Products, bool>> expresionDate = p => (EntityFunctions.TruncateTime(c.CreatedDate) <= mydate);
expresionFinal = PredicateBuilder.And(expresionFinal, expresionDate );
}
IQueryable<T> query = dbSet;
query = query.Where(expresionFinal);
I have this function:
public List<T> Find(Expression<Func<T, bool>> query)
{
}
Find(x => x.Id == 4);
Inside the method Find I want to chain And Condition.
something like:
query.And(x => x.Secured == false);//Secured is a memeber inside T like Id.
Your problem is that you want to access a member of T within the generic method. T could be anything at this point so the compiler will not let you access Secured since T may not have a Secured member.
You could cast T to dynamic, but this just changes a compile time error to a runtime error (plus it's horrible).
The best way would be to ensure T implements some known interface that has a Secured member.
public List<T> Find(Expression<Func<T, bool>> query) where T : ISecured
The expression must be "opened" and rebuilt, like this:
public List<T> Find<T>(Expression<Func<T, bool>> query)
{
ParameterExpression parameter = query.Parameters[0];
Expression body = query.Body;
MemberExpression property = Expression.Property(parameter, "Secured");
body = Expression.AndAlso(body, Expression.Not(property));
Expression<Func<T, bool>> query2 = Expression.Lambda<Func<T, bool>>(body, parameter);
// Now you can use query2
return null;
}
Note that I'm considering this x.Secured == false to be equivalent to !x.Secured. Clearly Secured could be a strange class that overloads the == operator, but I'll ignore that case.
As suggested by #Ralf, you could even simply do two .Where. like:
public List<T> Find<T>(Expression<Func<T, bool>> query)
{
ParameterExpression parameter = query.Parameters[0];
MemberExpression property = Expression.Property(parameter, "Secured");
Expression<Func<T, bool>> query2 = Expression.Lambda<Func<T, bool>>(Expression.Not(property), parameter);
return context.Set<T>
.Where(query)
.Where(query2)
.ToList();
}
(I'm using as an example context.Set<T>, that is very similar to what you would do if you are using Entity Framework, but in general nearly all the IQuerable<>/IEnumerable<> treat two .Where() like a single .Where() with an && condition)
Something like
Find(x => x.Id == 4 && x.Secured == false);
I have tried searching but I don't seem to find any relevant answers. Perhaps because I'm not really sure how to formulate my question.
I'm writing a class library to aid working with SharePoint's Client Side Object Model. When executing a query, one can specify which properties of returned objects should be loaded, in order to avoid unnecessary network traffic. This is done by the means of Lambda Expression.
Here is an example that works:
public ListItemCollection GetItems(
params Expression<Func<ListItemCollection, object>>[] retrievals)
{
var query = new CamlQuery {...};
ListItemCollection queryResults = _list.GetItems(query);
ReloadClientObject(queryResults, retrievals)
return queryResults;
}
public void ReloadClientObject<T>(T clientObject,
params Expression<Func<T, object>>[] retrievals)
where T : ClientObject
{
_context.Load(clientObject, retrievals);
_context.ExecuteQuery();
}
Example call:
var items = GetItems(items => items.Include(
item => item.Id,
item => item.DisplayName));
This would all be fine. But I'd rather return IEnumerable<ListItem> instead of ListItemCollection and I would like to pass parameters of type Expression<Func<ListItem, object>> instead of Expression<Func<ListItemCollection, object>>... not to introduce the user to the ListItemCollection at all. So I'd like to move the Include() call to the body of my method... and that's where I got stuck.
Here's what I've got so far:
public IEnumerable<ListItem> GetItems(
params Expression<Func<ListItem, object>>[] retrievals)
{
var query = new CamlQuery {...};
ListItemCollection queryResults = _list.GetItems(query);
ReloadClientObject(queryResults, items => items.Include(retrievals))
_context.ExecuteQuery();
return queryResults.AsEnumerable();
}
Example call (much cleaner and nicer):
var items = GetItems(item => item.Id, item => item.DisplayName));
However, this throws OperationNotSupportedException when calling the Load() method.
I would be grateful for any guidance. Thank you!
Call Include directly on the query itself, and then just use LoadQuery instead of Load, to load the query:
public IEnumerable<ListItem> GetItems(this ClientContext context,
string listName,
params Expression<Func<ListItem, object>>[] retrievals)
{
var query = new CamlQuery();
var queryResults = context.Web.Lists.GetByTitle(listName)
.GetItems(query)
.Include(retrievals);
context.LoadQuery(queryResults);
context.ExecuteQuery();
return queryResults;
}
Since that doesn't work for you (according to your comment stating that you need to leverage the paging functionality) we'll need to do a bit more work.
So what we'll do here is create a Expression<Func<ListItemCollection, ItemSelector, object>> that will take a collection, a selector, and map that to an object. Here ItemSelector is defined through using ItemSelector = Expression<Func<ListItem, object>>; (Because trying to use a Expression<Func<ListItemCollection, Expression<Func<ListItem, object>>, object>> is just cruel and unusual punishment). We can define it like so:
Expression<Func<ListItemCollection, ItemSelector, object>> includeSelector =
(items, selector) => items.Include(selector);
Now what we can do is write an Apply method that can take an expression of a function taking two parameters, replace all instances of the second parameter with the constant, and thus create a method with one less parameter. Here is the definition of that Apply method:
public static Expression<Func<T1, TResult>> Apply<T1, T2, TResult>(
this Expression<Func<T1, T2, TResult>> expression,
T2 value)
{
return Expression.Lambda<Func<T1, TResult>>(
expression.Body.Replace(expression.Parameters[1],
Expression.Constant(value))
, expression.Parameters[0]);
}
This uses this helper method to replace all instances of one expression with another:
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
So now we can take this includeSelector expression and, for each item selector in our array, apply that selector to this function. Taking those results and putting them into an array gives us an Expression<Func<ListItemCollection, object>>[], which is exactly what we need to pass to Load.
Whew. Here is the final code to actually do that:
public static IEnumerable<ListItem> GetItems(this ClientContext context,
string listName,
params Expression<Func<ListItem, object>>[] retrievals)
{
var query = new CamlQuery();
var queryResults = context.Web.Lists.GetByTitle(listName)
.GetItems(query);
Expression<Func<ListItemCollection, ItemSelector, object>> includeSelector =
(items, selector) => items.Include(selector);
context.Load(queryResults, retrievals
.Select(selector => includeSelector.Apply(selector))
.ToArray());
context.ExecuteQuery();
return queryResults;
}
I have a method that accepts an Expression<Func<T, bool>> as a parameter. I would like to use it as a predicate in the List.Find() method, but I can't seem to convert it to a Predicate which List takes. Do you know a simple way to do this?
public IList<T> Find<T>(Expression<Func<T, bool>> expression) where T : class, new()
{
var list = GetList<T>();
var predicate = [what goes here to convert expression?];
return list.Find(predicate);
}
Update
Combining answers from tvanfosson and 280Z28, I am now using this:
public IList<T> Find<T>(Expression<Func<T, bool>> expression) where T : class, new()
{
var list = GetList<T>();
return list.Where(expression.Compile()).ToList();
}
Func<T, bool> func = expression.Compile();
Predicate<T> pred = t => func(t);
Edit: per the comments we have a better answer for the second line:
Predicate<T> pred = func.Invoke;
Another options which hasn't been mentioned:
Func<T, bool> func = expression.Compile();
Predicate<T> predicate = new Predicate<T>(func);
This generates the same IL as
Func<T, bool> func = expression.Compile();
Predicate<T> predicate = func.Invoke;
I'm not seeing the need for this method. Just use Where().
var sublist = list.Where( expression.Compile() ).ToList();
Or even better, define the expression as a lambda inline.
var sublist = list.Where( l => l.ID == id ).ToList();