[英]EF Core: Can add and save parent entity, but fail to save child entities at all
[英]EF Core update entity and child entities in loop - save changes commiting entire list on first pass
我想更新父表上的一个字段并在 foreach 循环中添加子记录。 这个想法是确保每组记录都独立更新,因为每个父项可以包含超过 10 万个子项,如果一个失败,其他的仍应尝试。
数据库中已经存在父记录,只有一个字段需要更新,还没有子记录。
问题是 for each 循环的第一次传递实际上是提交集合中所有项目的更改,而不是单个父记录及其子记录。 其他通道引发重复主键异常,因为它再次尝试更新和插入所有项目。 就好像 foreach 被忽略并且所有事情都被立即提交了 - 这是太多的负载并且不能确保每个父母,孩子都是一个孤立的事务。
什么可能导致这种行为?
private async Task<IEnumerable<ParentTable>> SaveChildItems(IEnumerable<ParentTable> itemsToBeSaved)
{
var res = new List<ParentTable>();
foreach(var itemToSave in itemsToBeSaved)
{
var strategy = _dbContext.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async() =>
{
using (var transaction = _dbContext.Database.BeginTransaction())
{
try
{
var resFile = await _dbContext.ParentTable.Where(x => x.Name == itemsToBeSaved.Name).FirstOrDefaultAsync();
resFile.FieldToUpdate = enum.UpdateValue;
resFile.ChildTable = itemsToBeSaved.ChildTable;
await _dbContext.SaveChangesAsync();
transaction.Commit();
res.Add(itemToSave);
}
catch (System.Exception ex)
{
transaction.Rollback();
Log.Fatal(ex, LoggingTemplates.Exception, "Error on parent table file. " + ex.Message, @"\n", ex);
}
}
});
}
return res;
}
用于在同一应用程序中添加每个父记录及其子记录的代码如下。 我可以更改逻辑以进行这样的工作,但是有依赖于父记录的处理,并且更新插入不是我想要的。
private async Task<IEnumerable<ParentTable>> SaveRecords(IEnumerable<ParentTable> itemsToBeSaved)
{
var res = new List<ParentTable>();
foreach(var itemToSave in itemsToBeSaved)
{
var strategy = _dbContext.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async() =>
{
using (var transaction = _dbContext.Database.BeginTransaction())
{
try
{
var resFile = await _dbContext.ParentTable.AddAsync(itemToSave);
await _dbContext.SaveChangesAsync();
transaction.Commit();
res.Add(itemToSave);
}
catch (System.Exception ex)
{
transaction.Rollback();
Log.Fatal(ex, LoggingTemplates.Exception, "Error on parent table file. " + ex.Message, @"\n", ex);
}
}
});
}
return res;
}
我的猜测是 itemsToBeSaved 中被传递到 function 的“父”表是从 _dbContext 的同一实例中检索的。
在这种情况下,它们都被该上下文跟踪,并且只要您调用
await _dbContext.SaveChangeAsync()
第一次,EF 将自动更新由该上下文跟踪的所有内容。
那些正在传递的“itemsToBeSaved”需要不被跟踪。
这是正在发生的事情:
// Retrieve 2 tables (tracked by the _dbContext))
var parents = _dbContext.ParentTable.Where(x => x.Name == "foo" || x.Name == "bar");
// Assign to two different vars
var tableFoo = parents.where(x => x.Name == "foo");
var tableBar = parents.where(x => x.Name == "bar");
// Put a child table on first one
tableFoo.ChildTable = new ChildTable() { Name = "Fum" };
// Will save ALL parent tables originally retrieved
// and the child table added so far in one go
await _dbContext.SaveChangesAsync();
// Too LATE. tableBar has already been updated.
tableBar.ChildTable = new ChildTable() { Name = "Too" };
我认为您可能错误地使用了EF。
尝试:
// Get the parent tables out of the database (without tracking). They're now not 'attached'.
var parents = _dbContext.ParentTable.AsNoTracking().Where(x => x.Name == "foo" || x.Name == "bar").ToList();
然后处理该 _dbContext 并在您进行更改的 function 之外完成所有工作。
然后在循环中为从数据库中检索的每个 ParentTable 创建一个新的 Context 实例,进行更改,调用 SaveChangesAsync,然后处理上下文。
下一个循环,再做一次。
在 class 模块级别保留 _dbContext 的单个实例可能看起来合乎逻辑,但它有点“陷阱”。 最好根据需要创建(和处置)上下文。
foreach ()
{
// instantiate context with 'using' to enforce disposal
using (var _dbContext = new DbContext())
{
// retrieve records to be updated (NOT with AsNoTracking())
// make updates to the 'attached' records
// save changes
// dispose of context ("using" will automatically call Dispose() at the closing brace
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.