Entity Framework 5. Multiple Include. Is this possible? - entity-framework

I am trying to create a multiple include method in my repository to use as follows:
repository.Include<Post>(x => x.Images, x => x.Tags).First(x => x.Id == 1)
I tried something as:
public IQueryable<T> Include<T>(params Expression<Func<T, Object>>[] paths) where T : class {
return paths.Aggregate(_context.Set<T>(), (x, path) => x.Include(path));
} // Include
But I get the error:
Cannot implicitly convert type 'System.Linq.IQueryable' to 'System.Data.Entity.DbSet'.
Note that the original include is the following:
public static IQueryable Include(
this IQueryable source,
Expression> path
) where T : class;
Can I make this work without turning my repository method into static?
Thank You,
Miguel

If you really want to create your own .Include non-extension method that allows for multiple paths in one call, internally translating to the already provided .Include method, you can do something like
public IQueryable<T> Include<T>(params Expression<Func<T, object>>[] paths)
where T : class
{
IQueryable<T> query = _context.Set<T>();
foreach (var path in paths)
query = query.Include(path);
return query;
}
This is pretty close to what you have already, but avoids the pitfall with Enumerable.Aggregate that you encountered: you'd have the same problem if you replace IQueryable<T> query with var query in my version.
Note that using many .Include may harm performance. If it does in your situation, you can rewrite it to use multiple queries, which you can run one after the other in a transaction.
Personally, as you can call the already provided .Include extension method (using System.Data.Entity;) on any IQueryable, I'd just write it as:
repository.Posts.Include(x => x.Images).Include(x => x.Tags).First(x => x.Id == 1)

public ICollection<SomeClass> Filter(string name, int skip, int take,out int total, params Expression<Func<SomeClass, object>>[] includeProperties) {
IQueryable<SomeClass> query = Session.All<SomeClass>().AsNoTracking();
//query = includeProperties.Aggregate(query, (current, property) => current.Include(property));
foreach (var property in includeProperties){
query = query.Include(property);
}
if (!string.IsNullOrWhiteSpace(name)){
query = query.Where(x => x.Name.Contains(name));
var page = query.OrderBy(x => x.Name)
.Skip(skip)
.Take(take)
.GroupBy(p => new{Total = query.Count()})
.FirstOrDefault();
total = (page != null) ? page.Key.Total : 0;
if (page == null) {
return new List<SomeClass>();
}
return page.ToList();
} else {
var page = query.OrderBy(x => x.Name)
.Skip(skip)
.Take(take)
.GroupBy(p => new { Total = query.Count() })
.FirstOrDefault();
total = (page != null) ? page.Key.Total : 0;
if (page == null) {
return new List<SomeClass>();
}
return page.ToList();
}
}

I guess the shortest way is as follows:
public static class LinqExtensions
{
/// <summary>
/// Acts similar of .Include() LINQ method, but allows to include several object properties at once.
/// </summary>
public static IQueryable<T> IncludeMultiple<T>(this IQueryable<T> query, params Expression<Func<T, object>>[] paths)
where T : class
{
foreach (var path in paths)
query = query.Include(path);
return query;
}
}

Related

How to specify a default Sort with Hotchocolate and EF Core?

Is there a way to add a Default Sort field, so that I can UsePaging and UserSorting, but if no order is specified I add a field, such as Id. But if the user does specify an order, then don't add the default.
For example, I can add the default sort to the query method, but then no other sorting works
[UseContext]
[UsePaging]
[UseProjection]
[UseFiltering]
[UseSorting]
public IQueryable<Property> GetProperties([ScopedService] PropContext dbContext)
{
return dbContext.Properties
.OrderBy(p => p.Id); // Default sort by Prop Id
}
If don't have a sort, then Entity Framework shows a warning:
The query uses a row limiting operator ('Skip'/'Take') without an 'OrderBy' operator.
This may lead to unpredictable results
And I've seen some unexpected results
Credit to S Blomstrand
Using these extension methods
public static bool HasOrderByArgument(this IResolverContext context,
string argumentName = "order")
{
try
{
var orderByArgument = context.ArgumentLiteral<IValueNode>(argumentName);
if (orderByArgument != NullValueNode.Default && orderByArgument != null)
{
return true;
}
}
catch
{
return false;
}
return false;
}
public static IQueryable<T> OrderByArgumentOrDefault<T>(this IQueryable<T> query, IResolverContext context,
Func<IQueryable<T>> func, string argumentName = "order")
{
if (context.HasOrderByArgument(argumentName))
{
return query;
}
return func.Invoke();
}
It can then be call as follows:
[UseContext]
[UsePaging]
[UseProjection]
[UseFiltering]
[UseSorting]
public IQueryable<Property> GetProperties([ScopedService] PropContext dbContext)
{
return dbContext.Properties
.OrderByArgumentOrDefault(context, () => properties.OrderBy(p => p.Id));
}
the Kerr/Blomstrand answer to this question didn't quite work for me as written
The following changes did work:
you need an IResolverContext argument in the query method (the runtime will fill it, no attribute needed)
change the Func to an Expression
public static IQueryable<T> OrderByArgumentOrDefault<T>(
this IQueryable<T> query,
IResolverContext context,
Expression<Func<IQueryable<T>>> expression,
string argumentName = "order")
{
return context.HasOrderByArgument(argumentName) ? query : expression.Compile().Invoke();
}
public IQueryable<Property> GetProperties(
[ScopedService] PropContext dbContext,
IResolverContext context)
{
return dbContext.Properties
.OrderByArgumentOrDefault(context, () => dbContext.Properties.OrderBy(p => p.Id));
}

EF Core 2+ - Eager loading using generic repository [duplicate]

This question already has answers here:
.ThenInclude for sub entity in Entity Framework Core 2
(2 answers)
Closed 2 years ago.
My BaseRespository is like so:
public abstract class BaseRespository<TEntity, TContext> : IBaseRepository<TEntity>
where TEntity : class
where TContext : DbContext
{
private readonly TContext _context;
protected BaseRespository(TContext context)
{
_context = context;
}
public async Task<TEntity> GetByCondition(Expression<Func<TEntity, bool>> predicate)
{
return await _context.Set<TEntity>().Where(predicate).FirstOrDefaultAsync();
}
}
And I access the GetByCondition method like so:
public async Task<Tips> GetTipBySlug(string slug)
{
Expression<Func<Tips, bool>> predicate = (t) => t.Slug == slug &&
t.Status == (int)LU_Status.active &&
t.User.Status == (int)LU_Status.active;
return await _tipRepository.GetByCondition(predicate);
}
I want to use the Include and ThenInclude of EF Core with the predicate(This is just my desire) like so:
public async Task<Tips> GetTipBySlug(string slug)
{
Expression<Func<Tips, bool>> whereExpr = (t) => t.Include(t=>t.User).ThenInclude(u=>u.UserImages)
t.Slug == slug &&
t.Status == (int)LU_Status.active &&
t.User.Status == (int)LU_Status.active;
return await _tipRepository.GetByCondition(whereExpr);
}
How can I add the desired t.Include(t=>t.User).ThenInclude(u=>u.UserImages) to the predicate using EF CORE 2 and above?
Even if this worked without the need to split up your logic across multiple repository argumets, would you really prefer to write
Expression<Func<Tips, bool>> whereExpr = (t) => t.Include(t=>t.User).ThenInclude(u=>u.UserImages)
t.Slug == slug &&
t.Status == (int)LU_Status.active &&
t.User.Status == (int)LU_Status.active;
return await _tipRepository.GetByCondition(whereExpr);
over the way EF was designed to be used:
var q = _tipRepository.Set<Tips>()
.Include(t=>t.User)
.ThenInclude(u=>u.UserImages)
.Where(t => t.Status == (int)LU_Status.active)
.Where(t => t.User.Status == (int)LU_Status.active);
return await q.FirstOrDefaultAsync();
Why would you want to create an Expression<Func<Tips,bool>> instead of just an IQueryabe<T>. It has nothing do do with whether the repository is "generic", and everything to do with how you want to write queries. Queries are written by the consumers of the repository. Not by or in the repository (except to the extent you want to reuse a query across consumers).
The crazy thing about this design is that it allows the repo's consumer to specify the query. It just forces them to do it through a clunky, bespoke API.
You could write it like this:
public async Task<TEntity> GetByCondition<TEntity>(Expression<Func<TEntity, bool>> predicate, Func<DbSet<TEntity>, IQueryable<TEntity>> baseQuery = null) where TEntity : class
{
IQueryable<TEntity> q = Set<TEntity>();
if (baseQuery != null)
{
q = baseQuery(Set<TEntity>());
}
return await q.Where(predicate).FirstOrDefaultAsync();
}
You don't need Expression for that one because the Include is not deferred. It's a function that returns an IIncludableQueryable, so it's a query transformation function.
And then:
public async Task<Tips> GetTipBySlug(string slug)
{
Expression<Func<Tips, bool>> whereExpr = (t) => t.Slug == slug &&
t.Status ==1 &&
t.User.Status == 1;
return await GetByCondition(whereExpr, s => s.Include(t => t.User).ThenInclude(u => u.UserImages)) ;
}

Finally got Include working with child objects in Entity Framework 6.1

I'm using a Generic Repository pattern, so it was crucial that I got this to work. I read in a couple of places online that Entity Framework will start to ignore Includes once the shape of the query changes. The fix was to move the Includes to the end of the query. This did not work for me. In fact, just the exact opposite is working. I moved the Where statement to the end of the Includes query.
Here is what I had.
public Task<List<T>> ItemsWithAsync(Expression<Func<T, bool>> predicate = null, params Expression<Func<T, object>>[] includeProperties)
{
IQueryable<T> query = null;
if (predicate != null)
query = _context.Set<T>().Where(predicate);
else
query = _context.Set<T>();
foreach (var includeProperty in includeProperties)
{
query = query.Include(includeProperty);
}
return query.ToListAsync();
}
Here is what now works for me.
public Task<List<T>> ItemsWithAsync2(Expression<Func<T, bool>> predicate = null, params Expression<Func<T, object>>[] includeProperties)
{
var query = _context.Set<T>() as IQueryable<T>; // _dbSet = dbContext.Set<TEntity>()
query = includeProperties.Aggregate(query, (current, property) => current.Include(property)).Where(predicate);
return query.AsNoTracking().ToListAsync();
}
The key for me was to move the .Where(predicate) to the end of the query that handles all the includedProperties.
In my case, this returns all the Parent objects and two Child objects for each record. Prior to this fix, I would get all the Parents objects and only 4 of the records would contain the Child objects.
Here is how I'm calling the methods.
using (var uow = _unitOfWorkFactory.Create())
{
return (await uow.OfferRepository.ItemsWithAsync2(o =>
o.Deleted == false
&& o.StartDate <= clientDateTime
&& o.ExpiryDate >= clientDateTime, o => o.Merchant, o => o.App)).ToList();
}
Hope this helps! I searched for days and days. I never found the exact solution posted, which is why I posted it. Can anyone comment if this is an efficient way to solve the problem? Also, is the query.AsNoTracking actually needed? Thanks!
Here is what now works for me.
public Task<List<T>> ItemsWithAsync2(Expression<Func<T, bool>> predicate = null, params Expression<Func<T, object>>[] includeProperties)
{
var query = _context.Set<T>() as IQueryable<T>; // _dbSet = dbContext.Set<TEntity>()
query = includeProperties.Aggregate(query, (current, property) => current.Include(property)).Where(predicate);
return query.AsNoTracking().ToListAsync();
}
The key for me was to move the .Where(predicate) to the end of the query that handles all the includedProperties.
In my case, this returns all the Parent objects and two Child objects for each record. Prior to this fix, I would get all the Parents objects and only 4 of the records would contain the Child objects.
This also works with nested Child objects like o.Merchant.Locations.

Linq Expression building for Entity Framework involving complex object

I am using Entity Framework version 4. I need to compare a large (~1 million record) SQL Server table to a longish (~2000) array of complex objects returned from a web service. Five different properties need to be compared to determine whether an instance of the complex object is already in the database.
I created a function that returns an expression for use in .Where and .Any methods. It looks like this (where A is the complex object, and tblA is the EF class):
function Expression<tblA, bool> GetSearchPredicate(A a)
{
return ta => ta.Field1.Equals(a.Field1)
&& ta.Field2.Equals(a.Field2)
&& ta.Field3.Equals(a.Field3)
&& ta.Field4.Equals(a.Field4)
&& ta.Field5.Equals(a.Field5);
}
This works. And I can compare all 2000 instances of A by doing this:
IEnumerable<A> objects = [web service call];
var result = objects.Select(a => !db.tblA.Any(GetSearchPredicate(a)));
That works, too. But it's slow. So I looked into building a utility method that could build an expression that could be transmitted down to the database directly through EF.
I used the code in this question as a basis for building that utility method. The example in that question shows comparing a single property to a series of constants, while my version would have to compare multiple properties to multiple constants. My best effort is below:
public static IQueryable<TEntity> WhereIn<TEntity>
(
this ObjectQuery<TEntity> query,
IEnumerable<Expression<Func<TEntity, bool>>> predicates
)
{
if (predicates == null) throw new ArgumentNullException("predicates");
IEnumerable<ParameterExpression> p = predicates.Select(pred => pred.Parameters.Single()).ToArray();
IEnumerable<Expression> equals = predicates.Select(value =>
(Expression)value.Body);
Expression bigEqual = equals.Aggregate((accumulate, equal) =>
Expression.Or(accumulate, equal));
var result1 = Expression.Lambda<Func<TEntity, bool>>(bigEqual, p.First());
var result = query.Where(result1);
return result;
}
This would be invoked like this:
IEnumerable<A> objects = [web service call];
var result = db.tblA.WhereIn(objects.Select(a => GetSearchPredicate(a)));
What I get is a message saying that "ta" (the placeholder for the TEntity object) is not bound. I thought this was because I had multiple expressions (the variable predicates) being combined, and maybe this message was being thrown because I was only passing the parameter from the first of the predicates IEnumerable. But this happens even if predicates is one expression long.
I am reasonably sure, based on the method I linked to, that I could build an expression comparing each of the five properties to a constant (the values of A.Field1 through A.Field5), rather than passing in the parameter predicates that already has them assembled into a series of expressions. But I would rather not, since that would require my method to know that it's working with types A and tblA, and that's the opposite of generic and general-purpose. (It'd also be complex and messy.)
I hope the examples I've shown explain what I want to do. Can it be done in a generic way?
You will need to replace the parameter in the predicate bodies with a single parameter. Something like this should work:
public static Expression<Func<T, bool>> BuildOr<T>(
IEnumerable<Expression<Func<T, bool>>> predicates)
{
Expression body = null;
ParameterExpression p = null;
Expression<Func<T, bool>> first = null;
foreach (Expression<Func<T, bool>> item in predicates)
{
if (first == null)
{
first = item;
}
else
{
if (body == null)
{
body = first.Body;
p = first.Parameters[0];
}
var toReplace = item.Parameters[0];
var itemBody = ReplacementVisitor.Transform(item, toReplace, p);
body = Expression.OrElse(body, itemBody);
}
}
if (first == null)
{
throw new ArgumentException("Sequence contains no elements.", "predicates");
}
return (body == null) ? first : Expression.Lambda<Func<T, bool>>(body, p);
}
private sealed class ReplacementVisitor : ExpressionVisitor
{
private IList<ParameterExpression> SourceParameters { get; set; }
private Expression ToFind { get; set; }
private Expression ReplaceWith { get; set; }
public static Expression Transform(
LambdaExpression source,
Expression toFind,
Expression replaceWith)
{
var visitor = new ReplacementVisitor
{
SourceParameters = source.Parameters,
ToFind = toFind,
ReplaceWith = replaceWith,
};
return visitor.Visit(source.Body);
}
private Expression ReplaceNode(Expression node)
{
return (node == ToFind) ? ReplaceWith : node;
}
protected override Expression VisitConstant(ConstantExpression node)
{
return ReplaceNode(node);
}
protected override Expression VisitBinary(BinaryExpression node)
{
var result = ReplaceNode(node);
if (result == node) result = base.VisitBinary(node);
return result;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (SourceParameters.Contains(node)) return ReplaceNode(node);
return SourceParameters.FirstOrDefault(p => p.Name == node.Name) ?? node;
}
}
Your WhereIn method then becomes:
public static IQueryable<TEntity> WhereIn<TEntity>(
this ObjectQuery<TEntity> query,
IEnumerable<Expression<Func<TEntity, bool>>> predicates)
{
if (predicates == null) throw new ArgumentNullException("predicates");
var predicate = BuildOr(predicates);
return query.Where(predicate);
}

LINQ to Entities - cannot cast 'System.DateTime' to type 'System.Object' in orderBy

I am trying to order an IQueryable of entities by date from a passed in Expression< Func< T, object>> and am getting the error: "Unable to cast the type 'System.Nullable`1' to type 'System.Object'. LINQ to Entities only supports casting Entity Data Model primitive types." The entity has a nullable datetime property on it on which I am trying to sort:
Example: (where e.Date is a nullable DateTime)
Expression<Func<T,object>> sorter = (e) => e.Date;
IOrderedQueryable<T> sortedData = data.OrderBy(sorter);
Thanks in advance!
I wrote a simple class for ordering entities based on a lambda expression at runtime.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace DataModeling
{
public class QueryOrderer<TEntity>
where TEntity : class
{
private LambdaExpression defaultSortExpression;
private Dictionary<string, LambdaExpression> orderFieldLookup;
public QueryOrderer()
{
orderFieldLookup = new Dictionary<string, LambdaExpression>();
}
public void AddOrderMapping<TProp>(string fieldName, Expression<Func<TEntity, TProp>> selector)
{
orderFieldLookup[fieldName] = selector;
}
public void SetDefaultSortExpression<TProp>(Expression<Func<TEntity, TProp>> selector)
{
defaultSortExpression = selector;
}
public IQueryable<TEntity> GetOrderedEntries(string field, bool isDescending, IQueryable<TEntity> entries)
{
return orderEntries(entries, field, isDescending);
}
private IQueryable<TEntity> orderEntries(IQueryable<TEntity> entries, string fieldName, bool isDescending)
{
dynamic lambda = getOrderByLambda(entries, fieldName);
if (lambda == null)
{
return entries;
}
if (isDescending)
{
return Queryable.OrderByDescending(entries, lambda);
}
else
{
return Queryable.OrderBy(entries, lambda);
}
}
private dynamic getOrderByLambda(IQueryable<TEntity> entries, string fieldName)
{
if (!String.IsNullOrWhiteSpace(fieldName) && orderFieldLookup.ContainsKey(fieldName))
{
return orderFieldLookup[fieldName];
}
else
{
return defaultSortExpression;
}
}
}
}
You use this class by initially setting up all of the fields:
QueryOrderer<User> orderer = new QueryOrderer<User>();
orderer.SetDefaultSortExpression(u => u.FullName);
orderer.AddOrderMapping("UserId", u => u.UserId);
orderer.AddOrderMapping("Name", u => u.FullName);
orderer.AddOrderMapping("Email", u => u.Email);
orderer.AddOrderMapping("CreatedOn", u => u.CreatedOn);
...
var users = orderer.GetOrderedEntries("CreatedOn", isDescending: false, context.Users);
I nice feature of this code is that it handles look-up values perfectly. For instance, if you're trying to sort using the description rather than a key, you can use the outer context when building up the sort expression:
orderer.AddOrderMapping("UserType",
u => context.UserTypes
.Where(t => t.UserTypeId == u.UserTypeId)
.Select(t => t.Description)
.FirstOrDefault());
Entity Framework is smart enough to just fold the sub-query right into the outer query.
Two problem here: First you use object in your sorter, you should use DateTime. Secondly every element must have a place in the order so you have to define what should happen with elements where Date is null:
Expression<Func<T, DateTime>> sorter = (e) => e.Date ?? DateTime.MaxValue;
IOrderedQueryable<T> sortedData = data.OrderBy(sorter);
Try to reconstruct expression body
private LambdaExpression CreateLambdaPropertyGetter(Expression<Func<TEntity, object>> expression)
{
Expression body;
if (expression.Body is UnaryExpression && ((UnaryExpression)expression.Body).NodeType == ExpressionType.Convert)
body = ((UnaryExpression)expression.Body).Operand;
else
body = expression.Body;
var lambda = Expression.Lambda(body, expression.Parameters);
return lambda;
}
Try using Func delegate instead on Expression<Func>
Func<T,object> sorter = (e) => e.Date;
IOrderedEnumerable<T> sortedData = data.OrderBy(sorter);