如何在TransactionScope中实现MoveFileTransacted并同步数据库提交?
在TransactionScope中实现MoveFileTransacted并关联数据库提交
咱们要实现的是把MoveFileTransacted和TransactionScope绑定到同一个事务里,让文件移动和数据库提交要么全成、要么全败——核心就是让文件操作加入到TransactionScope管理的事务中,下面一步步来落地:
第一步:补全必要的P/Invoke定义和辅助类型
你找到的MoveFileTransacted定义不完整,咱们先把它补全,还要加上需要的枚举和获取事务句柄的方法:
using System; using System.Runtime.InteropServices; using System.Transactions; // MoveFileTransacted的参数枚举,按需选择使用 [Flags] public enum MoveFileFlags : uint { MOVEFILE_REPLACE_EXISTING = 0x00000001, MOVEFILE_COPY_ALLOWED = 0x00000002, MOVEFILE_DELAY_UNTIL_REBOOT = 0x00000004, MOVEFILE_WRITE_THROUGH = 0x00000008, MOVEFILE_CREATE_HARDLINK = 0x00000010, MOVEFILE_FAIL_IF_NOT_TRACKABLE = 0x00000020 } // MoveFileTransacted要求的回调函数,不需要的话可以传null public delegate bool MoveFileProgressRoutine( IntPtr lpSourceFile, IntPtr lpDestinationFile, IntPtr lpProgressData, UInt64 ulTotalFileSize, UInt64 ulTotalBytesTransferred); // 完整的MoveFileTransacted P/Invoke定义 [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool MoveFileTransacted( [MarshalAs(UnmanagedType.LPWStr)] string lpExistingFileName, [MarshalAs(UnmanagedType.LPWStr)] string lpNewFileName, MoveFileProgressRoutine lpProgressRoutine, IntPtr lpProgressData, MoveFileFlags dwFlags, IntPtr hTransaction); // 关键参数:传入事务句柄
另外,咱们需要从TransactionScope的事务中获取内核级的事务句柄,用TransactionInterop类来实现:
private static IntPtr GetKernelTransactionHandle() { var currentTransaction = Transaction.Current; if (currentTransaction == null) throw new InvalidOperationException("当前没有活跃的事务"); // 获取DTC事务句柄,再转换为内核事务句柄 var dtcTransaction = TransactionInterop.GetDtcTransaction(currentTransaction); IntPtr kernelHandle; var result = Ole32.DuplicateHandle( GetCurrentProcess(), dtcTransaction, GetCurrentProcess(), out kernelHandle, 0, false, 0x00000002); // DUPLICATE_SAME_ACCESS if (!result) throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); return kernelHandle; } // 补充需要的辅助P/Invoke [DllImport("ole32.dll", SetLastError = true)] private static extern bool DuplicateHandle( IntPtr hSourceProcessHandle, IntPtr hSourceHandle, IntPtr hTargetProcessHandle, out IntPtr lpTargetHandle, uint dwDesiredAccess, bool bInheritHandle, uint dwOptions); [DllImport("kernel32.dll")] private static extern IntPtr GetCurrentProcess();
第二步:在TransactionScope中整合文件移动和数据库操作
现在可以写出完整的业务逻辑了,把文件移动和数据库提交放在同一个TransactionScope里,确保两者事务一致:
public void MoveFileAndCommitDb(string sourcePath, string destPath) { // 初始化TransactionScope,默认使用可提升的事务 using (var scope = new TransactionScope(TransactionScopeOption.Required)) { try { // 获取当前事务的内核句柄 var transactionHandle = GetKernelTransactionHandle(); // 调用MoveFileTransacted,传入事务句柄绑定事务 bool moveSuccess = MoveFileTransacted( sourcePath, destPath, null, // 不需要进度回调,传null即可 IntPtr.Zero, MoveFileFlags.MOVEFILE_REPLACE_EXISTING, // 根据你的需求设置标志 transactionHandle); if (!moveSuccess) { throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); } // 执行你的数据库提交操作,替换成实际业务代码 CommitDatabaseChanges(); // 提交整个事务,只有调用Complete后,事务才会真正生效 scope.Complete(); } catch (Exception ex) { // 任何一步出错,TransactionScope会自动回滚整个事务 // 包括文件移动和数据库操作都会被撤销 Console.WriteLine($"操作失败:{ex.Message}"); throw; } } } // 示例数据库提交方法,替换成你实际的数据库操作逻辑 private void CommitDatabaseChanges() { // 比如用EF Core提交: // using (var dbContext = new YourDbContext()) // { // // 你的数据库变更操作 // dbContext.SaveChanges(); // } // 或者用ADO.NET: // using (var conn = new SqlConnection("你的连接字符串")) // { // conn.Open(); // var cmd = conn.CreateCommand(); // cmd.CommandText = "你的提交SQL"; // cmd.ExecuteNonQuery(); // } }
关键注意事项
- 事务兼容性:确保你的数据库支持分布式事务(比如SQL Server),因为当你把文件系统操作和数据库操作放在同一个TransactionScope时,事务会自动升级为分布式事务(DTC),需要先确认DTC服务已经开启。
- 权限要求:执行
MoveFileTransacted需要足够的文件系统权限,同时进程要有访问DTC的权限。 - 句柄管理:示例里通过
DuplicateHandle拿到的内核句柄,一般不需要手动释放,TransactionScope结束后事务会自动清理;如果要严格管控,可以在使用完后调用CloseHandle来释放。
内容的提问来源于stack exchange,提问作者dMilan
相关产品推荐
相关产品推荐

