2014-01-06 4 views
2

저는 Entity Framework를 통해 응용 프로그램 데이터베이스로 대량 데이터 가져 오기/내보내기를 수행하기위한 명령 줄 도구가 있습니다. 이 도구는 새 레코드를 데이터베이스에 삽입하는 데는 잘 작동하지만 EF 잠금과 관련이있는 기존 레코드를 업데이트하려고 할 때 시간 초과 오류가 발생했습니다.EF 5 업데이트 시간 초과; 아마도 교착 상태로 인한 것일 수도 있습니다.

many 기타 posts (Entity Framework 및 교착 상태)에 대해 some을 읽었지만이 상황에 대한 답변이없는 것으로 보입니다. TransactionScope 내에서 가져 오기 코드를 래핑하고 SET TRANSACTION ISOLATION LEVEL SQL 명령을 실행하려고했지만 어느 것도 시간 초과 문제를 해결하지 못했습니다.

SaveChanges에 대한 단일 호출에서 얼마나 많은 엔티티가 업데이트되는지에 관계없이 시간 초과가 발생합니다. 아래의 샘플 코드에서는 배치 예외 크기를 1에서 500 사이의 값으로 설정했습니다. 항상 같은 예외가 throw됩니다.

다음은 예외 코드와 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 서버 활동 모니터 쇼 다음 쿼리는 모두 SUSPENDED 상태입니다. 빨간색 쿼리는 readContext을 대상 데이터베이스에서 가져오고 파란색 쿼리는 readWriteContext을 대상 데이터베이스에서 가져 왔습니다.

SQL Server Management Studio Activity Monitor

또한, 일시 중단 자체가 의심스러운, 단지 straighforward 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() 호출이 쿼리를 완료하지 못하는 이유는 무엇입니까? 이 대기 유형은 대개 응용 프로그램 오류를 나타내는 것처럼 들리지만이 동작을 트리거하는 방법은 확실하지 않습니다.

답변

3

해결책은 ToList() 확장 메소드를 사용하여 SQLQuery()의 결과를 명시 적으로 구체화하는 것이 었습니다. 이로 인해 기본 테이블 인 UPDATE을 시도하기 전에 결과가 완전히 소모되었습니다.

직접 SQL 명령이 전체 결과를 즉시 반환하지 않는다는 것은 놀랍습니다. 특히 쿼리가 Entity 개체가 아닌 기본 형식을 반환했기 때문에. MSDN documentation을 자세히 읽으면 SQLQuery 메서드에 의해 반환 된 IEnumerable<T>이 "열거 된 경우 쿼리를 실행합니다"라고 나와 있습니다.

저는 EF6에서이 메소드가 DbRawSqlQuery<T> 객체를 반환하기 때문에 다음에 문서를 조금 더 신중하게 읽고 멈추는 데 도움이 될 것입니다.