为什么 DbSet.Add 会更改其他实体的属性?
Why does DbSet.Add change properties of other entities?
我有一个罕见的情况,即 DbSet<T>.Add()
的调用更改了 DbSet<T>
中已有的其他实体的某些属性。不幸的是,它很少发生,我唯一的证据是一些日志文件,所以我还不能在本地重现它。
行为是这样的:
- 首先,我们使用 LINQ 查询从
DbSet
加载一些实体。
- 然后,其中一些实体发生了变化。还没有
SaveChanges()
。
- 现在我们通过调用
DbSet<T>.Add()
添加一些实体。
步骤 2 的一些实体在步骤 3 中被更改(其中一个外键 属性 设置为 null
)。
有什么想法吗?在 EF 6 Code-First 模型上会发生这种情况吗?
我能想到的唯一可能是 DbContext
从数据库中刷新一些数据,但我们现在不希望它这样做。
编辑: 目前代码中散布着日志语句,因为几周以来我们一直在追查这个错误。这些是相关的代码部分:
// parameter: List<Entry> entriesFromUser
var entriesFromDb = db.Entries
.Where(...)
.OrderBy(...)
.ToList();
var newEntries = MergeEntries(entriesFromDb, entriesFromUser);
var propertyBefore = entriesFromDb[0].MyForeignKeyId;
for (var i = 0; i < newEntries.Count; i++)
{
// make sure that the "new entry" is not a modified one
if (entriesFromDb.Contains(newEntries[i])
{
throw new Exception();
}
db.Entries.Add(newEntries[i]);
}
var propertyAfter = entriesFromDb[0].MyForeignKeyId;
Debug.Assert(propertyBefore == propertyAfter); // <=== fails sometimes
db.SaveChanges();
请注意,更改的外键在添加到 DbSet
的实体上 NOT。它位于来自数据库的实体上,但已在同一事务中更改。
哦。找到原因了。希望对其他人有帮助。
我们使用的是Foreign Key Associations,也就是说我们既有导航属性entry.MyForeignKey
,也有外键属性entry.MyForeignKeyId
,有很多优点,但这也意味着您在有时使用 this,有时使用 that 属性.
时必须小心
原来我们在代码深处的某处进行了以下分配,其中一个条目的所有数据都被复制到另一个条目:
entry.MyForeignKeyId = otherEntry.MyForeignKeyId
entry.MyForeignKey = otherEntry.MyForeignKey
然而,在许多情况下,您将外键值设置为实体的 MyForeignKeyId
但将 属性 MyForeignKey
保留为 null,因为未加载父实体。这很好,只要您不 将 null 分配给实体的 MyForeignKey
属性,因为 EF 似乎随后会将 MyForeignKeyId
设置为也为空。
因此,在我们的代码将 null 分配给 MyForeignKey
之后,该实体似乎在内存中逗留了一个 null MyForeignKey
和一个非 null MyForeignKeyId
。一旦下一个 DbSet
命令被执行(Add()
操作),DbSet
注意到 MyForeignKey
已经收到一个 null
赋值,所以 DbSet
继续并将 null
分配给 MyForeignKeyId
。
我有一个罕见的情况,即 DbSet<T>.Add()
的调用更改了 DbSet<T>
中已有的其他实体的某些属性。不幸的是,它很少发生,我唯一的证据是一些日志文件,所以我还不能在本地重现它。
行为是这样的:
- 首先,我们使用 LINQ 查询从
DbSet
加载一些实体。 - 然后,其中一些实体发生了变化。还没有
SaveChanges()
。 - 现在我们通过调用
DbSet<T>.Add()
添加一些实体。
步骤 2 的一些实体在步骤 3 中被更改(其中一个外键 属性 设置为 null
)。
有什么想法吗?在 EF 6 Code-First 模型上会发生这种情况吗?
我能想到的唯一可能是 DbContext
从数据库中刷新一些数据,但我们现在不希望它这样做。
编辑: 目前代码中散布着日志语句,因为几周以来我们一直在追查这个错误。这些是相关的代码部分:
// parameter: List<Entry> entriesFromUser
var entriesFromDb = db.Entries
.Where(...)
.OrderBy(...)
.ToList();
var newEntries = MergeEntries(entriesFromDb, entriesFromUser);
var propertyBefore = entriesFromDb[0].MyForeignKeyId;
for (var i = 0; i < newEntries.Count; i++)
{
// make sure that the "new entry" is not a modified one
if (entriesFromDb.Contains(newEntries[i])
{
throw new Exception();
}
db.Entries.Add(newEntries[i]);
}
var propertyAfter = entriesFromDb[0].MyForeignKeyId;
Debug.Assert(propertyBefore == propertyAfter); // <=== fails sometimes
db.SaveChanges();
请注意,更改的外键在添加到 DbSet
的实体上 NOT。它位于来自数据库的实体上,但已在同一事务中更改。
哦。找到原因了。希望对其他人有帮助。
我们使用的是Foreign Key Associations,也就是说我们既有导航属性entry.MyForeignKey
,也有外键属性entry.MyForeignKeyId
,有很多优点,但这也意味着您在有时使用 this,有时使用 that 属性.
原来我们在代码深处的某处进行了以下分配,其中一个条目的所有数据都被复制到另一个条目:
entry.MyForeignKeyId = otherEntry.MyForeignKeyId
entry.MyForeignKey = otherEntry.MyForeignKey
然而,在许多情况下,您将外键值设置为实体的 MyForeignKeyId
但将 属性 MyForeignKey
保留为 null,因为未加载父实体。这很好,只要您不 将 null 分配给实体的 MyForeignKey
属性,因为 EF 似乎随后会将 MyForeignKeyId
设置为也为空。
因此,在我们的代码将 null 分配给 MyForeignKey
之后,该实体似乎在内存中逗留了一个 null MyForeignKey
和一个非 null MyForeignKeyId
。一旦下一个 DbSet
命令被执行(Add()
操作),DbSet
注意到 MyForeignKey
已经收到一个 null
赋值,所以 DbSet
继续并将 null
分配给 MyForeignKeyId
。