将表达式插入到 LINQ-To-Sql 的 LINQ 语句中

Insert expression into LINQ statement for Linq-To-Sql

我有一个在 LINQ 表达式中使用的半复杂函数。该函数称为 DatePeriodOverlap(),如下所示:

 bool DatePeriodOverlap(DateTime startDate, DateTime endDate, DateTime filterStartDate, DateTime filterEndDate)
 {
    return ((startDate >= filterStartDate && startDate <= filterEndDate)
            ||
            (endDate <= filterEndDate && endDate >= filterStartDate)
            ||
            (startDate <= filterStartDate && endDate >= filterEndDate));
}

现在,我想在 LINQ to SQL 场景 (nHibernate) 中使用它。这意味着,我不能只使用函数本身,而是必须创建一个表达式并使用它。所以我创建了这个函数来做到这一点:

Expression<Func<Domain.Course, bool>> GetDatePeriodOverlapForCourseExpression(DateTime filterStartDate, DateTime filterEndDate)
{
    return x => (x.StartDate >= filterStartDate && x.StartDate <= filterEndDate)
                ||
                (x.EndDate <= filterEndDate && x.EndDate >= filterStartDate)
                ||
                (x.StartDate <= filterStartDate && x.EndDate >= filterEndDate);
}

我现在可以毫无问题地在我的查询中使用它。但是,我必须为每个具有日期范围的 SQL 对象重写完全相同的函数。这样做不是更容易吗:

Expression<Func<TObject, bool>> GetDatePeriodOverlapExpression<TObject>(Expression<Func<TObject, DateTime>> StartDate, Expression<Func<TObject, DateTime>> EndDate, DateTime filterStartDate, DateTime filterEndDate)
{
    return x => (StartDate(x) >= filterStartDate && StartDate(x) <= filterEndDate)
                ||
                (EndDate(x)<= filterEndDate && EndDate(x) >= filterStartDate)
                ||
                (StartDate(x) <= filterStartDate && EndDate(x) >= filterEndDate);

}

那么我可以这样称呼它:

var query = GetDatePeriodOverlapExpression<Domain.Course>((x => x.StartDate), (y => y.EndDate), filterStartDate, filterEndDate);

这似乎是个很棒的主意。除了它不起作用。 "Method, delegate or event is expected" 每次调用 StartDate(x)EndDate(x)。据我所知,我必须从头开始创建表达式树,不能使用 LINQ 来创建它。的确,我只需要做一次,但要完成 LINQ 自然应该做的事情似乎需要做很多工作。

我试过使用对 StartDate.Compile()(x) 的调用来编译。但是当我 运行 它时,它似乎比较函数指针而不是函数的结果。

有没有办法将表达式插入到 LINQ 语句中?

您可以像这样动态构建表达式树:

Expression<Func<TObject, bool>> GetDatePeriodOverlapExpression<TObject>(Expression<Func<TObject, DateTime>> StartDate, Expression<Func<TObject, DateTime>> EndDate, DateTime filterStartDate, DateTime filterEndDate)
{
    var parameter = Expression.Parameter(typeof(TObject));


    var e1 = Expression.GreaterThanOrEqual(ParameterRebinder.ReplaceParameters(parameter, StartDate.Body), Expression.Constant(filterStartDate)); // StartDate(x) >= filterStartDate
    var e2 = Expression.LessThanOrEqual(ParameterRebinder.ReplaceParameters(parameter, StartDate.Body), Expression.Constant(filterEndDate)); // StartDate(x) <= filterEndDate

    var e3 = Expression.AndAlso(e1, e2); // (StartDate(x) >= filterStartDate && StartDate(x) <= filterEndDate)

    var e4 = Expression.LessThanOrEqual(ParameterRebinder.ReplaceParameters(parameter, EndDate.Body), Expression.Constant(filterEndDate)); // EndDate(x) <= filterEndDate
    var e5 = Expression.GreaterThanOrEqual(ParameterRebinder.ReplaceParameters(parameter, EndDate.Body), Expression.Constant(filterStartDate)); // EndDate(x) >= filterStartDate

    var e6 = Expression.AndAlso(e4, e5); // (EndDate(x) <= filterEndDate && EndDate(x) >= filterStartDate)

    var e7 = Expression.LessThanOrEqual(ParameterRebinder.ReplaceParameters(parameter, StartDate.Body), Expression.Constant(filterStartDate)); // StartDate(x) <= filterStartDate
    var e8 = Expression.GreaterThanOrEqual(ParameterRebinder.ReplaceParameters(parameter, EndDate.Body), Expression.Constant(filterEndDate)); // EndDate(x) >= filterEndDate

    var e9 = Expression.AndAlso(e7, e8); // (StartDate(x) <= filterStartDate && EndDate(x) >= filterEndDate)

    var e10 = Expression.OrElse(Expression.OrElse(e3, e6), e9);

    return Expression.Lambda<Func<TObject, bool>>(e10, parameter);
}  

public class ParameterRebinder : ExpressionVisitor 
{ 
    private readonly ParameterExpression parameter; 


    public ParameterRebinder(ParameterExpression parameter) 
    { 
        this.parameter = parameter; 
    } 

    public static Expression ReplaceParameters(ParameterExpression parameter, Expression exp) 
    { 
        return new ParameterRebinder(parameter).Visit(exp); 
    } 

    protected override Expression VisitParameter(ParameterExpression p) 
    { 
        return base.VisitParameter(parameter); 
    } 
}

编辑:当我运行你的初始代码时,我得到了一个错误"An item with the same key has already been added."我已经用那个修复更新了代码,并修复了一个副本-在逻辑中粘贴问题。 - @ErikAllen

您可以创建一个界面:

public interface IDateSearchable
{
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
}

然后在所有要使用表达式的 classes 中实现接口。您的表达式函数将变为:

Expression<Func<IDateSearchable, bool>> GetDatePeriodOverlapForCourseExpression(DateTime filterStartDate, DateTime filterEndDate)
{
    return x => (x.StartDate >= filterStartDate && x.StartDate <= filterEndDate)
                ||
                (x.EndDate <= filterEndDate && x.EndDate >= filterStartDate)
                ||
                (x.StartDate <= filterStartDate && x.EndDate >= filterEndDate);
}

因为您的 Getperiodoverlap... 方法 returns 一个接受 IDateSearchable 作为参数的函数,它将在任何实现该接口的 class 上工作。

那么你的class就是

public class SomeClassName : IDateSearchable
{
    public DateTime StartDate { get; set; } 
    public DateTime EndDate { get; set; }

    // all your other stuff
}