我正在使用实体框架来建模文档与其页面之间的简单父子关系。应该使用以下代码(按此顺序):
新页面与删除的页面具有相同的键,因为有一个索引由文档编号和页码(1..n)组成。
此代码有效。但是,当我删除第一次调用SaveChanges时,它失败了:
System.Data.SqlClient.SqlException: Cannot insert duplicate key row in object
'dbo.DocPages' with unique index 'IX_DocPages'.
以下是两次调用SaveChanges的工作代码:
Document doc = _docRepository.GetDocumentByRepositoryDocKey(repository.Repository_ID, repositoryDocKey);
if (doc == null) {
doc = new Document();
_docRepository.Add(doc);
}
_fieldSetter.SetDocumentFields(doc, fieldValues);
List<DocPage> pagesToDelete = (from p in doc.DocPages
select p).ToList();
foreach (DocPage page in pagesToDelete) {
_docRepository.DeletePage(page);
}
_docRepository.GetUnitOfWork().SaveChanges(); //IF WE TAKE THIS OUT IT FAILS
int pageNo = 0;
foreach (ConcordanceDatabase.PageFile pageFile in pageList) {
++pageNo;
DocPage newPage = new DocPage();
newPage.PageNumber = pageNo;
newPage.ImageRelativePath = pageFile.Filespec;
doc.DocPages.Add(newPage);
}
_docRepository.GetUnitOfWork().SaveChanges(); //WHY CAN'T THIS BE THE ONLY CALL TO SaveChanges
如果我保留代码,EF会创建两个事务 - 每次调用SaveChanges一个事务。第一个更新文档并删除任何现有页面。第二个事务插入新页面。我检查了SQL跟踪,这就是我所看到的。
然而,如果我删除对SaveChanges的第一次调用(因为我希望在一个事务中运行整个事件),EF神秘地根本不进行删除,而只是生成插入?? - 这会导致重复键错误。我不认为等待调用SaveChanges应该在这里重要吗?
顺便说一句,对_docRepository.DeletePage(页面)的调用执行了objectContext.DeleteObject(页面)。谁能解释这种行为?感谢。
答案 0 :(得分:4)
我认为更可能的解释是EF会执行删除操作,但可能会在插入后执行删除操作,因此您最终会通过无效状态。
不幸的是,您没有对数据库中执行DbCommands的顺序进行低级别控制。
所以你需要两个SaveChanges()调用。
一种选择是创建包装TransactionScope。
然后你可以调用SaveChanges()两次,这一切都发生在同一个事务中。
有关相关技术的更多信息,请参阅this post
希望这有帮助
亚历
答案 1 :(得分:0)
谢谢Alex。这很有趣。我确实决定将整个事情包装在一个事务范围内,并且它确实可以正常使用两个SaveChanges() - 正如您所指出的那样,由于主键与删除和后续插入冲突,似乎需要它。现在出现一个新问题,基于您链接的文章。它正确地建议调用SaveChanges(false) - 指示EF保持其更改,因为外部事务范围实际上将控制这些更改是否实际上使其进入数据库。一旦控制代码调用scope.Complete(),模式就会调用EF的context.AcceptAllChanges()。但我认为这对我来说会有问题,因为我不得不为最初描述的问题调用SaveChanges TWICE。如果这两个对SaveChanges的调用都为accept changes参数指定了False,那么我怀疑第二个调用将最终重复第一个SQL。我担心我可能会遇到Catch-22。