Как записать файл на диск и вставить запись базы данных в одну транзакцию?

Я пытаюсь записать файл на диск, а также вставить данные в базу данных с помощью хранимой процедуры в рамках атомной транзакции. т.е. если какая-либо из этих двух операций не удалась (либо файл не может быть записан на диск, либо сбой хранимой процедуры), я бы ничего не делал и просто отбрасывал исключение обратно вызывающему.

Любые предложения о том, как лучше всего решить эту атомную транзакцию для записи файла и вставки базы данных?

Дополнительная информация: Я использую С#.NET с хранимой процедурой в MS SQL Server, но общие решения, не обязательно соответствующие этим технологиям, тоже прекрасны.

ОБНОВЛЕНИЕ: После просмотра всех ответов ниже и изучения других, я написал этот пост о том, как решить эту проблему проблема с использованием трех разных подходов.

Ответ 1

Вам нужно использовать новую TxF, Transacted NTFS, представленную в Vista, Windows 7 и Windows Server 2008. Это хорошая вводная статья: Расширение ваших приложений С транзакциями файловой системы. Он содержит небольшой управляемый образец регистрации операции с файлом в системную транзакцию:

// IKernelTransaction COM Interface
[Guid("79427A2B-F895-40e0-BE79-B57DC82ED231")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IKernelTransaction
{
    int GetHandle(out IntPtr pHandle);
}

[DllImport(KERNEL32, 
   EntryPoint = "CreateFileTransacted",
   CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern SafeFileHandle CreateFileTransacted(
   [In] string lpFileName,
   [In] NativeMethods.FileAccess dwDesiredAccess,
   [In] NativeMethods.FileShare dwShareMode,
   [In] IntPtr lpSecurityAttributes,
   [In] NativeMethods.FileMode dwCreationDisposition,
   [In] int dwFlagsAndAttributes,
   [In] IntPtr hTemplateFile,
   [In] KtmTransactionHandle hTransaction,
   [In] IntPtr pusMiniVersion,
   [In] IntPtr pExtendedParameter);

....

using (TransactionScope scope = new TransactionScope())
{
   // Grab Kernel level transaction handle
   IDtcTransaction dtcTransaction = 
      TransactionInterop.GetDtcTransaction(managedTransaction);
   IKernelTransaction ktmInterface = (IKernelTransaction)dtcTransaction;

   IntPtr ktmTxHandle;
   ktmInterface.GetHandle(out ktmTxHandle);

   // Grab transacted file handle
   SafeFileHandle hFile = NativeMethods.CreateFileTransacted(
      path, internalAccess, internalShare, IntPtr.Zero,
      internalMode, 0, IntPtr.Zero, ktmTxHandle,
      IntPtr.Zero, IntPtr.Zero);

   ... // Work with file (e.g. passing hFile to StreamWriter constructor)

   // Close handles
}

Вам нужно будет зарегистрировать операцию SQL в той же транзакции, которая будет выполняться автоматически в TransactionScope. Но я настоятельно рекомендую вам переопределить стандартный TransactionScope options для использования уровня изоляции ReadCommitted:

using (TransactionScope scope = new TransactionScope(
     TransactionScope.Required, 
     new TransactionOptions 
         { IsolationLevel = IsolationLEvel.ReadCommitted}))
{
...
}

Без этого вы получите уровень изоляции Serializable по умолчанию, который в большинстве случаев является переполненным способом.

Ответ 3

Может быть, я не совсем понимаю трудность здесь, но кажется довольно простым... псевдокод:

public void DoStuff()
{
      bool itWorked=false;
      StartTransaction();
      itWorked = RunStoredProcedure();
      itWorked = itWorked && WriteFile();
      if (!itWorked) {
          RollbackTransaction();
          throw new Exception("It didn't work");
      } else {
          CommitTransaction();
      }
}

Вы можете сделать это наоборот, но тогда вам придется удалить файл после этого, сначала сделав попытку БД, проще всего отменить первую операцию.

edit... я только понял, что это может быть сокращено несколькими строками, bool не нужно... оставляя оригинал для ясности:

public void DoStuff()
{
      StartTransaction();
      if (!(RunStoredProcedure() && WriteFile())) {
          RollbackTransaction();
          throw new Exception("It didn't work");
      } else {
          CommitTransaction();
      }
}

Любые короткие замыкания.

Ответ 4

Вы можете использовать пространство имен System.Transactions

Пространство имен System.Transactions содержит классы, которые позволяют вам писать собственное транзакционное приложение и диспетчер ресурсов. В частности, вы можете создавать и участвовать в транзакции (локальной или распределенной) с одним или несколькими участниками.

Подробнее см. документацию MSDN: http://msdn.microsoft.com/en-us/library/system.transactions.aspx

Ответ 5

Для чего-то такого простого, я бы просто (psudocode)

try
{
//write file

//commit to DB

}
catch(IOException ioe)
{
// no need to worry about sql as it hasn't happened yet
// throw new exception
}
catch(SqlException sqle)
{
// delete file
// throw exception
}