我有以下方法
public async Task Foo()
{
try
{
//Do stuff
bool inserted = false;
int tries=0;
while (!inserted && tries<2)
{
try
{
inserted = await Bar();
}
catch (Exception ex)
{
//log ex and continue
}
finally
{
if(!inserted)
{
tries++;
}
}
}
}
catch (Exception ex)
{
//log ex and continue
}
}
和
public async Task<bool> Bar()
{
//setup opbject to be inserted to database
try
{
//the table can not have auto incrememnt so we read the max value
objectToBeAdded.id = Context.Set<object>().Max(o => o.id) + 1;
await Context.Set<object>().AddAsync(objectToBeAdded);
await Context.SaveChangesAsync();
return true;
}
catch (Exception ex) {
return false;
}
}
该代码在多线程环境中运行,并且每分钟运行很多次,因此总是有机会发生以下异常。
Microsoft.EntityFrameworkCore.DbUpdateException:更新条目时发生错误。有关详细信息,请参见内部异常。 ---> MySql.Data.MySqlClient.MySqlException:键'PRIMARY'的条目'XXXXX'重复---> MySql.Data.MySqlClient.MySqlException:键'PRIMARY'的条目'XXXXX'的重复
不幸的是,这是一个很难重现的错误,我们的问题是它使应用程序崩溃而不是重试并继续前进。
我们无法更改表格以支持自动递增主键。
编辑:按要求的完整堆栈跟踪
-错误-无法执行DbCommand(8ms)[Parameters = [@ p0 ='?' (DbType = Int64),@ p1 ='?' (DbType =布尔),.....,@ pN ='?' (DbType =十进制)],CommandType ='文本',CommandTimeout ='600']插入
table
(id
,col1
,....colN
)值(@ p0,@ p1,.... @pN); -错误-保存上下文类型“实体”的更改时,数据库中发生异常。 Microsoft.EntityFrameworkCore.DbUpdateException:更新条目时发生错误。有关详细信息,请参见内部异常。 ---> MySql.Data.MySqlClient.MySqlException:键'PRIMARY'的条目'XXXXX'重复---> MySql.Data.MySqlClient.MySqlException:键'PRIMARY'的条目'XXXXXX'的重复 在MySqlConnector.Core.ServerSession.TryAsyncContinuation(Task1 task) in C:\.......\mysqlconnector\src\MySqlConnector\Core\ServerSession.cs:line 1248 at System.Threading.Tasks.ContinuationResultTaskFromResultTask
2.InnerInvoke() 在System.Threading.ExecutionContext.RunInternal(ExecutionContext executeContext,ContextCallback回调,对象状态) ---从之前引发异常的位置开始的堆栈结束跟踪--- 在System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task&currentTaskSlot) ---从之前引发异常的位置开始的堆栈结束跟踪--- 在C:........ \ mysqlconnector \ src \ MySqlConnector \ Core \ ResultSet.cs:line 42中的MySqlConnector.Core.ResultSet.ReadResultSetHeaderAsync(IOBehavior ioBehavior) ---内部异常堆栈跟踪的结尾--- 在C:........ \ mysqlconnector \ src \ MySqlConnector \ MySql.Data.MySqlClient \ MySqlDataReader.cs:line 80处的MySql.Data.MySqlClient.MySqlDataReader.ActivateResultSet(ResultSet resultSet) 在C中的MySql.Data.MySqlClient.MySqlDataReader.ReadFirstResultSetAsync(IOBehavior ioBehavior)中:... 在C:.............. :第287行 在C:.............. 在Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommand.ExecuteAsync(IRelationalConnection连接,DbCommandMethod executeMethod,IReadOnlyDictionary2 parameterValues, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken) --- End of inner exception stack trace --- at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(DbContext _, ValueTuple
2个参数,CancellationToken cancelledToken)处 在Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.ExecuteImplementationAsync [TState,TResult](Func4 operation, Func
4验证成功,TState状态,CancellationToken cancelledToken)处 在Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.ExecuteImplementationAsync [TState,TResult](Func4 operation, Func
4验证成功,TState状态,CancellationToken cancelledToken)处 在Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IReadOnlyList`1 entryToSave,CancellationToken cancelledToken) 在Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(Boolean acceptAllChangesOnSuccess,CancellationToken cancelToken) 在Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess,CancellationToken cancelToken)
答案 0 :(得分:0)
答案 1 :(得分:0)
我猜想,导致此错误难以重现的原因是您的代码异步运行。 您可以通过查询数据上下文中的当前最大ID来获取新的最大ID,由于查询的另一个线程也可能创建新实体,因此可以在查询后立即更改它。
您应该做的是在Bar()
方法中的逻辑周围添加一个锁语句。这样,您的第二个插入内容将在您的第一个插入完成后进行处理,因此所有项目共享相同的最大ID。
下面的内容应该会有所帮助,如果它确实可以编译,我没有在Visual Studio中对其进行检查,但是您明白了。
private object _lockObject = new object();
public async Task<bool> Bar()
{
//setup object to be inserted to database
try
{
// lock your changes, so they run in a safe order
lock (_lockObject)
{
//the table can not have auto incrememnt so we read the max value
objectToBeAdded.id = Context.Set<object>().Max(o => o.id) + 1;
await Context.Set<object>().AddAsync(objectToBeAdded);
await Context.SaveChangesAsync();
}
return true;
}
catch (Exception ex) {
return false;
}
}