实体更新其列,但不更新具有外键的列

本文关键字:更新 实体 | 更新日期: 2023-09-27 18:01:08

我有一个正在修改然后保存的实体。有两个属性,并且都已更改。我已经验证了两者都不是null,并设置为我想要的值。然而,当我在数据库中显示它时,我可以看到只有第一个发生了变化(列Some(,而另一个保持不变(列ThingId

public class Holder
{
  public string Some { get; set; }
  public Thing Thing { get; set; }
}
public class Thing
{
  public int Id { get; set; }
  public string Name { get; set; }
  public virtual ICollection<Holder> Holders { get; set; }
}

存储方法如下。

model.Holders.Attach(holder);
model.Entry(holder).State = EntityState.Modified;
model.SaveChanges();

它们之间的关系是使用Fluent API以以下方式定义的。

builder.Entity<Holder>()
  .HasOptional(_ => _.Thing)
  .WithMany(_ => _.Holders)
  .Map(_ => _.MapKey("ThingId"));

我到底错过了什么?创建新持有者时不会出现此问题。仅当尝试更新时。

实体更新其列,但不更新具有外键的列

当你说时

model.Entry(holder).State = EntityState.Modified;

EF理解holder实体中的标量被修改。

所以现在你想让EF知道"事情"也发生了变化。所以你必须明确地说:

model.Entry(holder.Thing).State = EntityState.Modified;

或者,如果你不想这样做,那么你必须将ThingID的属性添加到你的模型中,并以同样的方式填充它。

你可以通过https://www.safaribooksonline.com/library/view/programming-entity-framework/9781449331825/ch04s03.html用于理解EF中的状态。

处理分离的对象相当棘手
无论如何,一种方法是为外键添加一个属性并对其进行处理(而不仅仅是添加属性(。有关详细信息,请参阅其他答案。如果你想与独立实体合作,我认为这是最好的选择。如果你需要处理n-m关系,它是不起作用的(HasMany-WithMany(。

另一个解决方案是这样的(这也适用于n-m关系(

context.Holders.Attach(holder);
context.Entry(holder).State = EntityState.Modified;

var manager = ((IObjectContextAdapter)context).ObjectContext.ObjectStateManager;
manager.ChangeRelationshipState(holder, holder.Thing, "Thing", EntityState.Added);
context.SaveChanges();

这个解决方案的问题是EF生成一个类似于这个的更新查询

update [Holders]
set [Some] = @p0, [ThingId] = @p1
where (([Id] = @p2) and [ThingId] is null)
@p0 = "Holder updated"
@p1 = 100
@p2 = 200

(查看where子句(

如果您将更新方法更改为这个(更可读(,则会有类似的行为。

var thing = holder.Thing;
holder.Thing = null;
var attachedHolder = context.Holders.Attach(holder);
attachedHolder.Thing = thing;
context.Entry(holder).Property("Some").IsModified = true;
context.SaveChanges();

同样在这种情况下,生成的更新查询是

update [Holders]
set [Some] = @p0, [ThingId] = @p1
where (([Id] = @p2) and [ThingId] is null)
@p0 = "Holder updated"
@p1 = 100
@p2 = 200

因此,在这两种情况下,你都需要知道最初的东西(可以分离(。如果你知道,你可以用这种方式更改你的代码:

var thing = holder.Thing;
holder.Thing = myOldThing; // for example new Thing() {Id = 2} works
var attachedHolder = context.Holders.Attach(holder);
attachedHolder.Thing = thing;
context.Entry(holder).Property("Some").IsModified = true;
context.SaveChanges();

事实上,我还没有读过EF源代码的这一部分,但我认为这种行为与可以管理n-m关系的关系处理程序(HasMany-WithMany(有关。在这种情况下,EF还会为关系生成"支持"表,主键列是两个表的主键列之和。更新此类关系需要在支持表的主键上使用where。

我建议三种可能的方法来解决这个问题。

第一个是从EF收回一些控制,并自己管理连接。基本上,您需要引入携带外键的显式字段,并显式查看这些字段的设置。

第二个是允许EF管理更新,但要非常明确地告诉它如何做。原则上,你必须告诉它将实体视为添加、操纵、删除等。

最后,这是我推荐的方法,因为这是正确的、面向对象的ORM范式方法,是解决EF一开始就不承认实体的问题。在你的课上,我看不到任何关于正在实现的比较方法的信息(有用于等式、哈希控制等的方法(,如果你在谷歌上搜索这些方法,你可能会注意到EF依赖很多好东西,而我们通常对此有点马虎(因为计算机直观地帮助了我们,所以可以逃脱惩罚(。

此外,还有免责声明。我听过很多次(我自己也说过(,在某些情况下,EF是不够的,必须应用两种初始方法中的一种。回顾过去,它总是与其他地方的代码有关(无论是被遗忘的Hash实现、混乱的非唯一ID,还是只是迁移中的一个错误,没有更新它应该更新的地方(。我并不是说这里和任何地方都是这样。我只是想说,除了其他人的证明之外,我没有其他数据表明情况并非如此。