如何将文件写入磁盘并在单个事务中插入数据库记录?

时间:2011-02-24 21:07:58

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

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

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

附加信息:我正在使用带有存储过程的C#.NET进入MS SQL Server,但不一定适合这些技术的通用解决方案也很好。

更新:在审核了以下所有答案并研究其他人之后,我写了this post关于如何使用3种不同的方法来解决这个问题。

5 个答案:

答案 0 :(得分:8)

您需要使用新的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隔离级别,这对大多数情况来说都是过度杀伤。

答案 1 :(得分:5)

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

<击> 您可以尝试使用TransactionScope

<击>

答案 2 :(得分:1)

也许我没有在这里遇到困难,但看起来非常简单......伪代码:

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

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

编辑...我刚刚意识到这可以通过几行缩短,bool不是必需的......为了清晰起见留下原件:

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

喜欢短路。

答案 3 :(得分:1)

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

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

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

答案 4 :(得分:1)

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

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
}