首页 > 解决方案 > 查询数据库和未保存的更改以使用 Entity Framework 6 进行验证

问题描述

我有一个使用 Entity Framework 6 Database First的数据层。我的一个实体代表一个时间跨度——它有一个开始日期和一个结束日期。

public class Range
{
    public Guid ID { get; set; }
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
}

我想添加验证以确保Ranges 永远不会有重叠的日期。该应用程序将一次保存添加、修改和删除Range的所有内容。所以我ValidateEntity在我的DbContext. 这是我最终得到的结果,然后意识到我仍然有问题:

protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
    var result = new DbEntityValidationResult(entityEntry, new List<DbValidationError>());
    if (entityEntry.Entity is Range && (entityEntry.State == EntityState.Added || entityEntry.State == EntityState.Modified))
    {
        Range range = entityEntry.Entity as Range;

        if (Ranges.Local.Any(p => range.EndDate >= p.StartDate && range.StartDate <= p.EndDate) ||
            Ranges.Any(p => range.EndDate >= p.StartDate && range.StartDate <= p.EndDate))
        {
            result.ValidationErrors.Add(
                    new System.Data.Entity.Validation.DbValidationError("EndDate", "Cannot overlap another range.")); //Could be StartDate's fault but we just need save to fail
        }
    }

    if (result.ValidationErrors.Count > 0)
    {
        return result;
    }
    else
    {
        return base.ValidateEntity(entityEntry, items);
    }
}

问题是Ranges.Local不查询数据库,并且Ranges查询数据库的 不包括未保存的更改。

例如:如果我Range在数据库中有一个开始 9/1 和结束 9/8,我在内存中修改为开始 8/1 和结束 8/8,并且我还添加了一个新的Range开始 9/6 结束 9/ 12,然后Ranges.Any(p => blockOut.EndDate >= p.StartDate && blockOut.StartDate <= p.EndDate)将返回true为添加Range,因为当前保存的Range重叠。但它实际上是有效的,因为在内存中,这些日期已经更改,并且保存时不会有重叠。

有没有办法查询Local和数据库的组合,即只查询数据库中没有Local的记录?

编辑:

更新了我认为可以响应以下史蒂夫·皮的回答的代码:

//Check Local first because we may be able to invalidate without querying the DB
if (BlockOutPeriods.Local.Any(p => blockOut.ID != p.ID && blockOut.EndDate >= p.StartDate && blockOut.StartDate <= p.EndDate))
{
    result.ValidationErrors.Add(
            new System.Data.Entity.Validation.DbValidationError("EndDate",
            "Block out cannot overlap another block out."));
}
else 
{
    var editedIDs = BlockOutPeriods.Local.Select(p => p.ID);

    //!Contains avoids reading & mapping all records to Range model objects - better?
    if (BlockOutPeriods.Any(p => blockOut.EndDate >= p.StartDate && blockOut.StartDate <= p.EndDate && !editedIDs.Contains(p.ID)))
    {
        result.ValidationErrors.Add(
                new System.Data.Entity.Validation.DbValidationError("EndDate",
                "Block out cannot overlap another block out."));
    }
}

标签: entity-frameworkvalidationentity-framework-6

解决方案


您可以Union在本地状态和数据状态之间使用 a,提供IEqualityComparer匹配 PK 的 a,以便使用本地而不复制数据集。

var result = Ranges.Local
    .Where(p => range.EndDate >= p.StartDate || range.StartDate <= p.EndDate)
    .Union(
        Ranges.Where(p => range.EndDate >= p.StartDate || range.StartDate <= p.EndDate),
        new RangeEqualityComparer())
    .Any(p => p.RangeId != range.RangeId && range.EndDate >= p.StartDate && range.StartDate <= p.EndDate);

这应该:

  • Local拉出可能跨越所需开始/结束日期的任何范围。
  • 与数据库中可能跨越所需日期范围的任何范围联合。
  • 检查是否存在<>涵盖该范围的任何其他记录(我的 ID)。

推荐阅读