我尝试从父单数据库记录中删除子实体时遇到意外问题。 经过一些测试,我们已经复制了该问题。 我们的C#代码使用Northwind数据库。
NWModel context = new NWModel();
Orders order = context.Orders.Where(w => w.OrderID == 10248).FirstOrDefault();
context.Entry(order).Collection(typeof(Order_Details).Name).Load();
order.Order_Details.RemoveAt(1);
System.Data.Entity.Infrastructure.DbChangeTracker changeset = context.ChangeTracker;
var changes = changeset.Entries().Where(x => x.State == System.Data.Entity.EntityState.Modified).ToList<Object>();
var detetions = changeset.Entries().Where(x => x.State == System.Data.Entity.EntityState.Deleted).ToList<Object>();
在原始的Order_Details表设置中,一切正常。
正确的var删除已删除记录。
为了重现该问题,我们在Order_Details表上添加了一个新的PK身份int字段(OrderDetailId int identity);之后: var删除不包含记录,而 var更改包含 Order_Detail 记录。
EF将 Order_Detail 的 Orders 属性设置为null并将记录标记为已更新。
我找到了很多有关此问题的文章,所有这些文章都建议将 Order_Detail Orders 属性标记为 [必需] 。
我尝试按照this post中的建议在FK实体上设置 [Required] 属性,(本文介绍了与EF6行为相同的EFCore行为),但没有解决我的问题。
这是预期的行为吗?
我们将不胜感激任何意见或建议。
谢谢
答案 0 :(得分:1)
这是正确的行为。
确实,您不删除详细信息。您从订单明细集合中将其删除,即切断了订单和明细之间的关系。由于此关系是通过详细信息侧的导航属性实现的,因此发生了两件事:
从逻辑上来说,如果您SaveChanges
应该有一个例外,因为没有订单就无法存在详细信息,并且您还没有删除该详细信息,只能剪切该关系。因此,您应该ctx.Set<orderDetail>().Remove(detail)
。 (有点烦人)
这就是为什么在这种情况下,我通常使用复合键获取详细信息:detailId + orderId。
因此,当您删除详细信息时,会将orderId设置为null <=> PK被视为null =>将该实体标记为删除。
答案 1 :(得分:1)
按照tschmit007的建议,终于我们实现了此解决方法。
在 Orders 实体上,我们使用 ObservableListSource <> 代替了 ICollection <> ;放入 ObservableListSource <> 类,并使用 void RemoveItem 覆盖方法,我们可以管理ctx.Set<orderDetail>().Remove(detail)
方法。
这样,所有子记录删除操作都可以按预期进行。
以下是实现的代码:
public partial class Orders
{
public Orders()
{
Order_Details = new ObservableListSource<Order_Details>();
}
[Key]
public int OrderID { get; set; }
……………
public virtual ObservableListSource<Order_Details> Order_Details { get; set; }
}
public class ObservableListSource<T> : ObservableCollection<T>, IListSource
where T : class
{
private IBindingList _bindingList;
bool IListSource.ContainsListCollection { get { return false; } }
IList IListSource.GetList()
{
return _bindingList ?? (_bindingList = this.ToBindingList());
}
private bool _bRemoveInProgress = false;
protected override void RemoveItem(int index)
{
if (!_bRemoveInProgress && index>=0)
{
_bRemoveInProgress = true;
DbContext cntx = this[index].GetDbContextFromEntity();
Type tp = this[index].GetDynamicProxiesType();
cntx.Set(tp).Remove(this[index]);
base.RemoveItem(index);
}
_bRemoveInProgress = false;
}
}
public static class DbContextExtender
{
public static Type GetDynamicProxiesType(this object entity)
{
var thisType = entity.GetType();
if (thisType.Namespace == "System.Data.Entity.DynamicProxies")
return thisType.BaseType;
return thisType;
}
public static DbContext GetDbContextFromEntity(this object entity)
{
var object_context = GetObjectContextFromEntity(entity);
if (object_context == null)
return null;
return new DbContext(object_context, dbContextOwnsObjectContext: false);
//return object_context;
}
private static ObjectContext GetObjectContextFromEntity(object entity)
{
var field = entity.GetType().GetField("_entityWrapper");
if (field == null)
return null;
var wrapper = field.GetValue(entity);
var property = wrapper.GetType().GetProperty("Context");
var context = (ObjectContext)property.GetValue(wrapper, null);
return context;
}
}