我在Xamarin移动项目中具有以下插入和更新代码:
public async Task SaveData(T data, bool update)
{
try
{
if (update)
dbContext.Set<T>().Attach(data);
else
await dbContext.Set<T>().AddAsync(data);
await dbContext.SaveChangesAsync();
}
catch(Exception ex)
{
Log();
throw;
}
}
当我更新现有记录时,我总是会遇到异常:
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ThrowAggregateUpdateConcurrencyException (System.Int32 commandIndex, System.Int32 expectedRowsAffected, System.Int32 rowsAffected) [0x0001e] in <e12f0cc891a249419803faaf433b12e6>:0
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch+<ConsumeResultSetWithoutPropagationAsync>d__6.MoveNext () [0x000e9] in <e12f0cc891a249419803faaf433b12e6>:0
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) [0x0003e] in <43dbbdc147f2482093d8409abb04c233>:0
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) [0x00028] in <43dbbdc147f2482093d8409abb04c233>:0
at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) [0x00008] in <43dbbdc147f2482093d8409abb04c233>:0
at System.Runtime.CompilerServices.TaskAwaiter`1[TResult].GetResult () [0x00000] in <43dbbdc147f2482093d8409abb04c233>:0
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch+<ConsumeAsync>d__2.MoveNext () [0x0016f] in <e12f0cc891a249419803faaf433b12e6>:0
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) [0x0003e] in <43dbbdc147f2482093d8409abb04c233>:0
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) [0x00028] in <43dbbdc147f2482093d8409abb04c233>:0
at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) [0x00008] in <43dbbdc147f2482093d8409abb04c233>:0
at System.Runtime.CompilerServices.TaskAwaiter.GetResult () [0x00000] in <43dbbdc147f2482093d8409abb04c233>:0
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch+<ExecuteAsync>d__32.MoveNext () [0x00142] in <e12f0cc891a249419803faaf433b12e6>:0
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) [0x0003e] in <43dbbdc147f2482093d8409abb04c233>:0
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) [0x00028] in <43dbbdc147f2482093d8409abb04c233>:0
at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) [0x00008] in <43dbbdc147f2482093d8409abb04c233>:0
at System.Runtime.CompilerServices.TaskAwaiter.GetResult () [0x00000] in <43dbbdc147f2482093d8409abb04c233>:0
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor+<ExecuteAsync>d__10.MoveNext () [0x00220] in <e12f0cc891a249419803faaf433b12e6>:0
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) [0x0003e] in <43dbbdc147f2482093d8409abb04c233>:0
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) [0x00028] in <43dbbdc147f2482093d8409abb04c233>:0
at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) [0x00008] in <43dbbdc147f2482093d8409abb04c233>:0
at System.Runtime.CompilerServices.TaskAwaiter`1[TResult].GetResult () [0x00000] in <43dbbdc147f2482093d8409abb04c233>:0
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager+<SaveChangesAsync>d__79.MoveNext () [0x00088] in <159bf28779664ac6914c3caf595e15c9>:0
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) [0x0003e] in <43dbbdc147f2482093d8409abb04c233>:0
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) [0x00028] in <43dbbdc147f2482093d8409abb04c233>:0
at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) [0x00008] in <43dbbdc147f2482093d8409abb04c233>:0
at System.Runtime.CompilerServices.TaskAwaiter`1[TResult].GetResult () [0x00000] in <43dbbdc147f2482093d8409abb04c233>:0
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager+<SaveChangesAsync>d__77.MoveNext () [0x000f8] in <159bf28779664ac6914c3caf595e15c9>:0
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) [0x0003e] in <43dbbdc147f2482093d8409abb04c233>:0
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) [0x00028] in <43dbbdc147f2482093d8409abb04c233>:0
at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) [0x00008] in <43dbbdc147f2482093d8409abb04c233>:0
at System.Runtime.CompilerServices.TaskAwaiter`1[TResult].GetResult () [0x00000] in <43dbbdc147f2482093d8409abb04c233>:0
at Microsoft.EntityFrameworkCore.DbContext+<SaveChangesAsync>d__52.MoveNext () [0x000cf] in <159bf28779664ac6914c3caf595e15c9>:0
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) [0x0003e] in <43dbbdc147f2482093d8409abb04c233>:0
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) [0x00028] in <43dbbdc147f2482093d8409abb04c233>:0
at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) [0x00008] in <43dbbdc147f2482093d8409abb04c233>:0
at System.Runtime.CompilerServices.TaskAwaiter`1[TResult].GetResult () [0x00000] in <43dbbdc147f2482093d8409abb04c233>:0
at MyXamarin.DatabaseProject.Repository.SaveData [0x0036f]
这是我在Entity Framework Core日志中看到的:
[40m[37mdbug[39m[22m[49m: Microsoft.EntityFrameworkCore.ChangeTracking[10801]
DetectChanges completed for 'MyContext'.
[40m[37mdbug[39m[22m[49m: Microsoft.EntityFrameworkCore.Database.Connection[20000]
Opening connection to database 'main' on server '/data/data/com.example.MyXamarinProject/files/XamarinDB.db'.
[40m[37mdbug[39m[22m[49m: Microsoft.EntityFrameworkCore.Database.Connection[20001]
Opened connection to database 'main' on server '/data/data/com.example.MyXamarinProject/files/XamarinDB.db'.
[40m[37mdbug[39m[22m[49m: Microsoft.EntityFrameworkCore.Database.Command[20100]
Executing DbCommand [Parameters=[], CommandType='Text', CommandTimeout='30']
PRAGMA foreign_keys=ON;
[40m[32minfo[39m[22m[49m: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (4ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
PRAGMA foreign_keys=ON;
[40m[37mdbug[39m[22m[49m: Microsoft.EntityFrameworkCore.Database.Transaction[20200]
Beginning transaction with isolation level 'Serializable'.
[40m[37mdbug[39m[22m[49m: Microsoft.EntityFrameworkCore.Database.Command[20100]
Executing DbCommand [Parameters=[@p1='3201a98f-36b2-4b8c-87d2-4f7f85221b28' (DbType = String), @p0='55' (Size = 2)], CommandType='Text', CommandTimeout='30']
UPDATE "Detail" SET "Level" = @p0
WHERE "DetailID" = @p1;
SELECT changes();
[40m[32minfo[39m[22m[49m: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (4ms) [Parameters=[@p1='3201a98f-36b2-4b8c-87d2-4f7f85221b28' (DbType = String), @p0='55' (Size = 2)], CommandType='Text', CommandTimeout='30']
UPDATE "Detail" SET "Level" = @p0
WHERE "DetailID" = @p1;
SELECT changes();
[40m[37mdbug[39m[22m[49m: Microsoft.EntityFrameworkCore.Database.Command[20300]
A data reader was disposed.
[40m[37mdbug[39m[22m[49m: Microsoft.EntityFrameworkCore.Database.Transaction[20204]
Disposing transaction.
[40m[37mdbug[39m[22m[49m: Microsoft.EntityFrameworkCore.Database.Connection[20002]
Closing connection to database 'main' on server '/data/data/com.example.MyXamarinProject/files/XamarinDB.db'.
[40m[37mdbug[39m[22m[49m: Microsoft.EntityFrameworkCore.Database.Connection[20003]
Closed connection to database 'main' on server '/data/data/com.example.MyXamarinProject/files/XamarinDB.db'.
[41m[30mfail[39m[22m[49m: Microsoft.EntityFrameworkCore.Update[10000]
An exception occurred in the database while saving changes for context type 'MyXamarinProject.Database.MyContext'.
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
根据错误消息中的link,这可能是一些并发问题。
我根据该文章修改了代码,以检查数据库中是否存在该实体:
public async Task SaveData(T data, bool update)
{
try
{
if (update)
dbContext.Set<T>().Attach(data);
else
await dbContext.Set<T>().AddAsync(data);
var dbValue = GetFromDb<Detail>(b => b.DetailID == Guid.Parse("3201a98f-36b2-4b8c-87d2-4f7f85221b28")); //dbValue does exist!!!
await dbContext.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException dbex)
{
foreach (var entry in dbex.Entries)
{
Console.WriteLine(entry.State); //it is Microsoft.EntityFrameworkCore.EntityState.Modified
if (entry.Entity is Detail)
{
var proposedValues = entry.CurrentValues;
var databaseValues = entry.GetDatabaseValues(); //IT IS ALWAYS NULL!!!
foreach (var property in proposedValues.Properties)
{
var proposedValue = proposedValues[property];
var databaseValue = databaseValues[property];
// TODO: decide which value should be written to database
// proposedValues[property] = <value to be saved>;
}
// Refresh original values to bypass next concurrency check
entry.OriginalValues.SetValues(databaseValues);
}
else
{
throw new NotSupportedException(
"Don't know how to handle concurrency conflicts for "
+ entry.Metadata.Name);
}
}
}
正如GetDatabaseValues()
所建议的那样,似乎在某种程度上“丢失”了真实的数据库。
我该怎么办?
奖金 :当我尝试添加新实体时,总是会遇到外键问题,就像导航属性所引用的实体是不存在。
更新
当我使用NoTracking
从数据库中获取实体时,在保存期间,它将尝试在另一个实体中插入一个 navigation属性的新实体,该实体是Detail
。如果导航属性为null,为什么Entity Framework感觉需要插入一个全新的实体?
更新2
它看起来像Entity Framework Core中的一些严重错误。
如果我运行以下行,则会得到每个实体:
var full = await dbContext.Detail.ToListAsync();
如果运行以下行,则会得到要更新的实体:
var fullFilter = full.Where(b => b.DetailID == data.DetalID).FirstOrDefault();
如果我运行以下行,则会得到0个实体:
var actual = dbContext.Detail.Where(b => b.DetailID == data.DetalID).FirstOrDefault();
我想后者是Entity Framework Core在内部使用的...
有什么区别?