Search code examples
c#linqlambdaentity-framework-coreexpression

EF Core 7 - Include only items which are not deleted


In EF Core 7, I am able to filter included collections like

var users = _context.Users.Include(x => x.Roles.Where(y => y.IsDeleted == false)).ToList();

I would like to make an extension, which simplify this include to this:

var users = _context.Users.Include(x => x.Roles, true).ToList();

where true means withoutDeleted.

So, I need to merge two expressions, In my case

Expression<Func<IDbEntityDelete, bool>> expression1 = x => !x.IsDeleted;
Expression<Func<User,ICollection<Role>>> expression2 = x => x.Roles;

First one is constant expression and second one is an expression comming as argument in my extension.

public static IIncludableQueryable<TEntity, TCollection> Include<TEntity, TCollection>(this IQueryable<TEntity> query, Expression<Func<TEntity, TCollection>> navigationExpression, bool withoutDeleted) where TEntity : class, IDbEntityDelete
{

}

Could you please help me to Combine navigationExpression with expression1?

So the result lambda is exactly x => x.Roles.Where(y => y.IsDeleted == false)?

Thank you!


Solution

  • This simple extension implementation should add soft delete filter for Include:

    public static class IncludeExtensions
    {
        public static IIncludableQueryable<TEntity, IEnumerable<TItem>> Include<TEntity, TItem>(
            this IQueryable<TEntity> query, 
            Expression<Func<TEntity, IEnumerable<TItem>>> navigationExpression, 
            bool withoutDeleted
        ) 
            where TEntity : class
            where TItem : class, IDbEntityDelete
        {
            if (!withoutDeleted)
            {
                return query.Include(navigationExpression);
            }
    
            // let the compiler to generate filter stuff.
            Expression<Func<IEnumerable<TItem>, IEnumerable<TItem>>> filterTemplate = q => q.Where(e => !e.IsDeleted);
            
            // replacing template parameter with navigationExpression
            var filterBody = ReplacingExpressionVisitor.Replace(filterTemplate.Parameters[0], navigationExpression.Body, filterTemplate.Body);
            var filterLambda = Expression.Lambda<Func<TEntity, IEnumerable<TItem>>>(filterBody, navigationExpression.Parameters);
    
            return query.Include(filterLambda);
        }
    }