¿Cómo escribir un archivo en el disco e insertar un registro de base de datos en una sola transacción?

Estoy tratando de escribir un archivo en el disco, así como insertar datos en una base de datos a través de un procedimiento almacenado, todo dentro de una transacción atómica. es decir, si cualquiera de estas 2 operaciones falla (o bien el archivo no se puede escribir en el disco o el procedimiento almacenado falla), me gustaría no hacer nada y simplemente devolver una excepción a la persona que llama.

¿Alguna sugerencia sobre la mejor manera de abordar esta transacción atómica para una escritura de archivo y una inserción de base de datos?

Información adicional: estoy utilizando C # .NET con un procedimiento almacenado en MS SQL Server, pero las soluciones generales no necesariamente se adaptan a estas tecnologías también están bien.

ACTUALIZACIÓN: Después de revisar todas las respuestas a continuación e investigar otras, escribí esta publicación sobre cómo resolver este problema utilizando 3 enfoques diferentes.

Debe usar el nuevo TxF, el NTFS Transacted introducido en Vista, Windows 7 y Windows Server 2008. Este es un buen artículo introductorio: Mejore sus aplicaciones con transacciones del sistema de archivos . Contiene una pequeña muestra administrada de la inscripción de una operación de archivo en una transacción del sistema:

// 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 (eg passing hFile to StreamWriter constructor) // Close handles } 

Deberá registrar su operación de SQL en la misma transacción, que se producirá automáticamente en un TransactionScope. Pero le recomiendo encarecidamente que anule las opciones predeterminadas de TransactionScope para usar el nivel de aislamiento de ReadCommitted:

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

Sin esto, obtendrá el nivel de aislamiento Serializable predeterminado, que es demasiado para la mayoría de los casos.

Esta pregunta y respuesta parece ser parte de la respuesta. Se trata de NTFS transaccional. Los enlaces de SLaks a un contenedor administrado por .NET para NTFS transaccional alojado en MSDN.

Puedes intentar usar un TransactionScope .

Puede aprovechar el System.Transactions nombres System.Transactions

El espacio de nombres System.Transactions contiene clases que le permiten escribir su propia aplicación transaccional y administrador de recursos. Específicamente, puede crear y participar en una transacción (local o distribuida) con uno o varios participantes.

Para obtener más detalles, consulte la documentación de MSDN: http://msdn.microsoft.com/en-us/library/system.transactions.aspx

Para algo así de simple, me gustaría (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 } 

Tal vez no tengo la dificultad, pero parece bastante sencillo … pseudocódigo:

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

Podría hacerlo al revés, pero luego tendría que eliminar el archivo; al hacer el primer bash de base de datos, es más fácil deshacer la primera operación.

edit … acabo de darme cuenta de que esto podría reducirse en unas pocas líneas, el bool no es necesario … dejando el original para mayor claridad:

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

Me encantan los cortocircuitos.