Entity Framework 当对象具有复合主键时从列表创建重复对象
Entity Framework creating duplicate objects from list when object has composite Primary Key
我有一个包含子集合的对象:
public class ClaimGroup : BaseModel
{
public int Id { get; set; }
[Required,StringLength(100)]
public string Name { get; set; }
public int Order { get; set; }
public bool Include { get; set; }
public virtual ICollection<ClaimGroupItem> Items { get; set; }
}
ClaimGroupItem 是:
public class ClaimGroupItem : BaseModel
{
[Key,Column(Order = 0),DatabaseGenerated(DatabaseGeneratedOption.None)]
public int ClaimGroupId { get; set; }
[ForeignKey("ClaimGroupId")]
public virtual ClaimGroup ClaimGroup { get; set; }
[Key,Column(Order = 1),DatabaseGenerated(DatabaseGeneratedOption.None)]
public int MenuItemId { get; set; }
[ForeignKey("MenuItemId")]
public virtual MenuItem MenuItem { get; set; }
public string ClaimValue { get; set; }
}
如您所见,它有一个复合主键:MenuItemId 和 ClaimGroupId。
在更新时,它创建了 ClaimGroupItem 对象的副本,POCO 对象设置正确,但随后它创建了具有相同值的动态代理项,因此复制了对象。
例如:
var items = viewModel.Items.Where(c => !string.IsNullOrEmpty(c.ClaimValue));
上面的项目集合包含 10 个 ClaimGroupItemViewModel 对象,如下图所示。
但是,当我将 viewModel 对象映射到 Model 对象时,该集合有 20 个对象,其中 10 个是代理项,如下所示:
itemToSave.Items = (from i in items
select new ClaimGroupItem
{
ClaimValue = i.ClaimValue,
MenuItemId = i.MenuItemId,
})
.ToList();
然后当对象去保存时,我得到以下错误:
_repository.Update<ClaimGroup>(itemToSave);
Violation of PRIMARY KEY constraint 'PK_dbo.ClaimGroupItems'. Cannot
insert duplicate key in object 'dbo.ClaimGroupItems'. The duplicate
key value is (20, 6). The statement has been terminated.
错误是有道理的,EF 正在尝试保存 10 个重复对象。
为什么 Entity Framework 创建 10 个新对象并因此重复?
这是 POST 上的代码,它获取 viewModel.Items 中 79 项的完整列表,然后我们 select 只获取 claimvalue 不为空的那些。 现阶段没有重复项。
[HttpPost, ValidateAntiForgeryToken]
public ActionResult Group([Bind(Include = "Id,Name,Order,Include,Items")] ClaimGroupViewModel viewModel)
{
if (ModelState.IsValid)
{
ClaimGroup itemToSave = _repository.Get<ClaimGroup>(viewModel.Id);
itemToSave.Include = viewModel.Include;
itemToSave.Name = viewModel.Name;
itemToSave.Order = viewModel.Order;
var items = viewModel.Items.Where(c => !string.IsNullOrEmpty(c.ClaimValue));
// There is just 10 items in items variable at this point
itemToSave.Items = (from i in items
select new ClaimGroupItem
{
ClaimValue = i.ClaimValue,
MenuItem = new MenuItem { Id = i.MenuItemId}
})
.ToList();
_repository.Update<ClaimGroup>(itemToSave);
return RedirectToAction("Groups", new { updated = true });
}
return View(viewModel);
}
如果您不需要代理,请在构建 DBContext 期间设置 ProxyCreationEnabled = false 并检查。我见过我们甚至不需要代理的情况,默认情况下 EF 会为所有实体创建一个代理。
我终于搞定了。
就像调用
一样简单
itemToSave.Items.Clear()
确保它不加载旧代理对象的方法。弄清楚这是多么痛苦!这是我的工作代码。感谢大家的帮助。
[HttpPost, ValidateAntiForgeryToken]
public ActionResult Group([Bind(Include = "Id,Name,Order,Include,Items")] ClaimGroupViewModel viewModel)
{
if (ModelState.IsValid)
{
ClaimGroup itemToSave = _repository.Get<ClaimGroup>(viewModel.Id);
itemToSave.Include = viewModel.Include;
itemToSave.Name = viewModel.Name;
itemToSave.Order = viewModel.Order;
itemToSave.Items.Clear();// This needs to be done, otherwise it will try and load the list from the DB again.
itemToSave.Items = (from i in viewModel.Items
where !string.IsNullOrEmpty(i.ClaimValue)
select new ClaimGroupItem
{
ClaimValue = i.ClaimValue,
MenuItemId = i.MenuItemId,
})
.ToList();
_repository.Update<ClaimGroup>(itemToSave);
return RedirectToAction("Groups", new { updated = true });
}
return View(viewModel);
}
我有一个包含子集合的对象:
public class ClaimGroup : BaseModel
{
public int Id { get; set; }
[Required,StringLength(100)]
public string Name { get; set; }
public int Order { get; set; }
public bool Include { get; set; }
public virtual ICollection<ClaimGroupItem> Items { get; set; }
}
ClaimGroupItem 是:
public class ClaimGroupItem : BaseModel
{
[Key,Column(Order = 0),DatabaseGenerated(DatabaseGeneratedOption.None)]
public int ClaimGroupId { get; set; }
[ForeignKey("ClaimGroupId")]
public virtual ClaimGroup ClaimGroup { get; set; }
[Key,Column(Order = 1),DatabaseGenerated(DatabaseGeneratedOption.None)]
public int MenuItemId { get; set; }
[ForeignKey("MenuItemId")]
public virtual MenuItem MenuItem { get; set; }
public string ClaimValue { get; set; }
}
如您所见,它有一个复合主键:MenuItemId 和 ClaimGroupId。
在更新时,它创建了 ClaimGroupItem 对象的副本,POCO 对象设置正确,但随后它创建了具有相同值的动态代理项,因此复制了对象。
例如:
var items = viewModel.Items.Where(c => !string.IsNullOrEmpty(c.ClaimValue));
上面的项目集合包含 10 个 ClaimGroupItemViewModel 对象,如下图所示。
但是,当我将 viewModel 对象映射到 Model 对象时,该集合有 20 个对象,其中 10 个是代理项,如下所示:
itemToSave.Items = (from i in items
select new ClaimGroupItem
{
ClaimValue = i.ClaimValue,
MenuItemId = i.MenuItemId,
})
.ToList();
然后当对象去保存时,我得到以下错误:
_repository.Update<ClaimGroup>(itemToSave);
Violation of PRIMARY KEY constraint 'PK_dbo.ClaimGroupItems'. Cannot insert duplicate key in object 'dbo.ClaimGroupItems'. The duplicate key value is (20, 6). The statement has been terminated.
错误是有道理的,EF 正在尝试保存 10 个重复对象。 为什么 Entity Framework 创建 10 个新对象并因此重复?
这是 POST 上的代码,它获取 viewModel.Items 中 79 项的完整列表,然后我们 select 只获取 claimvalue 不为空的那些。 现阶段没有重复项。
[HttpPost, ValidateAntiForgeryToken]
public ActionResult Group([Bind(Include = "Id,Name,Order,Include,Items")] ClaimGroupViewModel viewModel)
{
if (ModelState.IsValid)
{
ClaimGroup itemToSave = _repository.Get<ClaimGroup>(viewModel.Id);
itemToSave.Include = viewModel.Include;
itemToSave.Name = viewModel.Name;
itemToSave.Order = viewModel.Order;
var items = viewModel.Items.Where(c => !string.IsNullOrEmpty(c.ClaimValue));
// There is just 10 items in items variable at this point
itemToSave.Items = (from i in items
select new ClaimGroupItem
{
ClaimValue = i.ClaimValue,
MenuItem = new MenuItem { Id = i.MenuItemId}
})
.ToList();
_repository.Update<ClaimGroup>(itemToSave);
return RedirectToAction("Groups", new { updated = true });
}
return View(viewModel);
}
如果您不需要代理,请在构建 DBContext 期间设置 ProxyCreationEnabled = false 并检查。我见过我们甚至不需要代理的情况,默认情况下 EF 会为所有实体创建一个代理。
我终于搞定了。 就像调用
一样简单itemToSave.Items.Clear()
确保它不加载旧代理对象的方法。弄清楚这是多么痛苦!这是我的工作代码。感谢大家的帮助。
[HttpPost, ValidateAntiForgeryToken]
public ActionResult Group([Bind(Include = "Id,Name,Order,Include,Items")] ClaimGroupViewModel viewModel)
{
if (ModelState.IsValid)
{
ClaimGroup itemToSave = _repository.Get<ClaimGroup>(viewModel.Id);
itemToSave.Include = viewModel.Include;
itemToSave.Name = viewModel.Name;
itemToSave.Order = viewModel.Order;
itemToSave.Items.Clear();// This needs to be done, otherwise it will try and load the list from the DB again.
itemToSave.Items = (from i in viewModel.Items
where !string.IsNullOrEmpty(i.ClaimValue)
select new ClaimGroupItem
{
ClaimValue = i.ClaimValue,
MenuItemId = i.MenuItemId,
})
.ToList();
_repository.Update<ClaimGroup>(itemToSave);
return RedirectToAction("Groups", new { updated = true });
}
return View(viewModel);
}