【问题标题】:How to write a file to disk and insert a database record in a single transaction?如何将文件写入磁盘并在单个事务中插入数据库记录?
【发布时间】:2011-02-24 21:07:58
【问题描述】:

我正在尝试将文件写入磁盘以及通过原子事务中的存储过程将数据插入数据库。即,如果这两个操作中的任何一个失败(文件无法写入磁盘或存储过程失败),我什么都不做,只是将异常返回给调用者。

关于如何最好地处理文件写入和数据库插入的原子事务的任何建议?

附加信息:我在 MS SQL Server 中使用带有存储过程的 C# .NET,但不一定针对这些技术量身定制的通用解决方案也可以。

更新:在查看了以下所有答案并研究了其他答案后,我写了this post关于如何使用 3 种不同方法解决此问题。

【问题讨论】:

  • 当您指的是“事务”时,您是指在 1 个例程中,还是在物理上是指您有回滚的 SQL 数据库事务?

标签: c# .net sql-server file transactions


【解决方案1】:

您需要使用新的 TxF,即在 Vista、Windows 7 和 Windows Server 2008 中引入的 Transacted NTFS。这是一篇很好的介绍性文章:Enhance Your Apps With File System Transactions。它包含一个将文件操作注册到系统事务中的小型托管示例:

// 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 options 以使用 ReadCommitted 隔离级别:

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

如果不这样做,您将获得默认的 Serializable 隔离级别,这在大多数情况下过于矫枉过正。

【讨论】:

【解决方案2】:

This question and answer 似乎是答案的一部分。它涉及事务性 NTFS。 SLaks 链接到.NET managed wrapper for Transactional NTFS hosted on MSDN.

您可以尝试使用TransactionScope

【讨论】:

  • 即使事务失败,文件是否仍然存在(如果 WriteToDisk 是第一个)?以前从未使用过它(看起来很酷),但它似乎对块中可能发生的非数据库事物没有任何魔力,只是如果发生异常则不会提交事务。
  • 事务性 NTFS 将回滚文件;)它具有魔力,因为它根据需要使用 DTC 来存储回滚信息。
  • 太棒了。我得去看看。我们刚刚使用了许多现代服务器,所以我实际上可以使用它!
  • 如果没有事务性 NTFS,您可以使用 CRM(补偿资源管理器)在重启后清理文件。
【解决方案3】:

您可以利用 System.Transactions 命名空间

System.Transactions 命名空间包含允许您编写自己的事务应用程序和资源管理器的类。具体来说,您可以创建并参与一个或多个参与者的事务(本地或分布式)。

有关详细信息,请参阅 MSDN 文档: http://msdn.microsoft.com/en-us/library/system.transactions.aspx

【讨论】:

    【解决方案4】:

    对于这么简单的事情,我会(伪代码)

    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
    }
    

    【讨论】:

    • 但如果由于某种原因无法删除(例如,另一个进程锁定了文件,或者您不希望应用程序具有删除权限),则可能会出现故障。不太可能,但并不完全安全。为什么不先做数据库部分,完全可以不费吹灰之力回滚?
    • -1。非常准确地显示了普通程序员对事务的无知。
    • @TomTom 那你有什么建议?
    • 使用 System.Transaction 命名空间,实现 CRM(用于文件更新的补偿资源管理器。分布式事务 101 - 适合初学者;)均受 .NET 框架支持。
    【解决方案5】:

    也许我不太明白这里的困难,但看起来很简单......伪代码:

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

    你可以反过来做,但之后你必须删除文件,首先尝试数据库可以最容易地撤消第一个操作。

    编辑...我刚刚意识到这可以缩短几行,布尔不是必需的...为了清楚起见,保留原始:

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

    喜欢短路。

    【讨论】:

    • 这里有很多可能出错的地方。您不能确定该文件是否真的被写入(几个月前在 Linux 中发生了类似的错误,其中配置文件已写入但未正确刷新),提交可能会失败,您将不得不删除该文件(并且删除可能会失败)...
    • 假设WriteFile()仅在文件被实际写入时才返回true。我不知道任何其他方式可以操作,我的意思是,如果它不是真的写的并且你没有任何办法找到它,那么你在哪里?我想如果你不能对其中一个或另一个有适当的信心,那么唯一的解决方案是硬编码其中的“撤消”部分,但是这些步骤也可能失败,你真的不是过得更好。如果对其中任何一个的信心程度不合适,那么您必须尝试所有方法,如果没有成功,请给某人发电子邮件。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多