结合LINQ的表达式

时间:2016-05-27 13:13:21

标签: c# linq lambda expression

我下面有一个很大的代码块,实际的代码并不重要,但是为了证明我尝试做的事情而包含在内。

我有一个公共静态表达式> GetFreeSpotCount(...)

此方法返回一个表达式,我想在另一个方法中重用

表达式本身返回一个数字,表示符合某些标准的[model]列表中[model]的数量。

我希望新方法返回上面的数字大于0的所有[模型]。

下面是我目前的代码,我想避免重复这么多代码

public static Expression<Func<ExamTimeSlot, int>> GetFreeSpotCountFor(List<Guid> drivingSchoolIds)
{
    return ets =>
    ets.Participants
        - ets.Exams.Where(ex => ex.Status == ExamStatus.Pending).Count(e => !ets.ExamTimeSlotReservations.Any(r => r.DrivingSchoolId == e.BookedByDrivingSchoolId))
        - ((int?)ets.ExamTimeSlotReservations.Sum(r => (ets.Exams.Where(ex => ex.Status == ExamStatus.Pending).Where(e => e.BookedByDrivingSchoolId == r.DrivingSchoolId).Count()
            - r.ReservedSpots) > 0 ? (ets.Exams.Where(ex => ex.Status == ExamStatus.Pending).Where(e => e.BookedByDrivingSchoolId == r.DrivingSchoolId).Count() - r.ReservedSpots) : 0) ?? 0)
        - ((int?)ets.ExamTimeSlotReservations.Sum(r => r.ReservedSpots) ?? 0)
        + ((((int?)ets.ExamTimeSlotReservations.Where(r => drivingSchoolIds.Any(id => r.DrivingSchoolId == id)).Sum(r => r.ReservedSpots) ?? 0)
            - ets.Exams.Where(ex => drivingSchoolIds.Any(id => ex.BookedByDrivingSchoolId == id) && ex.Status == ExamStatus.Pending).Count()) >= 0 ?
                (((int?)ets.ExamTimeSlotReservations.Where(r => drivingSchoolIds.Any(id => r.DrivingSchoolId == id)).Sum(r => r.ReservedSpots) ?? 0)
                - ets.Exams.Where(ex => drivingSchoolIds.Any(id => ex.BookedByDrivingSchoolId == id) && ex.Status == ExamStatus.Pending).Count())
                : 0);
}

public static Expression<Func<ExamTimeSlot, bool>> GetExamTimeSlotsWithFreeSpotsFor(List<Guid> drivingSchoolIds)
{
    return ets =>
    (ets.Participants
        - ets.Exams.Where(ex => ex.Status == ExamStatus.Pending).Count(e => !ets.ExamTimeSlotReservations.Any(r => r.DrivingSchoolId == e.BookedByDrivingSchoolId))
        - ((int?)ets.ExamTimeSlotReservations.Sum(r => (ets.Exams.Where(ex => ex.Status == ExamStatus.Pending).Where(e => e.BookedByDrivingSchoolId == r.DrivingSchoolId).Count()
            - r.ReservedSpots) > 0 ? (ets.Exams.Where(ex => ex.Status == ExamStatus.Pending).Where(e => e.BookedByDrivingSchoolId == r.DrivingSchoolId).Count() - r.ReservedSpots) : 0) ?? 0)
        - ((int?)ets.ExamTimeSlotReservations.Sum(r => r.ReservedSpots) ?? 0)
        + ((((int?)ets.ExamTimeSlotReservations.Where(r => drivingSchoolIds.Any(id => r.DrivingSchoolId == id)).Sum(r => r.ReservedSpots) ?? 0)
            - ets.Exams.Where(ex => drivingSchoolIds.Any(id => ex.BookedByDrivingSchoolId == id) && ex.Status == ExamStatus.Pending).Count()) >= 0 ?
                (((int?)ets.ExamTimeSlotReservations.Where(r => drivingSchoolIds.Any(id => r.DrivingSchoolId == id)).Sum(r => r.ReservedSpots) ?? 0)
                - ets.Exams.Where(ex => drivingSchoolIds.Any(id => ex.BookedByDrivingSchoolId == id) && ex.Status == ExamStatus.Pending).Count())
                : 0)) > 0;
}

我想做类似的事情:

public static Expression<Func<ExamTimeSlot, bool>> GetExamTimeSlotsWithFreeSpotsFor(List<Guid> drivingSchoolIds)
{
    return ets => GetFreeSpotCountFor(drivingSchoolIds) > 0;
}

我已尝试将Expression.GreaterThan与第一个表达式一起使用,但由于我需要结果AND [model],我无法找到使其工作的方法。

2 个答案:

答案 0 :(得分:1)

实际上没有更简单的方法(没有第三方库)。似乎我们应该能够轻松地完成你所写的内容,但是由于我们在创建表达式树时依赖的语法糖,我们会误导。

public static Expression<Func<ExamTimeSlot, bool>> GetExamTimeSlotsWithFreeSpotsFor(List<Guid> drivingSchoolIds)
{
    var param = Expression.Parameter(typeof(ExamTimeSlot));

    //We want to create a new lambda which will invoke `GetFreeSpotCountFor` with our parameter, and then check it's greater than 0
    var newBody = 
        Expression.GreaterThan(
            //View this as GetFreeSpotCountFor(drivingSchoolIds)(param) - where param will be given to us when this lambda is invoked
            Expression.Invoke(
                GetFreeSpotCountFor(drivingSchoolIds),
                param
            ),
            //Pass the right-hand value (0) to the GreaterThan check
            Expression.Constant(0)
        );

    var lambda = Expression.Lambda<Func<ExamTimeSlot, bool>>(newBody, param);
    return lambda;
}

一旦我们得到Expression对象,我们需要手动构建表达式树。

上面将你的lambda包装在一个新的lambda中,该lambda调用并将结果与​​0进行比较。另一种方法是检查现有的lambda .Body,并将.GreaterThan附加到它,然后返回一个全新的lambda。

即:

public static Expression<Func<ExamTimeSlot, bool>> GetExamTimeSlotsWithFreeSpotsFor(List<Guid> drivingSchoolIds)
{
    //This grabs the existing lambda, which we will work on
    var oldLambda = GetFreeSpotCountFor(drivingSchoolIds);
    var newBody = 
        //Invoke `GreaterThan` directly on the old lambda's Body
        Expression.GreaterThan(
            oldLambda.Body,
            //Pass the right-hand value (0) to the GreaterThan check
            Expression.Constant(0)
        );

    //Now, we need to pass in the old parameters, and build a new lambda.
    var lambda = Expression.Lambda<Func<ExamTimeSlot, bool>>(newBody, oldLambda.Parameters);
    return lambda;
}

答案 1 :(得分:0)

为什么你特意想要组合表达式而不是组合查询调用?一种简单的方法是让你的GetExamTimeSlotsWithFreeSpots不接受并返回一个表达式但是接受并返回一个查询,并在该查询中添加两个where子句(每个子句都是你存储的表达式)。

  public static IQueryable<ExamTimeSlot> WhereExamTimeSlotsWithFreeSpotsFor (IQueryable<ExamTimeSlot> Source, List<Guid> drivingSchoolIds)
  {
       var FirstFilter = GetFreeSpotCountFor(drivingSchoolIds);
       var SecondFilter = GetExamTimeSlotsWithFreeSpotsFor(drivingSchoolIds);
       return Source
              .Where(FirstFilter)
              .Where(SecondFilter);                   
  }

这样你就不需要组合任何东西,让你的LINQ提供者自己组合两个where子句。

请注意,如果你真的想手动组合表达式,那么就可以了,但据我所知,它需要第三方API或直接使用表达式树(不仅仅是享受编译器魔术为你做的事)。如果它只是返回一个带有2个表达式的表达式并且在它们之间抛出一个和运算符,但这对于相同的结果来说似乎有点过分并不难。