-5

I was testing by making a file copy within the scope and then throwing and error, thinking it would revert the rename. It didn't revert :(

using System;
using System.IO;
using System.Threading;
using System.Transactions;

namespace TestingTransactionScope
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                using (TransactionScope scope = new TransactionScope())
                {
                    File.Move(@"C:\file1.txt", @"C:\file1.txt.backup1");
                    // Do Operation 1
                    // Do Operation 2
                    //...
                    MyClass.ThrowError();
                    // if all the coperations complete successfully, this would be called and commit the trabsaction. 
                    // In case of an exception, it wont be called and transaction is rolled back
                    scope.Complete();
                }
            }
            catch (ThreadAbortException ex)
            {
                // Handle exception
            }

        }
    }

    class MyClass
    {
        public static void ThrowError()
        {
            throw new Exception("Something went wrong");
        }
    }
}
Rod
  • 14,529
  • 31
  • 118
  • 230
  • 3
    That is correct. Transactions are generally a database thing, though also extend to similar mediums such as message queues: https://learn.microsoft.com/en-us/dotnet/api/system.transactions I'm not aware of any file system that supports transactions. – David May 17 '19 at 23:53
  • 2
    Possible duplicate of [How to write a transaction to cover Moving a file and Inserting record in database?](https://stackoverflow.com/questions/7939339/how-to-write-a-transaction-to-cover-moving-a-file-and-inserting-record-in-databa) – Roman Marusyk May 17 '19 at 23:55
  • What, in particular, in the documentation made you think TransactionScope was applicable to the file system? – Anthony Pegram May 18 '19 at 02:19

3 Answers3

1

From docs:

The System.Transactions infrastructure makes transactional programming simple and efficient throughout the platform by supporting transactions initiated in SQL Server, ADO.NET, MSMQ, and the Microsoft Distributed Transaction Coordinator (MSDTC).

So, you can't use System.Transactions with the file system.

There is Transactional NTFS component but Microsoft strongly recommends developers utilize alternative means to achieve your applications needs.

Also you can look at TransactionalFileMgr

Or as said @ken2k, you need to implement IEnlistmentNotification and allow manual standard file operations to work with TransactionScope. For instance, to enable rollback for a write operation on an existing file, it first creates a backup of the file that will be written, then writes to the backuped file, and finally replaces the initial file with the backuped/modified file if the transaction is committed, or deletes the backup file if the transaction is rollbacked

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Roman Marusyk
  • 23,328
  • 24
  • 73
  • 116
1

Lots of good information already in the thread and they are correct; especially @Roman Marusyk. I thought what the heck and throw up a little example utilizing the System.Transactions and Runtime.InteropServices namespaces. The main function is MoveFileTransactedW, you can find more about it here

Note: You can rename the namespace, was for testing.

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Transactions;

namespace MoveFileRollback
{
    public abstract class FileTransactionHelper
    {
        [DllImport("Kernel32.dll")]
        private static extern bool CloseHandle(IntPtr handle);

        [DllImport("Kernel32.dll")]
        private static extern bool MoveFileTransactedW([MarshalAs(UnmanagedType.LPWStr)]string existingfile, [MarshalAs(UnmanagedType.LPWStr)]string newfile,
            IntPtr progress, IntPtr lpData, IntPtr flags, IntPtr transaction);

        [ComImport]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        [Guid("79427A2B-F895-40e0-BE79-B57DC82ED231")]
        private interface IKernelTransaction
        {
            void GetHandle([Out] out IntPtr handle);
        }

        public static bool MoveFile(string existingFile, string newFile)
        {
            bool success = true;
            using (TransactionScope tx = new TransactionScope())
            {
                if (Transaction.Current != null)
                {
                    IKernelTransaction kt = (IKernelTransaction)TransactionInterop.GetDtcTransaction(Transaction.Current);
                    IntPtr txh;
                    kt.GetHandle(out txh);

                    if (txh == IntPtr.Zero) { success = false; return success; }

                    success = MoveFileTransactedW(existingFile, newFile, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, txh);

                    if (success)
                    {
                        tx.Complete();
                    }
                    CloseHandle(txh);
                }
                else
                {
                    try
                    {
                        File.Move(existingFile, newFile);
                        return success;
                    }
                    catch (Exception ex) { success = false; }
                }

                return success;
            }
        }
    }
}

Quick Implementation

if (FileTransactionHelper.MoveFile(@"C:\file1.txt", @"C:\file1.txt.backup1")){
                MessageBox.Show("MOVED");
            }

I tested this on a few different files, including throwing a exception in MoveFile, when it's thrown, the transaction doesn't get marked as Complete and the move doesn't happen.

If you have questions, please let me know, I hope this may be a little useful.

Trevor
  • 7,777
  • 6
  • 31
  • 50
0

If the file system doesn't support transactions. You can then delete the copied files in the catch block.

Y.Y.
  • 664
  • 1
  • 6
  • 22