c# - 如何使用附加参数更新 Linq 表达式?

标签 c# linq

我有一个 Linq 表达式,它可能会根据特定条件进行更改。我想做的一个例子(我不确定的部分留空):

Expression<Func<Project, bool>> filter = (Project p) => p.UserName == "Bob";
if(showArchived)
{
    // update filter to add && p.Archived
}
// query the database when the filter is built
IEnumerable<Project> projects = unitOfWork.ProjectRepository.Get(filter);

如何更新过滤器以添加任何额外参数?

此时所有记录都已检索,然后我使用 Where 进一步过滤结果。然而,这会导致对数据库的查询比绝对必要的多。

IEnumerable<Project> projects = unitOfWork.ProjectRepository.Get(filter);
if(showArchived)
{
    projects = projects.Where(p => p.Archived);
}

Get 方法使用 GenericRepository 模式:

public class GenericRepository<TEntity> where TEntity : class
{
    internal ProgrammeDBContext context;
    internal DbSet<TEntity> dbSet;

    public GenericRepository(ProgrammeDBContext context)
    {
        this.context = context;
        this.dbSet = context.Set<TEntity>();
    }

    public virtual IEnumerable<TEntity> Get(
        Expression<Func<TEntity, bool>> filter = null,
        Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
        string includeProperties = "")
    {
        IQueryable<TEntity> query = dbSet;

        if (filter != null)
        {
            query = query.Where(filter);
        }

        foreach (var includeProperty in includeProperties.Split
            (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
        {
            query = query.Include(includeProperty);
        }

        if (orderBy != null)
        {
            return orderBy(query).ToList();
        }
        else
        {
            return query.ToList();
        }
    }

    public virtual TEntity GetByID(object id)
    {
        return dbSet.Find(id);
    }

    public virtual void Insert(TEntity entity)
    {
        dbSet.Add(entity);
    }

    public virtual void Delete(object id)
    {
        TEntity entityToDelete = dbSet.Find(id);
        Delete(entityToDelete);
    }

    public virtual void Delete(TEntity entityToDelete)
    {
        if (context.Entry(entityToDelete).State == EntityState.Detached)
        {
            dbSet.Attach(entityToDelete);
        }
        dbSet.Remove(entityToDelete);
    }

    public virtual void Update(TEntity entityToUpdate)
    {
        dbSet.Attach(entityToUpdate);
        context.Entry(entityToUpdate).State = EntityState.Modified;
    }

    public virtual IEnumerable<TEntity> GetWithRawSql(string query, params object[] parameters)
    {
        return dbSet.SqlQuery(query, parameters).ToList();
    }
}

更新
根据 Marc Gravell 和 David B 的以下代码创建了一些扩展方法,为我解决了问题

public static class LinqExtensionMethods
{
    public static Expression<Func<T, bool>> CombineOr<T>(params Expression<Func<T, bool>>[] filters)
    {
        return filters.CombineOr();
    }

    public static Expression<Func<T, bool>> CombineOr<T>(this IEnumerable<Expression<Func<T, bool>>> filters)
    {
        if (!filters.Any())
        {
            Expression<Func<T, bool>> alwaysTrue = x => true;
            return alwaysTrue;
        }
        Expression<Func<T, bool>> firstFilter = filters.First();

        var lastFilter = firstFilter;
        Expression<Func<T, bool>> result = null;
        foreach (var nextFilter in filters.Skip(1))
        {
            var nextExpression = new ReplaceVisitor(lastFilter.Parameters[0], nextFilter.Parameters[0]).Visit(lastFilter.Body);
            result = Expression.Lambda<Func<T, bool>>(Expression.OrElse(nextExpression, nextFilter.Body), nextFilter.Parameters);
            lastFilter = nextFilter;
        }
        return result;
    }

    public static Expression<Func<T, bool>> CombineAnd<T>(params Expression<Func<T, bool>>[] filters)
    {
        return filters.CombineAnd();
    }

    public static Expression<Func<T, bool>> CombineAnd<T>(this IEnumerable<Expression<Func<T, bool>>> filters)
    {
        if (!filters.Any())
        {
            Expression<Func<T, bool>> alwaysTrue = x => true;
            return alwaysTrue;
        }
        Expression<Func<T, bool>> firstFilter = filters.First();

        var lastFilter = firstFilter;
        Expression<Func<T, bool>> result = null;
        foreach (var nextFilter in filters.Skip(1))
        {
            var nextExpression = new ReplaceVisitor(lastFilter.Parameters[0], nextFilter.Parameters[0]).Visit(lastFilter.Body);
            result = Expression.Lambda<Func<T, bool>>(Expression.AndAlso(nextExpression, nextFilter.Body), nextFilter.Parameters);
            lastFilter = nextFilter;
        }
        return result;
    }

    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);
        }
    }
}

最佳答案

如果我理解这个问题,那么很可能是这里的问题:

IEnumerable<Project> projects = unitOfWork.ProjectRepository.Get(filter);

项目 上的任何工作都将使用Enumerable,而不是Queryable;应该是:

IQueryable<Project> projects = unitOfWork.ProjectRepository.Get(filter);
if(showArchived)
{
    projects = projects.Where(p => p.Archived);
}

后者是可组合的.Where 应该按您预期的方式工作,在将其发送到服务器之前构建限制性更强的查询.

您的另一个选择是重写过滤器以在发送前合并:

using System;
using System.Linq.Expressions;

static class Program
{
    static void Main()
    {
        Expression<Func<Foo, bool>> filter1 = x => x.A > 1;
        Expression<Func<Foo, bool>> filter2 = x => x.B > 2.5;

        // combine two predicates:
        // need to rewrite one of the lambdas, swapping in the parameter from the other
        var rewrittenBody1 = new ReplaceVisitor(
            filter1.Parameters[0], filter2.Parameters[0]).Visit(filter1.Body);
        var newFilter = Expression.Lambda<Func<Foo, bool>>(
            Expression.AndAlso(rewrittenBody1, filter2.Body), filter2.Parameters);
        // newFilter is equivalent to: x => x.A > 1 && x.B > 2.5
    }
}
class Foo
{
    public int A { get; set; }
    public float B { get; set; }
}
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);
    }
}

或者以方便使用的方式重写:

using System;
using System.Linq.Expressions;

static class Program
{
    static void Main()
    {
        Expression<Func<Foo, bool>> filter = x => x.A > 1;

        bool applySecondFilter = true;
        if(applySecondFilter)
        {
            filter = Combine(filter, x => x.B > 2.5);
        }
        var data = repo.Get(filter);
    }
    static Expression<Func<T,bool>> Combine<T>(Expression<Func<T,bool>> filter1, Expression<Func<T,bool>> filter2)
    {
        // combine two predicates:
        // need to rewrite one of the lambdas, swapping in the parameter from the other
        var rewrittenBody1 = new ReplaceVisitor(
            filter1.Parameters[0], filter2.Parameters[0]).Visit(filter1.Body);
        var newFilter = Expression.Lambda<Func<T, bool>>(
            Expression.AndAlso(rewrittenBody1, filter2.Body), filter2.Parameters);
        return newFilter;
    }
}
class Foo
{
    public int A { get; set; }
    public float B { get; set; }
}
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);
    }
}

关于c# - 如何使用附加参数更新 Linq 表达式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9683474/

相关文章:

c# - 如果 System.Enum 是一个类,它怎么可能也是一个 ValueType?

C# 获取非托管 dll 的版本

c# - 带有 LINQ 数据源插入模板的 ListView

c# - 忽略生成查询表达式 EF Core 的 ThenIninclude 语句

c# - 我可以从枚举中取出前 n 个元素,然后仍然使用枚举的其余部分吗?

c# - 在 C# 中解析 XML 时出现路径错误中的非法字符

c# - 几个 C# 语言问题

c# - 使用 LINQ 进行 AnyCase 搜索

C# Linq 语句或用于总计子集的 foreach()?

c# - 按日期值排序列表