在目前项目里,使用Code First的模式,但数据库已经存在,并且在数据库中并未设立外键关系,但在实体类中定义了实体关系。以上为这次遇到问题的背景。
问题:
在保存一组数据的时候,提示出现主键重复的问题。
相关代码:
Dictionary<<Tuple<string, int>, Detail> details = ... // 该数据有方法外部传入 if(order.Details != null) // Order对象包含一个Detail对象的列表 { foreach(var d in order.Details.ToArray()) { var key = Tuple.Create(d.Id, d.No); if(details.ContainKey(key)) { // update the records with new value details.Remove(key); } else { dbContext.Entry(d).State = EntityState.Deleted; } } foreach(var d in details.Values) { dbContext.Details.Add(d); } dbContext.SaveChanges(); }
问题定位:
首先根据异常,找出引起错误的数据,发现要保存的数据并不存在重复情况,同时在数据库里存在该数据,按理说不应出现这个错误。
根据Log无法发现问题,只好调试程序了。当程序执行到var order = dbContext.Order.Find("id")的时候,发现数据库并不存在该id的数据,代码执行到这里,大概就已经发现了问题所在。由于数据里并不存在该id的数据,所以在前面Find的代码执行完毕后,根据输入的order数据创建了一个新的Order对象,这个时候,order的State是Added状态。当entry处于该状态时,在访问导航属性的时候,并不会尝试去数据库里读取Detail数据,所以执行上面的代码的时候,实际上是执行的dbContext.Details.Add(d)的部分。这也就造成了提示主键重复的异常。
解决:
既然是导航属性造成的问题,就只好选择绕开导航属性,直接通过Linq将Order数据读出来。
Dictionary<<Tuple<string, int>, Detail> details = ... // 该数据有方法外部传入 var detailsInDb = (from item in dbContext.Details where item.Id == order.Id select item).ToArray(); if(detailsInDb != null) { foreach(var d in detailsInDb) { var key = Tuple.Create(d.Id, d.No); if(details.ContainKey(key)) { // update the records with new value details.Remove(key); } else { dbContext.Entry(d).State = EntityState.Deleted; } } foreach(var d in details.Values) { dbContext.Details.Add(d); } dbContext.SaveChanges(); }