我有一个很大的"扁平的" json格式的对象,以及有点复杂的关系数据库模式(大约有20个表对应于展平对象)。我试图在我的新关系数据库中自动插入那些展平对象:
foreach (var flattenedObject in flattenedObjects)
{
_repository.Insert(flattenedObject).Wait();
//some time logging, etc
}
Insert()
方法针对不同表格中的多个相关对象调用AddRangeAsync()
和AddAsync()
。
由于扁平化对象是遗留的,我说它们中有0.001%是格式错误的,并且会违反数据库约束 - 例如尝试在其中一个表中插入重复的复合主键。
我期待这些罕见的错误,因此我的想法是 - 在事务中包装整个Insert()
操作 - 如果操作的任何部分无效,只是不要插入任何内容并记录错误,所以我可以在再次尝试之前手动修改展平对象。因此,我的代码看起来有点类似于:
public async Task Insert(FlattenedObject fo)
{
using (var transaction = _context.Database.BeginTransaction())
{
try
{
//magical code that calls AddAsync for multiple tables
}
catch (Exception ex)
{
transaction.Rollback()
//logging
}
}
}
但是,如果我的try块中某处发生错误(我尝试插入违反复合主键的对象),我的整个上下文对象就会损坏。
导致异常的对象仍然存在于我的DbContext中,并且在另一个事务中对AddAsync()
的任何后续调用都会触发新的异常。
我尝试为上面foreach
循环中的每个新对象重新创建我的DbContext和repo - 但即便如此,如果我查询:
_context.ChangeTracker.Entries().Where(e => e.State != EntityState.Unchanged);
我看到我的旧对象仍然在dbContext的新实例中。
是否有任何(优雅)方式告诉我的上下文重置所有挂起的更改 - 这样我可以在发生错误时将其放入catch块中?我希望在失败的交易中发生的一切都留在那里而不是泄漏。
答案 0 :(得分:5)
以下代码对我有用。但是,如果有人发布了一个更清洁的解决方案(我仍然期望有一些开箱即用的EF),我会接受它。
private void ResetContextState() => _context.ChangeTracker.Entries()
.Where(e => e.Entity != null).ToList()
.ForEach(e => e.State = EntityState.Detached);