我有一个应用程序可以将大量数据读入内存并批量处理。
我想要的是实体框架在删除已删除的实体时忽略DbUpdateConcurrencyException
。
原因是,当一个实体被处理并标记为删除时,它可能已经从数据库中删除。
不经意地删除已经删除的行不是问题,不应该导致错误,我只需要一种方法告诉实体框架:)
实施例
Db.Entry(itemToRemove).State = EntityState.Deleted;
Db.SaveChanges();
如果itemToRemove
已被删除,则会导致错误。
注意:Db.Configuration.ValidateOnSaveEnabled = false;
并不像另一个线程所暗示的那样解决这个问题。
答案 0 :(得分:15)
怎么样?
Db.Entry(itemToRemove).State = EntityState.Deleted;
bool saveFailed;
do
{
saveFailed = false;
try
{
Db.SaveChanges();
}
catch(DbUpdateConcurrencyException ex)
{
saveFailed = true;
var entry = ex.Entries.Single();
//The MSDN examples use Single so I think there will be only one
//but if you prefer - do it for all entries
//foreach(var entry in ex.Entries)
//{
if(entry.State == EntityState.Deleted)
//When EF deletes an item its state is set to Detached
//http://msdn.microsoft.com/en-us/data/jj592676.aspx
entry.State = EntityState.Detached;
else
entry.OriginalValues.SetValues(entry.GetDatabaseValues());
//throw; //You may prefer not to resolve when updating
//}
}
} while (saveFailed);
答案 1 :(得分:3)
您可以处理DbUpdateConcurrencyException
,然后使用RefreshMode.StoreWins和已删除的实体作为参数调用Refresh(RefreshMode,IEnumerable)
。
try{
Db.Entry(itemToRemove).State = EntityState.Deleted;
Db.SaveChanges();
}
catch(DbUpdateConcurrencyException)
{
IObjectContextAdapter adapter = Db;
adapter.ObjectContext.Refresh(RefreshMode.StoreWins, context.ObjectStateManager.GetObjectStateEntries(System.Data.EntityState.Deleted));
Db.SaveChanges();
}
答案 2 :(得分:1)
基于来自https://msdn.microsoft.com/en-US/data/jj592904的代码,但我添加了一个infite循环计数器(以防万一,你永远不知道,对吧?)并循环遍历异常列表中的所有条目。
var maxTriesCounter = 20;
bool saveFailed;
do
{
saveFailed = false;
maxTriesCounter--;
try
{
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
saveFailed = true;
foreach (var entry in ex.Entries)
{
entry.Reload();
}
}
} while (saveFailed && maxTriesCounter > 0);
答案 3 :(得分:0)
这是我使用的。保存后分离所有问题记录。
Db.Entry(itemToRemove).State = EntityState.Deleted;
while(true)
try {
Db.SaveChanges();
break;
} catch (DbUpdateConcurrencyException ex) {
ex.Entries.ToList().ForEach(x=>x.State=EntityState.Detached);
}
或者您可以将自定义SaveChanges函数添加到DbContext类中,并在需要忽略这些错误时使用它。
public int SaveChanges_IgnoreConcurrencyExceptions () {
while(true)
try {
return this.SaveChanges();
} catch (DbUpdateConcurrencyException ex) {
ex.Entries.ToList().ForEach(x => x.State=EntityState.Detached);
}
}
答案 4 :(得分:0)
这是我的方法:
public async Task DeleteItem(int id)
{
bool isDone = false;
while (!isDone)
{
var item= await dbContext.Items.AsNoTracking().SingleOrDefaultAsync(x=> x.id== id);
if (item== null)
return;
dbContext.Items.Delete(item);
try
{
await dbContext.CommitAsync();
return;
}
catch (DbUpdateConcurrencyException ex)
{
}
}
}
答案 5 :(得分:0)
这是另一种方法:
context.Delete(item);
bool saveFailed;
do
{
saveFailed = false;
try
{
await context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException ex)
{
saveFailed = true;
var entity = ex.Entries.Single();
await entity.Single().ReloadAsync();
if (entity.State == EntityState.Unchanged)// entity is already updated
context.Delete(item);;
else if (entity.State == EntityState.Detached) // entity is already deleted
saveFailed =false;
}
} while (saveFailed);
ReloadAsync()
方法从 Microsoft docs 开始:
从数据库重新加载实体并覆盖任何属性值 使用数据库中的值。
调用此方法后实体将处于Unchanged状态, 除非实体不存在于数据库中,在这种情况下 实体将被分离。最后,在添加的实体上调用 Reload 数据库中不存在的就是空操作。但是请注意,一个 添加的实体可能尚未创建其永久键值。
答案 6 :(得分:0)
我很久以前发布了这个问题,但最近引起了一些关注,所以我想我会添加我实际使用的解决方案。
${WORKDIR}
我考虑过的事情 - 我不想使用 //retry up to 5 times
for (var retries = 0; retries < 5; retries++)
{
try
{
Db.SaveChanges();
break;
}
catch (DbUpdateConcurrencyException ex)
{
foreach (var entity in ex.Entries)
{
entity.State = EntityState.Detached;
}
}
}
或 ReloadAsync()
,因为我想忽略在另一个进程中删除的项目,而不需要任何额外的数据库开销。
我在 for 循环中添加了一个简单的防止无限循环的保护措施 - 这不是应该能够发生的事情,但如果可以避免的话,我是一个腰带和大括号接近的人而不是 ObjectContext.Refresh
的粉丝。
不需要像 while(true)
或 isDone
这样的局部变量 - 如果我们成功保存,只需中断。
无需将 saveFailed
强制转换为列表来枚举它 - 仅仅因为您可以在一行上写一些东西并不会使它变得更好。