Как записать файл на диск и вставить запись базы данных в одну транзакцию?
Я пытаюсь записать файл на диск, а также вставить данные в базу данных с помощью хранимой процедуры в рамках атомной транзакции. т.е. если какая-либо из этих двух операций не удалась (либо файл не может быть записан на диск, либо сбой хранимой процедуры), я бы ничего не делал и просто отбрасывал исключение обратно вызывающему.
Любые предложения о том, как лучше всего решить эту атомную транзакцию для записи файла и вставки базы данных?
Дополнительная информация: Я использую С#.NET с хранимой процедурой в MS SQL Server, но общие решения, не обязательно соответствующие этим технологиям, тоже прекрасны.
ОБНОВЛЕНИЕ: После просмотра всех ответов ниже и изучения других, я написал этот пост о том, как решить эту проблему проблема с использованием трех разных подходов.
Ответы
Ответ 1
Вам нужно использовать новую TxF, Transacted NTFS, представленную в Vista, Windows 7 и Windows Server 2008. Это хорошая вводная статья: Расширение ваших приложений С транзакциями файловой системы. Он содержит небольшой управляемый образец регистрации операции с файлом в системную транзакцию:
// 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
Этот вопрос и ответ, как представляется, являются частью ответа. Он включает Transactional NTFS. SLaks ссылается на .NET управляемая оболочка для транзакционной NTFS, размещенной в MSDN.
Вы можете попробовать использовать TransactionScope
.
Забастовкa >
Ответ 3
Может быть, я не совсем понимаю трудность здесь, но кажется довольно простым... псевдокод:
public void DoStuff()
{
bool itWorked=false;
StartTransaction();
itWorked = RunStoredProcedure();
itWorked = itWorked && WriteFile();
if (!itWorked) {
RollbackTransaction();
throw new Exception("It didn't work");
} else {
CommitTransaction();
}
}
Вы можете сделать это наоборот, но тогда вам придется удалить файл после этого, сначала сделав попытку БД, проще всего отменить первую операцию.
edit... я только понял, что это может быть сокращено несколькими строками, bool не нужно... оставляя оригинал для ясности:
public void DoStuff()
{
StartTransaction();
if (!(RunStoredProcedure() && WriteFile())) {
RollbackTransaction();
throw new Exception("It didn't work");
} else {
CommitTransaction();
}
}
Любые короткие замыкания.
Ответ 4
Вы можете использовать пространство имен System.Transactions
Пространство имен System.Transactions содержит классы, которые позволяют вам писать собственное транзакционное приложение и диспетчер ресурсов. В частности, вы можете создавать и участвовать в транзакции (локальной или распределенной) с одним или несколькими участниками.
Подробнее см. документацию MSDN:
http://msdn.microsoft.com/en-us/library/system.transactions.aspx
Ответ 5
Для чего-то такого простого, я бы просто (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
}