在目前项目里,使用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();
}