TxF – NTFS Transaccional

Una capacidad de la que no se habla mucho es de TxF, que apareció junto con Vista: es la capacidad de tener transacciones sobre ficheros NTFS. Esas transacciones pueden afectar a uno o a varios ficheros… y no solo eso: gracias al poder de DTS podemos coordinar una transaccion TxF con otros tipos de transacciones como SQL Server o MSMQ!

Como pasa con muchas de las características avanzadas de windows, sólo se puede usar en .NET a través de p/invoke (si obviamos C++/CLI claro)… vamos a ver un ejemplo!

Primero creamos una clase que contenga todas las definiciones de los métodos Win32 que vamos a usar:

public static class NativeMethods
{
public const uint GENERIC_READ = 0x80000000;
public const uint GENERIC_WRITE = 0x40000000;
[DllImport("kernel32.dll", SetLastError = true)]
public static extern SafeFileHandle CreateFileTransacted(string lpFileName,
uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition,
uint dwFlagsAndAttributes, IntPtr hTemplateFile, IntPtr hTransaction, IntPtr pusMiniVersion,
IntPtr pExtendedParameter);

[DllImport("ktmw32.dll", SetLastError = true)]
public static extern IntPtr CreateTransaction(IntPtr lpTransactionAttributes, IntPtr uow,
uint createOptions, uint isolationLevel, uint isolationFlags, uint timeout, string description);

[DllImport("ktmw32.dll", SetLastError = true)]
public static extern bool CommitTransaction(IntPtr transaction);

[DllImport("ktmw32.dll", SetLastError = true)]
public static extern bool RollbackTransaction(IntPtr transaction);

[DllImport("Kernel32.dll")]
public static extern bool CloseHandle(IntPtr handle);
}

Vamos a usar los siguientes métodos del api de Win32:

  • CreateFileTransacted: Crea o abre un fichero para ser usado de forma transaccional. Una vez obtenido el handle, el resto de funciones win32 que trabajan con handles funcionan transaccionalmente con el nuevo handle.
  • CreateTransaction: Crea la transaccion
  • CommitTransaction: Hace el commit de la transaccion 
  • RollbackTransaction: Hace el rollback de la transacción
  • CloseHandle: Cierra un handle cualquiera (sea de fichero o no y sea transaccional o no).

Vamos a hacer un programilla que abra dos ficheros en modo transaccional, escriba algo en ellos y luego haga commit y/o rollback de la transacción para ver sus efectos…

Primero creamos la transacción y abrimos dos ficheros (suponemos que los nombres nos los pasarán por línea de comandos):

// 1. Crear la transacción
IntPtr transaction = NativeMethods.CreateTransaction(IntPtr.Zero, IntPtr.Zero, 0, 0, 0, 0, null);
// 2. Abrir dos ficheros de forma transaccional
SafeFileHandle sfh1 = NativeMethods.CreateFileTransacted(args[0], NativeMethods.GENERIC_READ | NativeMethods.GENERIC_WRITE,
0, IntPtr.Zero, (uint)FileMode.CreateNew, 0, IntPtr.Zero, transaction, IntPtr.Zero, IntPtr.Zero);
SafeFileHandle sfh2 = NativeMethods.CreateFileTransacted(args[1], NativeMethods.GENERIC_READ | NativeMethods.GENERIC_WRITE,
0, IntPtr.Zero, (uint)FileMode.CreateNew, 0, IntPtr.Zero, transaction, IntPtr.Zero, IntPtr.Zero);

Una vez tenemos los handles cualquier función Win32 estándard para escribir o leer en ellos nos serviría… por suerte los file streams de .NET pueden ser creados a partir de un handle de Win32, así que podremos utilizar la clase FileStream.

Ahora ya sólo nos queda hacer un commit o un rollback… Para ello si se produce cualquier error durante la escritura haremos un rollback, y en caso contrario haremos un commit. Finalmente debemos cerrar la transacción y por ello usaremos CloseHandle.

El código es tal y como sigue:

try
{
// Escribimos algo en ambos ficheros
using (FileStream f1 = new FileStream(sfh1, FileAccess.ReadWrite))
using (FileStream f2 = new FileStream(sfh2, FileAccess.ReadWrite))
using (StreamWriter sw1 = new StreamWriter(f1))
using (StreamWriter sw2 = new StreamWriter(f2))
{
sw1.Write("Fichero 1");
if (args.Length > 2 && args[2] == "/e")
throw new IOException("Error cualquiera");
sw2.Write("Fichero 2");
}

// Lanzamos el commit. Si todo ha ido bien llegaremos aquí
NativeMethods.CommitTransaction(transaction);
}
catch (IOException ex)
{
// Algun error: lanzamos el rollback
Console.WriteLine("Error en el acceso a ficheros:" + ex.Message);
NativeMethods.RollbackTransaction(transaction);
}
finally
{
// Cerramos la transacción TxF
NativeMethods.CloseHandle(transaction);
}

Como se puede observar la escritura en los ficheros se hace a través de las clases estándard de .NET. Es solo la apertura del fichero que debe hacerse a través de la API de Win32, así como la creación de la transacción.

Si el tercer parámetro que se le pasa al programa es /e el programa simulará un error.

Si ejecutais el programa TxFDemo.exe f1.txt f2.txt vereis que os aparecen los dos ficheros.

Por otro lado si ejecutais TxFDemo f1.txt f2.txt /e vereis que NO os aparece ninguno de los dos ficheros, ya que se lanza la excepción y con ella se hace un rollback de la transacción NTFS.

Ahora imaginad las posibilidades que este sistema ofrece!

Eso sí recordad que es para Windows Vista o superior…

Link: Un artículo sobre TxF bastante interesante de Jason Olson.

Saludos!

Un comentario sobre “TxF – NTFS Transaccional”

  1. Señor, gran artículo.

    Y una duda, por curiosidad, en WinXP y sin tener TxF, en C#, cuál sería la mejor forma de hacer «una transacción» para actualizar una base de datos y modificar (o mover, copiar, crear) un fichero ??

    Es decir, que las operaciones de actualizar base de datos y acceso a disco fuesen ´como una sóla operación atómica. Nunca vi ningún ejemplo sobre este tema.

    Parece que TxF ahora es la solución, pero antes cómo se hacía ?

    Saludos y gracias.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *