我试图将文件写入磁盘以及通过原子事务中的存储过程将数据插入数据库。即如果这两个操作中的任何一个失败(文件无法写入磁盘或存储过程失败),我想什么也不做,只是将一个异常抛回给调用者。
关于如何最好地解决文件写入和数据库插入的原子事务的任何建议?
附加信息:我正在使用带有存储过程的C#.NET进入MS SQL Server,但不一定适合这些技术的通用解决方案也很好。
更新: 在写完下面的所有答案并研究其他人之后,我写道 这个帖子 关于如何使用3种不同的方法解决这个问题。
我试图将文件写入磁盘以及通过原子事务中的存储过程将数据插入数据库。即如果这两个操作中的任何一个失败(文件无法写入磁盘或存储过程失败),我想什么也不做,只是将一个异常抛回给调用者。
关于如何最好地解决文件写入和数据库插入的原子事务的任何建议?
附加信息:我正在使用带有存储过程的C#.NET进入MS SQL Server,但不一定适合这些技术的通用解决方案也很好。
更新: 在写完下面的所有答案并研究其他人之后,我写道 这个帖子 关于如何使用3种不同的方法解决这个问题。
您需要使用新的TxF,Vista,Windows 7和Windows Server 2008中引入的Transacted NTFS。这是一篇很好的介绍性文章: 使用文件系统事务增强应用程序。它包含一个将文件操作注册到系统事务中的小型托管示例:
// 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 选项 使用ReadCommitted隔离级别:
using (TransactionScope scope = new TransactionScope(
TransactionScope.Required,
new TransactionOptions
{ IsolationLevel = IsolationLEvel.ReadCommitted}))
{
...
}
如果没有这个,你将获得默认的Serializable隔离级别,这对大多数情况来说都是过度杀伤。
这个问答 似乎是答案的一部分。它涉及Transactional NTFS。 SLaks链接到 在MSDN上托管的Transactional NTFS的.NET托管包装器。
你可以尝试使用 TransactionScope
。
你可以利用 System.Transactions
命名空间
System.Transactions命名空间包含允许您编写自己的事务应用程序和资源管理器的类。具体而言,您可以创建并参与具有一个或多个参与者的事务(本地或分布式)。
有关更多详细信息,请参阅MSDN文档 http://msdn.microsoft.com/en-us/library/system.transactions.aspx
对于这个简单的事情,我会(psudocode)
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
}
也许我在这里没有遇到困难,但看起来很简单......伪代码:
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();
}
}
喜欢短路。