【问题标题】:EntityFramework: Update single field with detached entityEntityFramework:使用分离实体更新单个字段
【发布时间】:2011-08-31 18:57:36
【问题描述】:

与正常情况不同,我的代码确实有效,但我想知道这是否是唯一(或最佳方法)。

基本想法是我有一个现有的应用程序,它的手工数据层正在移植到实体框架。作为最小化代码更改的折衷方案,我正在使用现有方法,这些方法往往采用更加不连贯的方法。比如我有很多这样的东西:

UpdateNote(int noteId, string note)

我似乎有一种无需重新获取即可用于此类更新的方法:

var context = new MyEntities();
context.Configuration.ValidateOnSaveEnabled = false;
var note = new Model.Note{ Id = noteId, Note = ""};
context.Notes.Attach(note);
note.Note = "Some Note";
context.SaveChanges();

它有点难看(虽然足够简洁),所以我想知道是否有更好的方法与 EF 一起使用?除了失去内置验证之外,这种方法有什么缺点吗?

这是一个将在我的应用程序中使用的模式。

【问题讨论】:

  • 我有一个更好的扩展方法我会在一点点。它是我从网上获取的 AttachAsModified 的一种变体,在这些场景中效果很好,而无需知道道具名称。
  • 回来看看这个 - 你将失去你的 DataAnnotations 验证(如果这就是你的意思)但是如果你定义了任何实体框架验证(例如在代码中首先使用IsRequired() 方法)

标签: asp.net entity-framework entity-framework-4.1


【解决方案1】:

DbContext 的以下扩展方法是一种避免使用与您想要更改的值不同的值来初始化实体的方法。

public static class EFExtensions
{
    public static void MarkAsModified(this DbContext context, object entity,
        params string[] properties)
    {
        foreach (var property in properties)
            context.Entry(entity).Property(property).IsModified = true;
    }
}

你可以这样使用它:

var context = new MyEntities();
context.Configuration.ValidateOnSaveEnabled = false;

var note = new Model.Note { Id = noteId }; // only key properties required to set

note.Note = "Some Note";
note.SomeOtherProperty = 1234;
note.AndAnotherProperty = "XYZ";

context.Notes.Attach(note);
context.MarkAsModified(note, "Note", "SomeOtherProperty" , "AndAnotherProperty");

context.SaveChanges();

注意:这仅适用于标量属性,不适用于导航属性。

除了验证之外,我可以想象这种方法对于正确的并发检查是有问题的。

编辑

根据@Adam Tuliper 在下面的评论,并发可能不是问题,因为当手动将实体附加到上下文(而不从数据库中读取它)并标记为已修改以将 UPDATE 命令发送到数据库。它只是覆盖数据库中的最新版本。感谢 Adam 指出这一点!

【讨论】:

  • 我明白你在初始化时所说的话,我唯一不喜欢的是使用字符串文字作为属性名称。但是并发检查会有什么问题呢?
  • @cadmium:我实际上不确定这是否有问题。但我的理解是,EF 中的乐观并发检查依赖于存储在数据库中的并发令牌(实体上的某些属性)。当您保存实体(UPDATE 语句)时,会将对象中的并发令牌与数据库中的值进行比较。如果它们不同,您将得到一个乐观的并发异常并需要决定要做什么。如果您不知道最新的并发令牌(当您不从数据库中读取它而只是将新实例附加到上下文时就是这种情况)(...继续...)
  • (...continued...) 您在实体和数据库中的令牌总是不同的,因此您总是会遇到并发冲突。这只是假设,我从实践中对并发检查的详细工作方式了解得不够多。顺便说一句:我不再喜欢我的答案了:属性名称有字符串 = 不利于重构。可以引入一个属性名称提取器来再次进行强类型化。但所有这些反映......不是很好。也许你的手写方式更好,至少更快。
  • @Slauma - 我相信 EF 只会更新 OldValue=KnownOldValye 的记录。如果没有更新记录,则您遇到并发问题并引发异常。如果您通过 Id def 获得有关您正在谈论的内容的链接。喜欢阅读它,但这就是我理解它如何与现场比较一起工作。有趣的是,虽然当我附加一个对象时它确实可以工作,所以如果没有“已知”的原始值并且你附加一个修改后的对象,它就会继续并更新它而无需检查。
  • 我想添加更多关于此的信息,似乎并发以两种不同的方式处理(至少)。我相信一个简单的方法就是在你的表中添加一个时间戳列,并用 [TimeStamp] 属性装饰它的属性。 EF 会自动在它的 where 子句中比较这个值。根据 Contoso 示例的另一种情况:“您必须通过向它们添加 ConcurrencyCheck 属性来标记实体中的所有非主键属性以进行并发跟踪。该更改将使实体框架能够在 SQL 中包含所有列UPDATE 语句的 WHERE 子句。”
【解决方案2】:

假设我们现在要保存它,请参阅以下代码,我使用它可以轻松地将断开连接的对象附加回图表。


public static class EntityFrameworkExtensions
{
    /// <summary>
    /// This class allows you to attach an entity.
    /// For instance, a controller method Edit(Customer customer)
    /// using ctx.AttachAsModified(customer); 
    /// ctx.SaveChanges();
    /// allows you to easily reattach this item for udpating.
    /// Credit goes to: http://geekswithblogs.net/michelotti/archive/2009/11/27/attaching-modified-entities-in-ef-4.aspx
    /// </summary>
    public static void AttachAsModified<T>(this ObjectSet<T> objectSet, T entity) where T : class
    {
        objectSet.Attach(entity);
        objectSet.Context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
    }

    /// <summary>
    /// This marks an item for deletion, but does not currently mark child objects (relationships).
    /// For those cases you must query the object, include the relationships, and then delete.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="objectSet"></param>
    /// <param name="entity"></param>
    public static void AttachAsDeleted<T>(this ObjectSet<T> objectSet, T entity) where T : class
    {
        objectSet.Attach(entity);
        objectSet.Context.ObjectStateManager.ChangeObjectState(entity, EntityState.Deleted);
    }

    public static void AttachAllAsModified<T>(this ObjectSet<T> objectSet, IEnumerable<T> entities) where T : class
    {
        foreach (var item in entities)
        {
            objectSet.Attach(item);
            objectSet.Context.ObjectStateManager.ChangeObjectState(item, EntityState.Modified);
        }
    }
}

【讨论】:

  • 是的,这很容易附加,但它也很昂贵,因为通过将实体状态设置为Modified EF 将向具有 all 标量属性的数据库发送完整的更新命令,不管他们是否真的改变了。这是一种“蛮力”更新。
  • 是的,但不幸的是,在大多数情况下,我们不知道网络上发生了什么变化。我们从一个页面转到一些断开连接的项目。在 MVC 中更是如此,您的模型作为断开连接的项目传入。然后,您需要将其发送到数据库或加载一个对象,然后将模型与该对象合并。在这种情况下,您不仅保存了每个属性,而且还必须进行全部加载。如果您 AttachAsModified,您可以先保存而无需加载。除非您的数据页面接近 8k 限制(或 varchar(max) 项更大,否则我不确定性能是否会成为问题。
  • 我同意。如果您不知道发生了什么变化,这是一种可行的方法。尽管@cadmium 处于他知道哪些属性确实发生变化的情况,但我从这个问题中得到了感觉。但我可能错了。
  • 是的,这种特殊情况是我知道哪些字段已更改并且有一个不完整的实体(一个或两个字段)来重构上下文。我认为没有太多选择,要么我必须在更新之前接受小打击并重新获取实体,要么处理我原始帖子的棘手问题,必要时可能会加入一点 MarkAsModified。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-10
  • 1970-01-01
相关资源
最近更新 更多