FluentValidation 在列表中查找重叠的日期时间

FluentValidation to find overlaping datetimes within a list

我想知道是否有人可以帮助我,因为我只是在学习。我正在尝试使用 FluentValidation 来验证日期时间列表,以提高我的技能,但我似乎无法解决问题,而且我似乎可以找到我正在尝试做的事情的例子。基本上,我想做的是:

  1. 检查开始是否在结束之前(好的)
  2. 检查开始时间和时间是否不相等(好)
  3. 检查在同一天没有重叠的开始和结束。 (做不到)

如果有人能帮助我,我将不胜感激。下面的代码

public class Room
{
    public DateTime RoomBooked { get; set; }

    public List<RoomSchedule> Schedule { get; set; }
}


public class RoomSchedule
{
    public DateTime Start { get; set; }
    public DateTime End { get; set; }
}


public class RoomValidator : AbstractValidator<Room>
{
    public RoomValidator()
    {
        RuleFor(o => o.RoomBooked)
                .NotEmpty().WithMessage("Booking can not be empty");

        RuleForEach(x => x.Schedule)
                .SetValidator(new RoomScheduleValidator());
    }
}


public class RoomScheduleValidator : AbstractValidator<RoomSchedule>
{
    public RoomScheduleValidator()
    {
        RuleFor(o => o.Start)
            .NotEmpty().WithMessage("Start time required.")
            .Equal(m => m.End).WithMessage("Start time can not be the same as the end time.");

        RuleFor(m => m.End)
            .NotEmpty().WithMessage("End time required.")
            .GreaterThan(m => m.Start)
            .WithMessage("End time can not be before start time.");
    }
}

有几种方法可以做到这一点。

首先,FV doco 建议从 Must 扩展开始做这种事情。对于你的情况,你会为你的 RoomValidator

做这样的事情
RuleFor(o => o.Schedule).Must(schedule =>
    {
        if (schedule == null || !schedule.Any())
        {
            return true;
        }

        return schedule.All(item => !schedule.Where(x => !ReferenceEquals(item, x)).Any(x => x.Start < item.End && x.End > item.Start));
    })
    .WithMessage("Schedule can not have overlapping times.");

如果房间时间表列表中有任何日期时间重叠,这将给您一条错误消息。

如果您想更好地控制错误消息;如果您想打印出重叠的时间表,请使用 Custom 扩展名。

RuleFor(o => o.Schedule).Custom((schedule, context) =>
{
    if (schedule == null || !schedule.Any())
    {
        return;
    }

    foreach (var item in schedule)
    {
        var scheduleOverlapsAnotherSchedule = schedule.Where(x => !ReferenceEquals(item, x)).Any(x => x.Start < item.End && x.End > item.Start);
        if (scheduleOverlapsAnotherSchedule)
        {
            context.AddFailure($"Schedule {item.Start.ToShortTimeString()}-{item.End.ToShortTimeString()} overlaps another schedule.");
        }
    }
});

您可以通过将检查的核心转移到另一种方法中使测试更易于阅读,或者如果您一遍又一遍地进行相同类型的检查,则可以使用可重用的 属性 验证器来进一步改进.我不打算在这里介绍它们,因为这不是所要求的,但是它们在上面 link.

可以找到上述规则的工作示例 here

此外,我会考虑将您的房间时间表验证器从开始 属性 更改为 NotEqual,因为看起来您希望在开始等于结束时触发该验证错误; Equal 扩展只会在它们不相等时触发验证消息。这就像一个断言,如果它们相等则一切正常,否则 return 验证错误。 Equal 扩展的 Doco:

/// <summary>
/// Defines an 'equals' validator on the current rule builder using a lambda to specify the comparison value.
/// Validation will fail if the value returned by the lambda is not equal to the value of the property.
/// </summary>

编辑:对于重叠检查,我假设时间表之间的开始和结束时间可以相同,例如,时间表 1 结束 == 下午 1 点 && 时间表 2 开始 == 下午 1 点不是重叠。将比较更改为使用 <= 和 >= 以完全不允许重叠。