繁体   English   中英

在检查实体是否存在于 EF Core 时适当更新实体

[英]Appropriate update of entity while checking if it exists in EF Core

我有以下方法更新实体。 我唯一的分歧是,当提供了一个不存在的 ID 时,我得到了一个严厉的例外。

public bool Update(Thing thing)
{
    Context.Things.Update(thing);
    int result = Context.SaveChanges();

    return result == 1;
}

所以我添加了一个检查来控制抛出的异常(加上一些不错的日志记录和其他便利)。 最终,我计划完全跳过呕吐。

public bool UpdateWithCheck(Thing thing)
{
    Thing target = Context.Things.SingleOrDefault(a => a.Id == thing.Id);
    if (target == null)
        throw new CustomException($"No thing with ID {thing.Id}.");

    Context.Things.Update(thing);
    int result = Context.SaveChanges();

    return result == 1;
}

不,这不起作用,因为实体已经被跟踪。 我有几个选择来处理这个问题。

  1. 更改为Context.Where(...).AsNoTracking()
  2. 在 target 中显式设置更新的字段并保存。
  3. 使用实体状态并篡改跟踪器。
  4. 删除当前并添加新的。

我无法决定哪个是最佳实践。 谷歌搜索给我的默认示例不包含在同一操作中检查预先存在的状态。

只需更改target属性并调用SaveChanges() - 删除更新调用。 我想说的典型使用情况,这些天是输入thing不实际上是一个Thing ,但是是一个ThingViewModelThingDto或携带足够的数据来识别和更新的事情的对象了主题为”其他一些变化,但实际上不是数据库实体”。 在这种情况下,如果手动从 ThingViewModel 更新 Thing 属性的概念让您感到厌烦,您可以查看一个映射器(AutoMapper 可能是最著名的,但还有很多其他的)来为您进行复制,甚至设置您如果您决定将此方法转换为 Upsert,则可以使用新方法

出现异常的原因是因为通过从 Context 加载实体来检查它是否存在,您现在有了一个跟踪引用。 当你去更新分离的引用时,EF 会抱怨一个实例已经被跟踪。

最简单的解决方法是:

public bool UpdateWithCheck(Thing thing)
{
    bool doesExist = Context.Things.Any(a => a.Id == thing.Id);
    if (!doesExist)
        throw new CustomException($"No thing with ID {thing.Id}.");

    Context.Things.Update(thing);
    int result = Context.SaveChanges();

    return result == 1;
}

但是,这种方法有两个问题。 首先,因为我们不知道 DbContext 实例的范围或不能保证方法的顺序,所以在某个时候 DbContext 实例可能已经加载并跟踪了事物的那个实例。 这可能表现为看似间歇性的错误。 防止这种情况的正确方法是:

public bool UpdateWithCheck(Thing thing)
{
    bool doesExist = Context.Things.Any(a => a.Id == thing.Id);
    if (!doesExist)
        throw new CustomException($"No thing with ID {thing.Id}.");

    Thing existing = Context.Things.Local.SingleOrDefault(a => a.Id == thing.Id);
    if (existing != null)
        Context.Entry(existing).State = EntityState.Detached;

    Context.Things.Update(thing);
    int result = Context.SaveChanges();

    return result == 1;
}

这会检查任何已加载实例的本地跟踪缓存,如果找到,则将它们分离。 这里的风险是,任何没有保留在那些被跟踪的引​​用中的修改都将被丢弃,并且任何浮动的、本应附加的引用现在都将被分离。

第二个重要问题是使用Update() 当您将分离的实体传递出去时,您不打算更新的数据可能会被更新。 更新将替换所有列,通常如果客户端可能只需要更新其中的一个子集。 EF 可以配置为在更新之前根据数据库检查实体上的行版本或时间戳,当您的数据库设置为支持它们时(例如快照隔离),这可以帮助防止过时覆盖,但仍然允许意外篡改。

正如您已经发现的那样,更好的方法是避免传递分离的实体,而是使用专用的 DTO。 这避免了关于哪些对象代表视图/消费者状态与数据状态的潜在混淆。 通过将值从 DTO 显式复制到实体,或配置映射器以复制受支持的值,您还可以保护您的系统免受意外篡改和潜在的陈旧覆盖。 这种方法的一个考虑因素是,您应该通过确保您的实体和 DTO 具有要比较的 RowVersion/Timestamp 来保护更新,以避免无条件地用潜在的陈旧数据覆盖数据。 在从 DTO 复制到新加载的实体之前,比较版本,如果匹配,则自您获取和组合 DTO 以来,数据行中没有任何更改。 如果它发生了变化,这意味着自 DTO 被读取以来,其他人已经更新了底层数据行,因此您的修改是针对陈旧数据的。 从那里,采取适当的行动,例如放弃更改、覆盖更改、合并更改、记录事实等。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM