将实体添加到另一个实体的子集合时重复.感谢被覆盖的平等?


public class Foo
{
public int Id { get; set; }
public ICollection<Bar> Bars{get;set;}
}
public class Bar
{
public int Id { get; set; }
public int FooId { get; set; }
public override bool Equals(object obj)
{
var bar= obj as Bar;
return bar != null &&
Id == bar.Id;
}
public override int GetHashCode()
{
return 2108858624 + Id.GetHashCode();
}
}
public class SomeClass
{
DbContext _context = ...; // injected via DI
public void AddNewBarToFoo(int fooId)
{
// Option 1
// result: foo.Bars contains two items
Foo foo = _context.Foos
.Where(f => f.id == fooId)
.Include(f => f.Bars)
.FirstOrDefault();
foo.Bars.Add(new Bar());
_context.SaveChanges();
// Option 2
// result: foo.Bars contains one item
Bar bar = new Bar();
bar.FooId = FooId;
_context.Bars.Add(bar);
_context.SaveChanges();
// Option 3:
// Remove _context.SaveChanges() from Option 1
// Option 4:
// Remove the Id comparison from Bar.Equals()
}
}

今天,我注意到使用实体框架核心有些奇怪。在SomeClassAddNewBarToFoo方法中,我想向 Foos 集合添加新的 Bar。最初我使用选项 1,但我注意到,在调用SaveChanges之后,foo。条形将包含两次新的条形图。
我注意到删除对SaveChanges的调用不会添加第二个 Bar 并且仍然正确保留该 Bar。使用选项 2 也可以按预期工作。

我发现这种行为很奇怪,经过一番调查,我发现这样做的原因是我覆盖了 Bar 的Equals方法,并且我使用 Id 来检查相等性。
Equals方法中删除 Id 比较并使用选项 1 按预期工作:Foo.Bars 仅包含一次新 Bar。

我现在实际上有两个问题:

  1. 实体框架两次添加相同栏的原因是什么?
    我想它会将新 Bar 添加到上下文中一次,给它一个 Id,然后再次遇到它(不知何故? (但这个实例仍然有 Id 0,因此根据我的Equals方法,它被认为与第一个不同。

  2. 我在覆盖的Equals方法中使用 Id 是错误的吗?还是在向 Foo 添加新柱的代码中?覆盖Equals是不好的做法吗?
    简而言之:做我试图做的事情的"正确"方法是什么?

这是破坏HashSet<T>与哈希代码的契约的副作用。

如果要将对象放入HashSet(或任何其他基于哈希的容器,包括Dictionary键(中,则必须保证从将对象添加到容器到删除对象期间,不会更改GetHashCode返回的值(既不是object上的值,也不是IEquatable<T>上的值(。

在这里,您添加具有未知Id(即零(的对象,然后 EF 在实际添加到数据库后修改Id- 但这发生在它仍在HashSet内时,破坏了它的合约。

当 EF 检查集合以查看它是否存在时,由于哈希现在不同,因此无法找到它,并将再次添加它。

你要么需要为哈希选择另一个属性,你可以更容易地保证至少在有效上是不可变的(遗憾的是,EF 不会让你以编译器将强制执行的方式声明它(,使用常量哈希,或使用普通集合(例如List<T>( 而不是HashSet<T>

最新更新