我有一个命令行工具,用于通过Entity Framework将数据记录批量导入/导出到应用程序数据库中。该工具适用于将新记录插入数据库,但在尝试更新似乎与EF锁定相关的现有记录时,我遇到了超时错误。
我已阅读some其他many关于实体框架和死锁的posts,但似乎没有一个答案适用于这种情况。我尝试在TransactionScope
中包装我的导入代码以及执行SET TRANSACTION ISOLATION LEVEL
SQL命令,但都没有解决超时问题。
无论在SaveChanges
的单次调用中有多少实体正在更新,都会发生超时。在下面的示例代码中,我将批处理大小设置为1到500之间的值,始终抛出相同的异常。
以下是更新代码的精简版本,后面是异常详细信息和SQL Server活动监视器的屏幕截图。
我正在使用从EDMX模型(模型优先)初始化的Entity Framework 5 DbContext对象。
using(var readContext = new MySourceEntities())
using(var readWriteContext = new MyTargetEntities())
{
var query = "SELECT ..."; // Determine which records to update
var keys = readContext.Database.SQLQuery<int>(query);
// Group the update into batches to improve performance. Batch()
// extension method from MoreLINQ
foreach (var batch in keys.Batch(BATCH_SIZE))
{
var sourceRecords = readContext
.AsNoTracking()
.Where(x => batch.Contains(x.SharedId))
.ToList();
var targetRecords = readWriteContext
.Where(x => batch.Contains(x.SharedId))
.ToLookup(x => x.SharedId);
foreach (var record in sourceRecords)
{
// Enforce a constraint on having only a single match
var target = targetRecords[record.SharedId].Single();
target.Field = record.Field;
}
readWriteContext.SaveChanges(); // <--- Timeout happens here
}
}
我从命令行应用程序运行它,它抛出的特定堆栈跟踪如下:
An error occurred while updating the entries. See the inner exception for details.
at System.Data.Entity.Internal.InternalContext.SaveChanges()
at System.Data.Entity.Internal.LazyInternalContext.SaveChanges()
at System.Data.Entity.DbContext.SaveChanges()
at <snip>
An error occurred while updating the entries. See the inner exception for details.
at System.Data.Mapping.Update.Internal.UpdateTranslator.Update(IEntityStateManager stateManager, IEntityAdapter adapter)
at System.Data.EntityClient.EntityAdapter.Update(IEntityStateManager entityCache)
at System.Data.Objects.ObjectContext.SaveChanges(SaveOptions options)
at System.Data.Entity.Internal.InternalContext.SaveChanges()
Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding. The statement has been terminated.
at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async, Int32 timeout, Task& task, Boolean asyncWrite)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean asyncWrite)
at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource`1 completion, String methodName, Boolean sendToPipe, Int32 timeout, Boolean asyncWrite)
at System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
at System.Data.Mapping.Update.Internal.DynamicUpdateCommand.Execute(UpdateTranslator translator, EntityConnection connection, Dictionary`2 identifierValues, List`1 generatedValues)
at System.Data.Mapping.Update.Internal.UpdateTranslator.Update(IEntityStateManager stateManager, IEntityAdapter adapter)
当SaveChanges
方法挂起时,SQL Server活动监视器会显示以下查询,所有这些查询都处于SUSPENDED
状态。红色查询来自readContext
所针对的数据库,而蓝色查询来自readWriteContext
所针对的数据库。
另外,暂停查询自己看起来并不可疑,只是直截了当的SELECT和UPDATE命令。我可以手动运行它们而不会出错。
修改
以下是运行的query
子句的详细信息,因为它似乎是相关的。该查询跨数据库执行连接以使记录与SharedId
匹配。从this page运行sys.dm_os_waiting_tasks
查询,会显示下表。
session_id wait_duration_ms wait_type blocking_session_id resource_description program_name text
55 15 ASYNC_NETWORK_IO NULL NULL EntityFramework <cross-db join query>
54 29310 LCK_M_IX 55 pagelock fileid=1... EntityFramework <update query>
查询的内容就像这样
SELECT DB1.dbo.Table1.SharedId
FROM DB2.dbo.Table2 INNER JOIN DB1.dbo.Table1.SharedId
ON DB1.dbo.Table1.SharedId = DB2.dbo.Table2.SharedId
WHERE (
(DB1.dbo.Table1.Field1 <> DB2.dbo.Table2.Field1) OR
(DB1.dbo.Table1.Field2 <> DB2.dbo.Table2.Field2)
)
对我来说最令人惊讶的是,查询仍处于活动状态。有关readContext.Database.SQLQuery()
调用无法完成查询的任何想法?听起来这种等待类型通常表示应用程序错误,但我不确定我是如何触发此行为的。
答案 0 :(得分:3)
解决方案是使用SQLQuery()
扩展方法明确实现ToList()
的结果。这样可以在尝试UPDATE
基础表之前完全消耗结果。
令我感到惊讶的是(对我而言)直接SQL命令没有立即返回完整结果 - 特别是因为查询返回了基本类型而不是实体对象。仔细阅读MSDN documentation后,它确实声明IEnumerable<T>
方法返回的SQLQuery
将在枚举时执行查询“。
我喜欢EF6中的这个方法,这个方法返回一个DbRawSqlQuery<T>
对象,这可能会帮助像我这样的人下次更加小心地停下来阅读文档。