please take a look at the following snippet:
public IEnumerable<T> Query(Expression<Func<T, bool>> filter)
{
return filter != null ? this.ObjectSet.Where(filter) : null;
}
As for example:
public IQueryable<Department> Query(Expression<Func<Department, bool>> filter)
{
return _context.Departments.Where(filter);
}
(original source: http://blogs.microsoft.co.il/blogs/gilf/archive/2010/06/21/revisiting-the-repository-and-unit-of-work-patterns-with-entity-framework.aspx)
Can anyone provide some concrete examples of how to go about actually calling the second method with an actual query?
Thanks!
To say get all departments with Sales over $100
var deparments = deparmentsRepository.Query(d => d.TotalSales > 100.0m);
Func and Expression> can both be declared the same way using lambda syntax.
In fact there's an example in the article (in the last code snippet) you linked to already for this.
foreach(var department in uow.Departments.Query(d => d.Budget > 150000)) { ... }
Given that Query returns IQueryable there's no reason you can't build into more complex queries.
var deparments = deparmentsRepository.Query(d => d.TotalSales > 100.0m)
.Select(d => new { ... });
Assuming Manager and Department have a foreign key you don't need that join.
var deparments = deparmentsRepository.Query(d => d.Manager.Name.StartsWith("A"));
Related
For every table (and I have a lot of them) I provide Lookup REST API method in my ASP.Net Core application.
This is part of my query for every table:
context.Users.Select(t => new ViewLookupModel()
{
id = t.Id,
title = t.DisplayName
})
//......
context.Groups.Select(t => new ViewLookupModel()
{
id = t.Id,
title = t.Name
})
But I want to write extension to shrink code I need to write for every table. Something like this:
public static IQueryable<ViewLookupModel> SelectLookup<T>(this IQueryable<T> query, Func<int> idField, Func<string> titleField)
{
return query.Select(t => new ViewLookupModel()
{
id = idField(),
title = titleField()
});
}
And use case:
context.Users.SelectLookup(t => t.Id, t => t.DisplayName)
//......
context.Groups.SelectLookup(t => t.Id, t => t.Title)
But I get error:
Delegate 'Func' does not take 1 arguments.
This and this question seems similar, but I can not get it to work.
I am also interesting in any performance issues when querying database with custom SELECT extension method.
Change your extension method to this and try. Extension method takes T as input and returns the corresponding int or string etc.
public static IQueryable<ViewLookupModel> SelectLookup<T>(this IQueryable<T> query, Func<T,int> idField, Func<T,string> titleField)
{
return query.Select(t => new ViewLookupModel()
{
id = idField(t),
title = titleField(t)
});
}
I'm looking for a way to merge multiple expression trees in order to build selectors for an Entity Framework query. The query knows which columns to select based on user-provided parameters. For example, a basic query returns ID/Name columns of an entity. If a parameter is explicitly set to also retrieve the Description column, then the query will return ID/Name/Description.
So, what I need it the code for the MergeExpressions method in the following code.
Expression<Func<T, TDto>> selector1 = x => new TDto
{
Id = x.Id,
Name = x.Name
}
Expression<Func<T, TDto>> selector2 = x => new TDto
{
Description = x.Description
}
var selector = selector1;
if (includeDescription)
selector = MergeExpressions(selector1, selector2);
var results = repo.All().Select(selector).ToList();
Thank you.
Not sure for general case, but merging MemberInitExpression bodied lambdas like in your sample is relatively easy. All you need is to create another MemberInitExpression with combined Bindings:
static Expression<Func<TInput, TOutput>> MergeExpressions<TInput, TOutput>(Expression<Func<TInput, TOutput>> first, Expression<Func<TInput, TOutput>> second)
{
Debug.Assert(first != null && first.Body.NodeType == ExpressionType.MemberInit);
Debug.Assert(second != null && second.Body.NodeType == ExpressionType.MemberInit);
var firstBody = (MemberInitExpression)first.Body;
var secondBody = (MemberInitExpression)second.Body.ReplaceParameter(second.Parameters[0], first.Parameters[0]);
var body = firstBody.Update(firstBody.NewExpression, firstBody.Bindings.Union(secondBody.Bindings));
return first.Update(body, first.Parameters);
}
Note that the lambda expressions must be bound to one and the same parameters, so the above code uses the following parameter replacer helper to rebind second lambda body to the first lambda parameter:
public static partial class ExpressionUtils
{
public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
{
return new ParameterReplacer { Source = source, Target = target }.Visit(expression);
}
class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression Source;
public Expression Target;
protected override Expression VisitParameter(ParameterExpression node)
{
return node == Source ? Target : base.VisitParameter(node);
}
}
}
Check out PredicateBuilder.
Example:
Expression<Func<Customer, bool>> expr1 = (Customer c) => c.CompanyName.StartsWith("A");
Expression<Func<Customer, bool>> expr2 = (Customer c) => c.CompanyName.Contains("B");
var expr3 = PredicateBuilder.And(expr1, expr2);
var query = context.Customers.Where(expr3);
or
var expr3 = expr1.And(expr2);
var query = context.Customers.Where(expr3);
I do this kind of thing with extension methods. Its syntactically a bit nicer than using expression trees everywhere. I call this composable repositories.
I also wrote a tool (LinqExpander) to combine the expression trees of different extension methods togeather, which is especially useful for doing projection (selects) from your database. This is only nessacary when you are doing things with sub-entities. (see my post here: Composable Repositories - Nesting extensions)
usage would be something along the lines of:
var dtos = context.Table
.ThingsIWant() //filter the set
.ToDtos() //project from database model to something else (your Selector)
.ToArray();//enumerate the set
ToDtos might look something like:
public static IQueryable<DtoType> ToDtos(this IQueryable<DatabaseType> things)
{
return things.Select(x=> new DtoType{ Thing = x.Thing ... });
}
You want to merge two selects togeather (im assuming to avoid an underfetch but this seems a bit wierd). I would do this by using a projection like this:
context.Table
.AsExpandable()
.Select(x=>new {
Dto1 = x.ToDto1(),
Dto2 = x.ToDto2()
})
.ToArray();
if you really wanted it to return a single entity like this you could probably do something like:
context.Table
.AsExpandable()
.Select(x=> ToDto1(x).ToDto2(x));
but I havent ever tried this.
As this uses a sub projection you will need the .AsExpandable extensions.
I generally use a generic repository to boilerplate my EF queries so I have to write limited code and also use caching. The source code for the repository can be found here.
The backbone query within the code is this one below. FromCache<T>() is an IEnumerable<T> extension method that utilizes the HttpContext.Cache to store the query using a stringified representation of the lambda expression as a key.
public IQueryable<T> Any<T>(Expression<Func<T, bool>> expression = null)
where T : class, new()
{
// Check for a filtering expression and pull all if not.
if (expression == null)
{
return this.context.Set<T>()
.AsNoTracking()
.FromCache<T>(null)
.AsQueryable();
}
return this.context.Set<T>()
.AsNoTracking<T>()
.Where<T>(expression)
.FromCache<T>(expression)
.AsQueryable<T>();
}
Whilst this all works it is subject to the N+1 problem for related tables since If I were to write a query like so:
var posts = this.ReadOnlySession.Any<Post>(p => p.IsDeleted == false)
.Include(p => p.Author);
The Include() will have no effect on my query since it has already been run in order to be cached.
Now I know that I can force Entity Framework to use eager loading within my model by removing the virtual prefix on my navigation properties but that to me feels like the wrong place to do it as you cannot predict the types of queries you will be making. To me it feels like something I would be doing in a controller class. What I am wondering is whether I can pass a list of includes into my Any<T>() method that I could then iterate though when I make the call?
ofDid you mean something like...
IQueryable<T> AnyWithInclude<T,I>(Expression<Func<T,bool>> predicate,
Expression<Func<T,I>> includeInfo)
{
return DbSet<T>.where(predicate).include(includeInfo);
}
the call
Context.YourDbSetReference.AnyWithInclude(t => t.Id==someId, i => i.someNavProp);
In response to extra question on as collection.
I realised late, there was an overload on Property. You can just pass a string
This might work but call is not easy. Well I find it hard.
IQueryable<T> GetListWithInclude<I>(Expression<Func<T, bool>> predicate,
params Expression<Func<T, I>>[] IncludeCollection);
so i tried
public virtual IQueryable<T> GetListWithInclude(Expression<Func<T, bool>> predicate,
List<string> includeCollection)
{ var result = EntityDbSet.Where(predicate);
foreach (var incl in includeCollection)
{
result = result.Include(incl);
}
return result;
}
and called with
var ic = new List<string>();
ic.Add("Membership");
var res = context.DbSte<T>.GetListWithInclude( t=>t.UserName =="fred", ic);
worked as before.
In the interest of clarity I'm adding the solution I came up with based upon #soadyp's answer.
public IQueryable<T> Any<T>(Expression<Func<T, bool>> expression = null,
params Expression<Func<T, object>>[] includeCollection)
where T : class, new()
{
IQueryable<T> query = this.context.Set<T>().AsNoTracking().AsQueryable<T>();
if (includeCollection.Any())
{
query = includeCollection.Aggregate(query,
(current, include) => current.Include(include));
}
// Check for a filtering expression and pull all if not.
if (expression != null)
{
query = query.Where<T>(expression);
}
return query.FromCache<T>(expression, includeCollection)
.AsQueryable<T>();
}
Usage:
// The second, third, fourth etc parameters are the strongly typed includes.
var posts = this.ReadOnlySession.Any<Post>(p => p.IsDeleted == false,
p => p.Author);
I have an extension on IQueryable that allows passing in delimited string of property names which, when used causes query not to construct JOINs and effectively causes SELECT N+1 issue.
What I noticed is that if I call native EF extension .Include("property") directly off of DbSet everything works fine. But if I use my extension (I even simplified it to just call .Include("property") SELECT N+1 occurs...
My questions is why? What am I doing wrong?
Here is calling method (from service)
public MyModel[] GetAll(int page, out int total, int pageSize, string sort, string filter)
{
return _myModelRepository
.Get(page, out total, pageSize, sort, filter, "PropertyOnMyModelToInclude")
.ToArray();
}
Here is the repository method that uses extension
public virtual IQueryable<T> Get(int page, out int total, int pageSize, string sort, string filter = null, string includes = null)
{
IQueryable<T> query = DatabaseSet;
if (!String.IsNullOrWhiteSpace(includes))
{
//query.IncludeMany(includes); // BAD: SELECT N+1
//query.Include(includes); // BAD: SELECT N+1
}
if (!String.IsNullOrWhiteSpace(filter))
{
query.Where(filter);
}
total = query.Count(); // needed for pagination
var order = String.IsNullOrWhiteSpace(sort) ? DefaultOrderBy : sort;
var perPage = pageSize < 1 ? DefaultPageSize : pageSize;
//return query.OrderBy(order).Paginate(page, total, perPage); // BAD: SELECT N+1 (in both variations above)
//return query.IncludeMany(includes).OrderBy(order).Paginate(page, total, perPage); // BAD: SELECT N+1
return query.Include(includes).OrderBy(order).Paginate(page, total, perPage); // WORKS!
}
Here is the extension (reduced just to call Include() to illustrate the issue)
public static IQueryable<T> IncludeMany<T>(this IQueryable<T> query, string includes, char delimiter = ',') where T : class
{
// OPTION 1
//var propertiesToInclude = String.IsNullOrWhiteSpace(includes)
// ? new string[0]
// : includes.Split(new[] {delimiter}, StringSplitOptions.RemoveEmptyEntries).Select(p => p.Trim()).ToArray();
//foreach (var includeProperty in propertiesToInclude)
//{
// query.Include(includeProperty);
//}
// OPTION 2
//if (!String.IsNullOrWhiteSpace(includes))
//{
// var propertiesToInclude = includes.Split(new[] { delimiter }, StringSplitOptions.RemoveEmptyEntries).AsEnumerable(); //.Select(p => p.Trim());
// propertiesToInclude.Aggregate(query, (current, include) => current.Include(include));
//}
// OPTION 3 - for testing
query.Include(includes);
return query;
}
I think the fundamental problem here is in the way you are using the Include method, and also incidentally, the Where method. These methods, as is typical with LINQ extension methods, do not modify the object that they are called on. Instead they return a new object which represents the query after the operator has been applied. So, for example, in this code:
var query = SomeQuery();
query.Include(q => q.Bing);
return query;
the Include method basically does nothing because the new query returned by Include is thrown away. On the other hand, this:
var query = SomeQuery();
query = query.Include(q => q.Bing);
return query;
applies the Include to the query and then updates the query variable with the new query object returned from Include.
It's not in the code you have posted, but I think you are still seeing N+1 with your code because the Include is being ignored and the related collections are therefore still being loaded using lazy loading.
We have a class called Task:
public partial class Task : EntityObject
{
public EntityCollection<TaskUser> TaskUsers { get {...} set{...} }
}
It has navigation property called TaskUsers, which contains users attached to this taks:
public partial class TaskUser : EntityObject
{
public User User { get {...} set { } }
}
Every TaskUser object has User object.
We are given IQueryable<Task> tasks. We want to find tasks assigned to user with ID = 1. When we use
tasks.Where(t => t.TaskUsers.Any(a => a.User.ID == 1))
everything works fine. When we use
Func<TaskUser, bool> function = a => a.User.ID == 1;
return tasks.Where(t => t.TaskUsers.Any(function));
we get nice 'Internal .NET Framework Data Provider error 1025' error. Why? I want to build much more complicated filters using Expression class, but if I can't pass simple Func, this can't be done. What should I do?
EDIT
Maybe
Func<TaskUser, bool> function = a => a.User.ID == 1;
return tasks.Where(t => t.TaskUsers.Any(function));
doesn't work, but
Expression<Func<TaskUser, bool>> expression = a => a.User.ID == 1;
return tasks.Where(t => t.TaskUsers.AsQueryable().Any(expression));
works! That is all I needed.
Well the EF can only translate Expressions, not functions.
i.e. it can translate this:
Expression<Func<TaskUser,bool>>
but not this:
Func<TaskUser,bool>
As for how to merge expressions (in pseudo code):
Expression<Func<TaskUser, bool>> expression = a => a.User.ID == 1;
return tasks.Where(t => t.TaskUsers.Any(expression));
There are probably some Expression guru's who can help with that.
I suggest a followup question focused on that particular problem
Alex