【问题标题】:How to update entities which are modified outside the DbContext?如何更新在 DbContext 之外修改的实体?
【发布时间】:2012-08-31 01:50:38
【问题描述】:

如果实体在 DbContext 之外更改(是一个分离的实体),我在更新实体时会遇到一个小问题。如果我附加修改后的实体,它的状态不会被修改。

我的代码如下所示:

var specificationToSave = GetSpecificationFromTmpStore(userSessionGuid);
using (var context = DataContextFactory.GetDataContext())
{
    // this works for update, if I change the values inside the context while debugging
    // but it breaks with new entities
    context.Specifications.Attach(specificationToSave);

    // this works for insert new entities, modified entities will be saved as new entities
    context.Specifications.Add((specificationToSave);)
    context.SaveChanges();
}

我知道 NHibernate 和它的方法 SaveOrUpdate。 NHibernate 根据值决定它是更新还是插入实体。

使用 EF 4.x 和在 DbContext 之外修改的实体执行此操作的最佳做​​法是什么? 如何告诉 EF 该实体处于修改状态?

【问题讨论】:

    标签: c# entity-framework entity-framework-4 code-first


    【解决方案1】:

    如果您对已更改的实体使用Attach 方法,您还需要在附加后告诉 EF 该实体是modified

    context.Specifications.Attach(entity);
    context.Entry(entity).State = EntityState.Modified;
    context.SaveChanges();
    

    另一种方法是获取(带跟踪),然后更新字段并保存:

    var entity = context.Specifications.First(s => s.Id == 1234);
    entity.Name = "Foo";
    ... other changes here
    context.SaveChanges();
    

    另一种选择是在您重新附加实体之后对其进行更改,例如as per here

    context.Specifications.Attach(entity);
    entity.Name = "Foo";
    ... other changes here
    context.SaveChanges();
    

    编辑

    您可以将泛型与 DbSet(类或方法)一起使用,如下所示:

    public void Update<TEntity>(TEntity entity)
    {
        DbContext.Set<TEntity>().Attach(entity);
        DbContext.Entry(entity).State = EntityState.Modified;
        DbContext.SaveChanges();
     }
    

    编辑:用于更新分离的父/子图

    对于效率和性能不重要的简单/浅层父子关系的更新,简单地删除所有旧子节点并重新插入新子节点是一种简单(尽管丑陋)的解决方案。

    但是,对于更高效的场景,我们需要遍历图,检测更改,然后添加新插入的、更新现有的、忽略未更改的以及从Context 中删除已删除的项目。

    Slauma 展示了一个很好的例子 of this here

    您可能想看看使用GraphDiff,它可以为您完成所有这些腿部工作!

    【讨论】:

    • 非常感谢 :-) 这就是我想要的。第二种方法是最好的方法,但在这种情况下是不可能的。所以我必须直接更新 DbEntityEntry 的状态。不是一个很好的解决方案,但它确实有效。
    • 如果必须设置“specificationToSave”的每个相关实体的状态,是真的吗?例如,如果我的“specificationToSave”中有一个修改项目列表。我真的希望有一个简单的解决方案来做到这一点......
    • @Jürgen parent:子场景需要特别考虑,因为您可能需要添加、删除或更新子场景。 FWIW我通常只是对孩子做一个哈希,看看是否有任何变化,如果有,然后删除所有现有的并再次插入所有孩子。但这只是我懒惰。
    • 感谢通用解决方案。但是是否可以以一种方式附加和修改实体和所有相关实体的状态?我的“specificationToSave”有一些其他实体的列表也被修改了。我真的必须遍历所有子实体才能将状态设置为已修改吗?
    • 您不必告诉 EF 状态已修改并附加它,当您执行 context.Entry(entity).State = EntityState.Modified; 时,您不仅将实体附加到DbContext,您还将整个实体标记为脏,这里进一步解释:stackoverflow.com/questions/30987806/…@StuartLC
    【解决方案2】:

    对于断开连接的实体,我发现了这个solution

    用于查找现有实体的更改:

    var existing = context.Find<Item>(1);
    if (existing != null) 
    { 
       context.Entry(existing).CurrentValues.SetValues(changed);
    }
    

    之后它的 EntityState 将是 Modified,但只有在实际发生变化的地方。

    我在单元/集成测试中所做的完整示例:

    await using var context1 = new MyContext(new DbContextOptionsBuilder().UseSqlite("Data Source=demo.db").Options);
    await context1.Database.EnsureDeletedAsync();
    await context1.Database.EnsureCreatedAsync();
    
    await context1.Items.AddAsync(new Item
    {
        Id = 1,
        Name = "Something to start with"
    });
    
    await context1.SaveChangesAsync();
    
    await using var context2 = new MyContext(new DbContextOptionsBuilder().UseSqlite("Data Source=demo.db").Options);
    var existing = context2.Find<Item>(1);
    
    var entry = context2.Entry(existing);
    entry.CurrentValues.SetValues(new Item
    {
        Id = 1,
        Name = "Something to start with"
    });
    entry.State.Should().Be(EntityState.Unchanged);
    
    entry.CurrentValues.SetValues(new Item
    {
        Id = 1,
        Name = "Updated now."
    });
    entry.State.Should().Be(EntityState.Modified);
    

    使用Microsoft.EntityFrameworkCore.SqliteFluentAssertions

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-06-22
      • 2023-03-23
      • 1970-01-01
      • 1970-01-01
      • 2014-12-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多