EntityFramework 使用 AsNoTracking 时出现问题
Issue using AsNoTracking with EntityFramework
我正在尝试对某些实体进行深度克隆。 this article 中提到的方法看起来不错,但我 运行 出错了。它建议使用 AsNoTracking() 来检索实体,然后将其重新插入到上下文中,因为它看起来像一个新对象,因此会导致插入。
这是我的代码:
Using ctxt As New ProductionDataEntities
Dim grade = ctxt.Grades.Include(Function(g) g.GradeWidths).AsNoTracking.First
ctxt.Grades.AddObject(grade)
ctxt.SaveChanges()
End Using
但是当我 运行 它时,我得到:
An object with the same key already exists in the ObjectStateManager. The existing object is in the Modified state. An object can only be added to the ObjectStateManager again if it is in the added state.
当我修改 grade.Name 时,它的 EntityState 更改为已修改,暗示它正在被跟踪。
我正在使用 EF5 Db-First。
或者,我尝试通过分离等级然后重新插入来进行克隆,这可行,但不会复制 GradeWidths。一旦我调用 detach,等级宽度计数就会从 2 变为 0。
问题:
- 知道为什么 AsNoTracking 不起作用吗?我可以做些什么来解决这个问题?
- 或者是否有人可以推荐另一种方法来进行简单的深度克隆?
谢谢。
---- 附加信息----
我有 5 个一对多关系,最终我想从顶层一直向下克隆。但我将其简化为仅查看最低级别。
- 1 个等级到多个等级宽度
没有模型很难理解确切的行为(在这种情况下主键很重要)。
另外,我没有使用 EF 5,而是使用 EF 6,它应该非常相似。
在您的应用中,您正在从数据库中读取具有相关实体 (GradeWidths) 的实体,您需要将其再次添加到数据库中。
我想你的模型是 1 Grade n GradeWidths 并且你正在创建从原始 Grade 复制的 GradeWidths 的新实例(你的模型可以是 n-m,在这种情况下你可以在更多 Grades 上使用 1 GradeWidth 但我想你不是)。
在这种情况下,要添加新实体,您需要重置所有 ID、Grade 和 GradeWidths。
然后,您有一个 "clean" 克隆,您可以将其添加到 DbSet(添加,而不是附加)。
using (var context = new MyContext(connection))
{
context.Grades.Add(new Grade()
{
GradeWidths = new List<GradeWidth>(new[]
{
new GradeWidth() {Width = 10},
new GradeWidth() {Width = 20},
new GradeWidth() {Width = 30}
})
});
context.SaveChanges();
}
using (var context = new MyContext(connection))
{
Grade grade = context.Grades.Include(g => g.GradeWidths).AsNoTracking().First();
// We need to reset all the ids
grade.Id = 0;
foreach (GradeWidth gradeWidth in grade.GradeWidths)
gradeWidth.Id = 0;
context.Grades.Add(grade);
context.SaveChanges();
}
using (var context = new MyContext(connection))
{
Debug.Assert(context.Grades.Count() == 2);
Debug.Assert(context.GradeWidths.Count() == 6);
}
使用 ObjectContext
使用ObjectContext
,您可以先附加父对象,然后将对象状态更改为已添加。同样对于每个子对象,将对象状态更改为已添加。你甚至不需要 AsNoTracking()
.
例如,对于 Category(1)↔(N)Product
关系,我使用了以下代码:
Using db As New SampleSystemEntities
Dim c = db.Categories.Include(Function(x) x.Products).First
db.Attach(c)
db.ObjectStateManager.ChangeObjectState(c, EntityState.Added)
For Each p As Product In c.Products
db.ObjectStateManager.ChangeObjectState(p, EntityState.Added)
Next
db.SaveChanges()
End Using
使用 DbContext
如果您使用 DbContext
使用您的代码一切都会正常工作:
Using db As New SampleSystemEntities
Dim c = db.Categories.Include(Function(x) x.Products).AsNoTracking().First
db.Categories.Add(c)
db.SaveChanges()
End Using
我正在尝试对某些实体进行深度克隆。 this article 中提到的方法看起来不错,但我 运行 出错了。它建议使用 AsNoTracking() 来检索实体,然后将其重新插入到上下文中,因为它看起来像一个新对象,因此会导致插入。
这是我的代码:
Using ctxt As New ProductionDataEntities
Dim grade = ctxt.Grades.Include(Function(g) g.GradeWidths).AsNoTracking.First
ctxt.Grades.AddObject(grade)
ctxt.SaveChanges()
End Using
但是当我 运行 它时,我得到:
An object with the same key already exists in the ObjectStateManager. The existing object is in the Modified state. An object can only be added to the ObjectStateManager again if it is in the added state.
当我修改 grade.Name 时,它的 EntityState 更改为已修改,暗示它正在被跟踪。
我正在使用 EF5 Db-First。
或者,我尝试通过分离等级然后重新插入来进行克隆,这可行,但不会复制 GradeWidths。一旦我调用 detach,等级宽度计数就会从 2 变为 0。
问题:
- 知道为什么 AsNoTracking 不起作用吗?我可以做些什么来解决这个问题?
- 或者是否有人可以推荐另一种方法来进行简单的深度克隆?
谢谢。
---- 附加信息----
我有 5 个一对多关系,最终我想从顶层一直向下克隆。但我将其简化为仅查看最低级别。
- 1 个等级到多个等级宽度
没有模型很难理解确切的行为(在这种情况下主键很重要)。
另外,我没有使用 EF 5,而是使用 EF 6,它应该非常相似。
在您的应用中,您正在从数据库中读取具有相关实体 (GradeWidths) 的实体,您需要将其再次添加到数据库中。
我想你的模型是 1 Grade n GradeWidths 并且你正在创建从原始 Grade 复制的 GradeWidths 的新实例(你的模型可以是 n-m,在这种情况下你可以在更多 Grades 上使用 1 GradeWidth 但我想你不是)。
在这种情况下,要添加新实体,您需要重置所有 ID、Grade 和 GradeWidths。 然后,您有一个 "clean" 克隆,您可以将其添加到 DbSet(添加,而不是附加)。
using (var context = new MyContext(connection))
{
context.Grades.Add(new Grade()
{
GradeWidths = new List<GradeWidth>(new[]
{
new GradeWidth() {Width = 10},
new GradeWidth() {Width = 20},
new GradeWidth() {Width = 30}
})
});
context.SaveChanges();
}
using (var context = new MyContext(connection))
{
Grade grade = context.Grades.Include(g => g.GradeWidths).AsNoTracking().First();
// We need to reset all the ids
grade.Id = 0;
foreach (GradeWidth gradeWidth in grade.GradeWidths)
gradeWidth.Id = 0;
context.Grades.Add(grade);
context.SaveChanges();
}
using (var context = new MyContext(connection))
{
Debug.Assert(context.Grades.Count() == 2);
Debug.Assert(context.GradeWidths.Count() == 6);
}
使用 ObjectContext
使用ObjectContext
,您可以先附加父对象,然后将对象状态更改为已添加。同样对于每个子对象,将对象状态更改为已添加。你甚至不需要 AsNoTracking()
.
例如,对于 Category(1)↔(N)Product
关系,我使用了以下代码:
Using db As New SampleSystemEntities
Dim c = db.Categories.Include(Function(x) x.Products).First
db.Attach(c)
db.ObjectStateManager.ChangeObjectState(c, EntityState.Added)
For Each p As Product In c.Products
db.ObjectStateManager.ChangeObjectState(p, EntityState.Added)
Next
db.SaveChanges()
End Using
使用 DbContext
如果您使用 DbContext
使用您的代码一切都会正常工作:
Using db As New SampleSystemEntities
Dim c = db.Categories.Include(Function(x) x.Products).AsNoTracking().First
db.Categories.Add(c)
db.SaveChanges()
End Using