谓词表达式作为过滤参数

时间:2017-06-27 09:39:44

标签: c# linq

我正在尝试创建一个方法来封装LINQ Where调用(在IQueryable上),以便对集合中的特定字段进行过滤,但是如何使其工作则不知所措。

例如,我有一组Job对象,类似于以下内容:

public class Job
{
    public int Id { get; set; }
    public int StatusId { get; set; }
}

public class StatusItem
{
    public int Id { get; set; }
    public string Name { get; set; }
    public bool IsAvailable { get; set; }

    public static readonly StatusItem Canceled = new StatusItem() { Id = (int)StatusEnum.Canceled, Name = StatusEnum.Canceled.ToString(), IsAvailable = true };
    public static readonly StatusItem Created = new StatusItem() { Id = (int)StatusEnum.Created, Name = StatusEnum.Created.ToString(), IsAvailable = true };
    public static readonly StatusItem Open = new StatusItem() { Id = (int)StatusEnum.Open, Name = StatusEnum.Open.ToString(), IsAvailable = true };
    public static readonly StatusItem Assigned = new StatusItem() { Id = (int)StatusEnum.Assigned, Name = StatusEnum.Assigned.ToString(), IsAvailable = false };
}

我希望有一个服务方法只使用系统定义的状态强制执行过滤,如下所示:

IEnumerable<Job> GetAll(Expression<Func<StatusItem, bool>> statusFilter)
{
    // Jobs is IQueryable<job>. How do I apply statusFilter to Job.StatusId?
    return jobs.Where(/* some magic here? */);
}

通话类似于:

return JobService.GetAll(s => s > StatusItem.Open && s < StatusItem.Assigned);

编辑:一直盯着这个太久了。脑现在糊里糊涂。尝试修复以前的错误

2 个答案:

答案 0 :(得分:0)

我不知道这是否是您所需要的,但请查看:

jobs.Where(x => statusFilter.Compile().Invoke((StatusEnum)x.StatusId));

还考虑将属性StatusId更改为StatusEnum。属性也应该公开。

class Job
{
    public int Id { get; set; }
    public StatusEnum StatusId { get; set; }
}

使用这种声明,不需要转换为StatusEnum:

jobs.Where(x => statusFilter.Compile().Invoke(x.StatusId));

答案 1 :(得分:0)

最简单的方法是使用Expression<Func<Job, bool>>代替Expression<Func<StatusEnum, bool>>,这样可以让您编写如下内容:

IEnumerable<Job> GetAll(Expression<Func<Job, bool>> jobFilter)
{
    return jobs.Where(jobFilter);
}

如果您想要通过状态以外的其他方式进行过滤,它还具有更灵活的优势。

如果您真的想使用Expression<Func<StatusEnum, bool>>,它会变得更加复杂,因为您需要重写表达式以从Expression<Func<Job, bool>>生成Expression<Func<StatusEnum, bool>>。这是一种方法:

IEnumerable<Job> GetAll(Expression<Func<StatusEnum, bool>> statusFilter)
{
    var job = Expression.Parameter(typeof(Job), "job");
    var visitor = new ParameterReplacementVisitor(
        statusFilter.Parameters[0],
        Expression.Property(job, nameof(Job.StatusId)));
    Expression<Func<Job, bool>> jobFilter =
        Expression.Lambda<Func<Job, bool>>(
            visitor.Visit(statusFilter.Body),
            job);
    return jobs.Where(jobFilter);
}

class ParameterReplacementVisitor : ExpressionVisitor
{
    private readonly ParameterExpression _parameter;
    private readonly Expression _replacement;

    public ParameterReplacementVisitor(ParameterExpression parameter, Expression replacement)
    {
        _parameter = parameter;
        _replacement = replacement;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        if (node == _parameter)
            return _replacement;
        return node;
    }
}