Использование транзакций между процессами

Я пытаюсь использовать System.Transactions (TransactionScope) для координации набора процессов, каждый из которых выполняет некоторую работу с базой данных. В конечном итоге все процессы должны фиксироваться или откатываться атомарно через один родительский процесс. К сожалению, пока ничего из того, что я пробовал, не работает.

Моя основная стратегия - использовать TransactionScope в родительском процессе, сохранить его в файл и вызвать дочерний процесс, который загружает файл, использует транзакцию внутри своего собственного TransactionScope и возвращается к родительскому процессу.

Но у меня это не работает. Когда я возвращаюсь после вызова первого дочернего элемента, я иногда вижу, что родительская транзакция была помечена как прерванная. При попытке клонирования возникает исключение TransactionAbortedException.

Я также видел исключения, когда второй ребенок пытается десериализовать транзакцию, я получаю TransactionException с кодом 0x8004d00e.

Я пытаюсь сделать то, что описано в TransactionScope для доменов приложений и процессов и http://blogs.microsoft.co.il/blogs/sasha/archive/2010/04/30/propagating-a-transaction-across-appdomains.aspx. Тем не менее, не повезло.

Вот некоторые вещи, которые я пробовал, но безуспешно:

  1. Создание DependentTransaction через DependentClone () в дочернем процессе из загруженной транзакции
  2. Создание DependentTransaction через DependentClone () в родительском процессе перед сохранением транзакции в файл
  3. Создание Clone () в родительском процессе перед сохранением транзакции в файл
  4. Сохранение транзакции с использованием сериализации
  5. Сохранение транзакции с помощью TransactionInterop.GetTransactionFromTransmitterPropagationToken ()
  6. Явное открытие соединения до родительского TransactionScope
  7. Явное присоединение транзакции к родительскому элементу
  8. Явное привлечение транзакции внутри дочернего элемента
  9. Завершение / не завершение области родительского
  10. Завершение / не завершение области для ребенка
  11. Явное создание CommittableTransaction в родительском элементе

Вот одно сообщение об исключении:

System.Transactions.TransactionException: транзакция уже неявно или явно зафиксирована или прервана. ---> System.Runtime.InteropServices.COMException: транзакция уже была явно или неявно зафиксирована или прервана (исключение из HRESULT: 0x8004D00E)

И другой (при использовании DependentClone ()):

System.Transactions.TransactionAbortedException: транзакция прервана. в System.Transactions.TransactionStatePromotedAborted.CreateBlockingClone (In ternalTransaction tx) в System.Transactions.DependentTransaction..ctor (IsolationLevel isoLevel, In ternalTransaction internalTransaction, логическая блокировка) в System.Transaction.Transaction.DeCloud

Есть идеи, что я делаю не так? Я пробовал много его перестановок, но безуспешно.

Вот код (который не пытается продемонстрировать все варианты, описанные выше):

        // one variant I have tried is to create a CommittableTransaction
        // and pass that in the scope below

        using (TransactionScope scope = new TransactionScope())
        {
            // optionally, do some parent-level EF work

            // invoke child operations in other processes
            DoChildOperation_OutOfProc(1, Transaction.Current);
            DoChildOperation_OutOfProc(2, Transaction.Current);

            scope.Complete();
        }

        // in the variant where I created a CommittableTransaction,
        // I committed it here

    ...

    private static void DoChildOperation_OutOfProc(int id, Transaction transaction)
    {
        string tranFile = string.Format("ChildTran_{0}.txt", id);
        SaveTransactionToFile(transaction, tranFile);

        Process process = new Process();
        process.StartInfo = new ProcessStartInfo(Process.GetCurrentProcess().MainModule.FileName.Replace(".vshost", string.Empty),
            string.Format("-CHILDID={0} -TRANFILE={1}", id, tranFile));
        process.StartInfo.UseShellExecute = false;
        process.Start();
        process.WaitForExit();
    }

    private static void SaveTransactionToFile(Transaction transaction, string tranFile)
    {
        byte[] transactionBytes =
            TransactionInterop.GetTransmitterPropagationToken(transaction);

        string tranFileContents = Convert.ToBase64String(transactionBytes);

        File.WriteAllText(tranFile, tranFileContents);
    }

    private static Transaction LoadTransactionFromFile(string tranFile)
    {
        string tranFileContents = File.ReadAllText(tranFile);
        File.Delete(tranFile);

        byte[] tranBytes = Convert.FromBase64String(tranFileContents);

        Transaction tran = 
            TransactionInterop.GetTransactionFromTransmitterPropagationToken(tranBytes);
        return tran;
    }

    // the child instance of the app runs this after decoding the arguments
    // from DoChildOperation_OutOfProc() and loading the transaction out of the file

    private static void DoChildOperation(int id, Transaction childTransaction)
    {
        // in one variant, I call 
        // childTransaction.DependentClone(DependentCloneOption.BlockCommitUntilComplete)
        // and then use that inside the TransactionScope

        using (TransactionScope scope = new TransactionScope(childTransaction))
        {
            // do EF work and call SaveChanges()

            scope.Complete();
        }

        // if I created a dependent clone, call Complete() here on it

person Todd    schedule 26.11.2012    source источник
comment
это действительно приятно, что вы объяснили проблемы / ошибки, с которыми вы сталкиваетесь ... но не могли бы вы показать полный блок кода, в котором выполняется код транзакции? ..?   -  person MethodMan    schedule 27.11.2012
comment
Я добавил код, немного продезинфицированный, но, надеюсь, он проясняет его.   -  person Todd    schedule 27.11.2012
comment
Это выглядит довольно волосатым. Любая причина, по которой вы не можете разделить работу на несколько транзакций, а затем использовать компенсацию для отката при необходимости ›   -  person Eben Roux    schedule 27.11.2012
comment
Конечная цель - иметь возможность вызывать другие службы для распределения работы. Каждая служба должна знать о своей работе только на основе предоставленной ей информации (которая может включать или не включать транзакцию). Если данные будут частично зафиксированы, база данных будет в поврежденном состоянии, даже если я смогу реализовать логику компенсации. Более того, логика компенсации, вероятно, добавит дополнительное состояние и определенно усложнит. Информация по ссылкам, на которые я ссылался, показывает, что вы можете передавать транзакции, как это сделал я, поэтому удивительно, что это работает не только для меня.   -  person Todd    schedule 28.11.2012
comment
У меня была аналогичная проблема, но это было из-за того, что я выгружал домены приложений. Мне пришлось поддерживать домены приложений в рабочем состоянии до тех пор, пока не будет зафиксирована родительская транзакция, после чего я выгрузил домены приложений. - ›social.msdn.microsoft.com/Forums/en-US/   -  person Hasani Blackwell    schedule 21.08.2014


Ответы (1)


Хорошо, похоже, ключ в том, что вы можете использовать TransactionScope в родительском элементе, но не в дочернем. Для дочернего элемента я открываю соединение EF, вызываю connection.EnlistTransaction () с переданной транзакцией и выполняю EF SaveChanges () или transaction.Rollback (), но не фиксирую (класс Transaction этого не предлагает). Поступая таким образом, я получаю желаемое поведение.

Пробел в моем понимании заключался в том, была ли транзакция вложенной (как это можно сделать в SQL Server) или нет. Похоже, что на самом деле это не так; это та же сделка. Примечание. Даже если вы создадите DependentTransaction с помощью Transaction.DependentClone () в дочернем элементе, вы все равно не сработаете, если поместите его в TransactionScope.

Это немного прискорбно, потому что это означает, что если ваш процесс является родительским, вы можете использовать TransactionScope, но если это дочерний процесс, вы не можете.

person Todd    schedule 27.11.2012