【问题标题】:Entity Framework validation with partial updates带有部分更新的实体框架验证
【发布时间】:2012-10-04 00:37:37
【问题描述】:

我正在使用带有 DbContext 和 POCO 实体的 Entity Framework 5.0。有一个包含 3 个属性的简单实体:

public class Record
{
    public int Id { get; set; }
    public string Title { get; set; }
    public bool IsActive { get; set; }
}

Title 字段始终未修改,UI 只是显示它,而不提供任何输入框来修改它。这就是为什么在将表单发送到服务器时将Title 字段设置为null

这是我告诉 EF 执行实体的部分更新的方法(仅限IsActive 字段):

public class EFRepository<TEntity>
{
   ...
   public void PartialUpdate(TEntity entity, params Expression<Func<TEntity, object>>[] propsToUpdate)
   {
       dbSet.Attach(entity);
       var entry = _dbContext.Entry(entity);
       foreach(var prop in propsToUpdate)
           contextEntry.Property(prop).IsModified = true;
   }
}

和电话:

repository.PartialUpdate(updatedRecord, r => r.IsActive);

调用SaveChanges 方法,我得到DbEntityValidationException,它告诉我,Title 是必需的。当我设置dbContext.Configuration.ValidateOnSaveEnabled = false 时,一切正常。 有什么方法可以避免禁用整个上下文的验证并告诉 EF 不要验证未更新的属性? 提前致谢。

【问题讨论】:

  • 这似乎需要做很多工作,否则很容易处理。您只需在表单中包含一个包含只读模型项的隐藏字段,然后它们会包含在更新中,EF 会进行更改跟踪并知道值没有更改。
  • 存根实体比呢?例如,我有一个将实体标记为已删除的 Action 方法。以下代码:var person = new Person { Id = 5 }; dbSet.Attach(person); dbSet.Entry(person).Property(p =&gt; p.IsDeleted).IsModified = true; dbContext.SaveChanges(); 将导致相同的异常。 DbContext 验证是否完全适用于存根实体?我想避免从数据库中检索整个实体只是为了将其标记为已删除。
  • 真的有用吗?当您手头有一个实体并且您确定该实体存在于数据库中并且是相同的时,使用附加。您附加的实体与商店中的不同(标题不同)。然后将实体标记为已修改(通过标记要修改的属性)。由于 EF 对实体而不是属性进行操作,因此它将更新所有属性,而不仅仅是标记为已修改的属性。我的 EF 心智模型告诉我,在此之后数据库中的 Title 列将设置为 null。你能检查一下是不是这样吗?
  • 默认情况下验证会验证处于已添加和已修改状态的实体的所有属性。可以通过使用验证公开的扩展机制来更改此行为。请注意,一旦您将附加的实体标记为已修改,它就不再是存根实体,因为我相信它将被发送到数据库......
  • EF 允许对实体进行部分更新。例如,dbSet.Attach(entity); dbContext.Entry(entity).State = EntityState.Modified; dbContext.SaveChanges(); 将更新整个实体。如果您明确告诉 EF,要更新哪些属性,则只会更新这些属性。 dbSet.Attach(entity); dbContext.Entry(entity).Property(e =&gt; e.Title).IsModified = true; dbContext.SaveChanges(); 将仅更新标题。禁用验证后,这很有效。

标签: entity-framework validation updates


【解决方案1】:

如果您使用部分更新或存根实体(这两种方法都非常有效!)您不能使用全局 EF 验证,因为it doesn't respect your partial changes - 它总是验证整个实体。使用默认验证逻辑,您必须通过调用提及的方法将其关闭:

dbContext.Configuration.ValidateOnSaveEnabled = false

并分别验证每个更新的属性。这应该有希望发挥作用,但我没有尝试,因为我根本不使用 EF 验证:

foreach(var prop in propsToUpdate) {
    var errors = contextEntry.Property(prop).GetValidationErrors();
    if (erros.Count == 0) {
        contextEntry.Property(prop).IsModified = true;
    } else {
        ...
    }
}

如果您想更进一步,您可以尝试在您的上下文中覆盖 ValidateEntity 并重新实现验证,以验证整个实体或仅基于实体状态和 IsModified 属性状态的选定属性 - 即将允许您使用带有部分更新和存根实体的 EF 验证。

EF 中的验证是恕我直言的错误概念 - 它在逻辑不属于的数据访问层中引入了额外的逻辑。它主要基于这样的想法,即如果您在导航属性上放置所需的验证规则,您始终使用整个实体甚至整个实体图。一旦您违反了这种方法,您总会发现硬编码到您的实体的单个固定验证规则集是不够的。

我在很长的待办事项中的一件事是调查验证如何影响 SaveChanges 操作的速度 - 我曾经在 EF4(EF4.1 之前)中基于 DataAnnotations 及其 @987654327 拥有自己的验证 API @class,由于性能非常差,我很快就停止使用它。

使用本机 SQL 的解决方法与使用存根实体或关闭验证的部分更新具有相同的效果 = 您的实体仍未验证,但此外您的更改不是同一工作单元的一部分。

【讨论】:

  • 即使您禁用实体验证,也并非一切正常。例如,我的属性值应保持不变,但是当我没有设置显式值时,这些道具会获得所有隐式默认值,例如整数的 0。结果是让它真正起作用,你必须使用 RAW Sql EF 6.x 没有针对这种情况的解决方法或解决方案
  • @Pascal:如果它们的值未更改,您应该填充该值(因此不会使用默认值),或者您应该使用部分更新并且不要将它们传递到数据库的更新中。
  • 选项 1:我不想填充该值,因为我必须再次访问数据库,以获取我必须更新的每个实体选项 2:我不明白您能否显示一些代码?或解释更多?我使用 property.IsModified = true 的部分更新;这里还缺少什么?
  • @Pascal:如果您不想将这些默认值发送回数据库,我的意思是不要将这些属性标记为已修改。
  • 但我没有将这些属性标记为已修改。我只是设置了一个属性。IsModified = true 不多。
【解决方案2】:

关于Ladislav's answer,我已将此添加到DbContext 类中,现在它会删除所有未修改的属性。
我知道它并没有完全跳过对这些属性的验证,而只是省略了它,但是 EF 验证的是每个实体而不是属性,并且重新重写整个验证过程对我来说太麻烦了。

protected override DbEntityValidationResult ValidateEntity(
  DbEntityEntry entityEntry,
  IDictionary<object, object> items)
{
  var result = base.ValidateEntity(entityEntry, items);
  var falseErrors = result.ValidationErrors
    .Where(error =>
    {
      if (entityEntry.State != EntityState.Modified) return false;
      var member = entityEntry.Member(error.PropertyName);
      var property = member as DbPropertyEntry;
      if (property != null)
        return !property.IsModified;
      else
        return false;//not false err;
    });

  foreach (var error in falseErrors.ToArray())
    result.ValidationErrors.Remove(error);
  return result;
}

【讨论】:

  • 谢谢!这是我发现的最优雅的方式,但我建议在Where 中添加此条件if (entityEntry.State != EntityState.Modified) return false;,确保仅在更新实体时删除 falseErrors,否则在添加验证时将全部删除,只是因为没有设置标志IsModified
  • 我只是添加了一个 if (entityEntry.State == EntityState.Modified) 包装 var falseErrorsforeach 以防止 Michael Denny 描述的行为。比在 Linq 的查询中这样做更好。
  • 嘿 Shimmy,为这个 sn-p 欢呼。想知道您是否知道 EF6.1 是否仍需要这样做?我刚刚检查了一下,EF6 到 2013 年就已经发布了,但自去年 4 月以来,似乎 EF 方面可以改进一些东西?
  • @MichaelDenny 我补充说。我不再使用这个了,所以我不知道它是否会起作用。
【解决方案3】:

这是以前@Shimmy 响应的混音,是我目前使用的版本。

我添加的是Where中的子句(entityEntry.State != EntityState.Modified) return false;

protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
    var result = base.ValidateEntity(entityEntry, items);

    var falseErrors = result
        .ValidationErrors
        .Where(error =>
        {
            if (entityEntry.State != EntityState.Modified) return false;
            var member = entityEntry.Member(error.PropertyName);
            var property = member as DbPropertyEntry;
            if (property != null) return !property.IsModified;
            return false;
        });

    foreach (var error in falseErrors.ToArray())
    {
        result.ValidationErrors.Remove(error);
    }

    return result;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-09-16
    • 2022-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-17
    相关资源
    最近更新 更多