Как записать файл на диск и вставить запись базы данных в одну транзакцию? - PullRequest
11 голосов
/ 25 февраля 2011

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

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

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

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

Ответы [ 5 ]

7 голосов
/ 25 февраля 2011

Вам необходимо использовать новый TxF, Transactted 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 по умолчанию , чтобы использовать уровень изоляции ReadCommitted:

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

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

5 голосов
/ 25 февраля 2011

Этот вопрос и ответ , кажется, часть ответа. Он включает транзакционную NTFS. SLaks ссылается на управляемую оболочку .NET для транзакционной NTFS, размещенной на MSDN.

Вы можете попробовать использовать TransactionScope.

1 голос
/ 25 февраля 2011

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

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

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

0 голосов
/ 25 февраля 2011

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

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
}
0 голосов
/ 25 февраля 2011

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

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();
      }
}

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

...