13

Wyodrębniam zawartość plików w tabeli plików SQL. Poniższy kod działa, jeśli nie używam Parallel.Threading i SqlFileStream. Proces nie może uzyskać dostępu do określonego pliku, ponieważ został otwarty w innej transakcji

Otrzymuję następujący wyjątek podczas odczytu strumienia pliku sql jednocześnie (Parallel).

Proces nie może uzyskać dostępu do określonego pliku, ponieważ został otwarty w innej transakcji.

TL, DR:

Podczas odczytu pliku z FileTable (stosując GET_FILESTREAM_TRANSACTION_CONTEXT) w Parallel.ForEach uzyskać powyższego wyjątku.

Przykładowy kod, aby wypróbować:

https://gist.github.com/NerdPad/6d9b399f2f5f5e5c6519

dłuższa wersja:

Fetch Osprzęt i wyodrębnić zawartość:

var documents = new List<ExtractedContent>(); 
using (var ts = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) 
{ 
    var attachments = await dao.GetAttachmentsAsync(); 

    // Extract the content simultaneously 
    // documents = attachments.ToDbDocuments().ToList(); // This works 
    Parallel.ForEach(attachments, a => documents.Add(a.ToDbDocument())); // this doesn't 

    ts.Complete(); 
} 

DAO Czytaj File Table:

public async Task<IEnumerable<SearchAttachment>> GetAttachmentsAsync() 
{ 
    try 
    { 
     var commandStr = "...."; 

     IEnumerable<SearchAttachment> attachments = null; 
     using (var connection = new SqlConnection(this.DatabaseContext.Database.Connection.ConnectionString)) 
     using (var command = new SqlCommand(commandStr, connection)) 
     { 
      connection.Open(); 

      using (var reader = await command.ExecuteReaderAsync()) 
      { 
       attachments = reader.ToSearchAttachments().ToList(); 
      } 
     } 

     return attachments; 
    } 
    catch (System.Exception) 
    { 
     throw; 
    } 
} 

Tworzenie obiektów dla każdego pliku: Obiekt zawiera odniesienie do GET_FILESTREAM_TRANSACTION_CONTEXT

public static IEnumerable<SearchAttachment> ToSearchAttachments(this SqlDataReader reader) 
{ 
    if (!reader.HasRows) 
    { 
     yield break; 
    } 

    // Convert each row to SearchAttachment 
    while (reader.Read()) 
    { 
     yield return new SearchAttachment 
     { 
      ... 
      ... 
      UNCPath = reader.To<string>(Constants.UNCPath), 
      ContentStream = reader.To<byte[]>(Constants.Stream) // GET_FILESTREAM_TRANSACTION_CONTEXT() 
      ... 
      ... 
     }; 
    } 
} 

Czytaj plik używając SqlFileStream: Wyjątkiem jest wyrzucony tutaj

public static ExtractedContent ToDbDocument(this SearchAttachment attachment) 
{ 
    // Read the file 
    // Exception is thrown here 
    using (var stream = new SqlFileStream(attachment.UNCPath, attachment.ContentStream, FileAccess.Read, FileOptions.SequentialScan, 4096)) 
    { 
     ... 
     // extract content from the file 
    } 

    .... 
} 

Aktualizacja 1:

Według this artykułu wydaje się, że może to być problem poziom izolacji. Czy ktoś kiedykolwiek stanął przed podobnym problemem?

+0

Spróbuj otworzyć plik w tym samym wątku, który wykonał resztę kodu SQL. Może to po prostu nie jest dozwolone. – usr

+0

Piszesz do 'dokumentów' na wielu wątkach,' Lista 'nie jest bezpieczna dla wątków i nie możesz tego zrobić (To nie jest prawdopodobne źródło twojego problemu, ale jest to problem) –

Odpowiedz

4

Transakcja nie płynąć do Parallel.ForEach, należy ręcznie doprowadzić transakcję.

//Switched to a thread safe collection. 
var documents = new ConcurrentQueue<ExtractedContent>(); 
using (var ts = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) 
{ 
    var attachments = await dao.GetAttachmentsAsync(); 
    //Grab a reference to the current transaction. 
    var transaction = Transaction.Current; 
    Parallel.ForEach(attachments, a => 
    { 
     //Spawn a dependant clone of the transaction 
     using (var depTs = transaction.DependentClone(DependentCloneOption.RollbackIfNotComplete)) 
     { 
      documents.Enqueue(a.ToDbDocument()); 
      depTs.Complete(); 
     } 
    }); 

    ts.Complete(); 
} 

ja również przełączony z List<ExtractedContent> do ConcurrentQueue<ExtractedContent> ponieważ nie są dozwolone połączenia .Add( na listę z wielu wątków w o tym samym czasie.

+0

Zakładając, że transakcja jest kopiowana, czy równoczesny dostęp do plików SQL jest bezpieczny? – usr

+0

@usr Tak, jest to jedna z korzyści używania [SQL Filestreams] (https://technet.microsoft.com/en-us/library/bb933993 (v = sql.105) .aspx), mogą one brać udział w transakcjach. Rzeczywisty dostęp do plików odbywa się za pośrednictwem udziałów sieciowych UNC na przejściową ścieżkę istniejącą przez cały czas trwania transakcji. Nie byłoby inaczej niż otwarcie dwóch obiektów "FileStream" tylko do odczytu na tę samą ścieżkę sieciową. –

+2

@usr Z ciekawości sprawdziłem źródło odniesienia. SqlFileStream jest po prostu otoką wokół normalnego FileStream i zarządzanego opakowania wokół [FILE_FULL_EA_INFORMATION] (https://msdn.microsoft.com/en-us/library/windows/hardware/ff545793 (v = vs.85) .aspx) ("Bajtek []" przekazany do konstruktora jest danymi dla tej struktury). –