você pode incluir mudanças linq-to-sql e DataSet ADO.NET atualizações adaptador de tabela em uma única transação?

StackOverflow https://stackoverflow.com/questions/888299

Pergunta

Aqui estão as tecnologias relevantes que eu estou trabalhando com:

  • O Devart dot Connect para Oracle (para facilitar o Linq-to-SQL para Oracle).
  • fortemente tipado ADO.NET conjuntos de dados.
  • banco de dados ORACLE.

Aqui está o desafio:

  • atualizações de dados meu código legado submete com conjuntos de dados ADO.NET e adaptadores de tabela.
  • Eu gostaria de começar a converter esse código até Linq-to-SQL, mas eu gostaria de fazê-lo aos poucos para minimizar a rotatividade de código e risco.

Aqui está a minha prova de esquema conceito:

Table Pais

  • Parent.Id
  • Parent.Name

Tabela Criança

  • Child.Id
  • Child.ParentId
  • Child.Name

Aqui está a minha prova de bloco de código conceito:

using System;
using System.Data.Common;
using DevArtTry1.DataSet1TableAdapters;

namespace DevArtTry1
{
    class Program
    {
        static void Main(string[] args)
        {
            using (DataContext1 dc = new DataContext1())
            {
                dc.Connection.Open();
                using (DbTransaction transaction = dc.Connection.BeginTransaction(System.Data.IsolationLevel.ReadCommitted))
                {
                    dc.Transaction = transaction;

                    Parent parent = new Parent();
                    parent.Id = 1;
                    parent.Name = "Parent 1";
                    dc.Parents.InsertOnSubmit(parent);
                    dc.SubmitChanges(); // By virtue of the Parent.Id -> Child.ParentId (M:N) foreign key, this statement will impose a write lock on the child table.

                    DataSet1.CHILDDataTable dt = new DataSet1.CHILDDataTable();
                    DataSet1.CHILDRow row = dt.NewCHILDRow();
                    row.ID = 1;
                    row.PARENTID = 1;
                    row.NAME = "Child 1";
                    dt.AddCHILDRow(row);

                    CHILDTableAdapter cta = new CHILDTableAdapter();
                     // cta.Transaction = transaction;  Not allowed because you can't convert source type 'System.Data.Common.DbTransaction to target type 'System.Data.OracleClient.OracleTransaction.
                    cta.Update(dt); // The thread will encounter a deadlock here, waiting for a write lock on the Child table.
                    transaction.Commit();
                }
            }

            Console.WriteLine("Successfully inserted parent and child rows.");
            Console.ReadLine();
        }
    }
}

  • Como os comentários acima indicam, a thread irá parar por tempo indeterminado na chamada atualização do adaptador de dados filho porque ele vai aguardar indefinidamente um bloqueio de gravação na tabela filho. [Note-se a relação de chave estrangeira: Parent.Id -> Child.ParentId (M: N)]

Aqui está a minha pergunta:

  • Eu quero envolver toda o bloco de código em uma transação.
  • Posso fazer isso? Considerando que:
    • Eu quero cometer uma atualização sobre a mesa do pai com Linq para Sql método SubmitChanges de ...
    • E eu quero cometer um atualizar sobre a mesa da criança com um ADO.NET dataset mesa adaptador .

Aqui estão duas notas de rodapé interessantes:

  1. todo este coisas funciona em reverter. Isto é, se eu quisesse enviar alterações para a tabela pai com um adaptador de dados e alterações tabela filho com linq-to-sql ... que iria funcionar .
  2. Eu tentei anexar explicitamente a transação para o DataAdapter, mas o compilador não vai permitir isso, porque é um tipo diferente de transação.

                    CHILDTableAdapter cta = new CHILDTableAdapter();
                cta.Transaction = transaction; // Not allowed because you can't convert source type 'System.Data.Common.DbTransaction' to target type 'System.Data.OracleClient.OracleTransaction'.
                cta.Update(dt);
                transaction.Commit();
    
Foi útil?

Solução

Eu não sei nada sobre as transações da Oracle ... mas do lado dotnet você deve ser fino para controlar a transação si mesmo. Certifique-se de ambas as tecnologias estão usando a mesma instância de conexão.

Quando controlar transações através da conexão em vez de através do ORM, usamos escopo de transação: http://msdn.microsoft.com/en-us/library/ms172152.aspx

Outras dicas

Eu tive o mesmo problema, encontrando estes dois erros:

  • integridade violação de restrição (ORA-02291)
  • "Não é possível inserir entidade com a mesma chave se a chave não é de banco de dados gerado"

O problema era que a coluna de identidade do objeto filho não foi definido corretamente. Se dotConnect LINQ não assume uma chave de identidade, então objetos propriedades parecem ser definido ad hoc, resultando em atualizações não-seqüenciais, levando a violações de integridade.

Aqui está a correção:

  • LINQ precisa saber que a chave primária da criança é uma chave de entidade e auto-gerado.
  • No Oracle, configurar uma chave auto-incrementada para o objeto filho.
  • Primeiro, crie uma seqüência:

      DROP SEQUENCE MyChild_SEQ;
      CREATE SEQUENCE MyChild_SEQ
          MINVALUE 1
          MAXVALUE 999999999999999999999999999
          START WITH 1
          INCREMENT BY 1
          CACHE 20;
    
  • Em seguida, crie o gatilho OnInsert:

    CREATE OR REPLACE TRIGGER MyChild_AUTOINC 
    BEFORE INSERT
    ON MyChildObject
    FOR EACH ROW
    BEGIN
      SELECT MyChild_SEQ.nextval
      INTO :NEW.MyChild_ID
      FROM dual;
    END MyChild_AUTOINC ; 
    ALTER TRIGGER MyChild_AUTOINC ENABLE
    
  • Modificar o modelo de armazenamento para incorporar a nova chave primária gerada automaticamente:

    • No EntityDeveloper para dotConnect, abra o modelo de armazenamento LINQ (arquivo .LQML).
    • chave de entidade Set do objeto filho para 'Auto Gerado Valor', e Auto-sincronização para 'OnInsert'.
    • Salve o modelo de armazenamento, e no Visual Studio, limpa e reconstruir a solução.
    • Remova qualquer código que define explicitamente a chave primária da criança.
      • LINQ irá reconhecer implicitamente isso como auto-incrementado, e recuperar o ID criado-gatilho.
  • No código, depois de criar o objeto filho, anexá-lo ao pai, como abaixo:

    ChildType newChild = new ChildType();
    DataContext.InsertOnSubmit(newChild);
    Parent.Child = newChild;
    

Aqui estão mais recursos:

Felicidades!

Use uma classe TransactionScope.

Tenha em atenção que se você estiver usando bancos de dados diferentes (ou que residem em servidores distintos), você precisa verificar a configuração do DTC.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top