Si può includere LINQ to SQL modifiche e gli aggiornamenti della scheda tavolo ADO.NET dataset in una singola transazione?

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

Domanda

Ecco le tecnologie rilevanti che sto lavorando con:

  • di Devart dot Connect per Oracle (per facilitare Linq to Sql per Oracle).
  • fortemente tipizzato dataset ADO.NET.
  • Un database Oracle.

Ecco la sfida:

  • Il mio codice legacy invia gli aggiornamenti del database con set di dati di ADO.NET e adattatori da tavolo.
  • Mi piacerebbe avviare la conversione che il codice verso LINQ to SQL, ma mi piacerebbe farlo frammentario per ridurre al minimo il codice churn e il rischio.

Ecco la mia prova del concetto di schema:

Parent Table

  • Parent.Id
  • Parent.Name

Bambino tabella

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

Ecco la mia prova del blocco di codice concetto:

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();
        }
    }
}

  • Come i commenti che stabiliscono, il filo si fermerà a tempo indeterminato su chiamata di aggiornamento della scheda di dati figlio perché attenderà indefinitamente un blocco di scrittura sul tavolo Bambino. [Si noti la relazione di chiave esterna: Parent.Id -> Child.ParentId (M: N)]

Ecco la mia domanda:

  • Voglio avvolgere l'intero blocco di codice in una transazione.
  • Posso fare questo? Considerando che:
    • voglio commettere un aggiornamento sul tavolo genitore con LINQ to SQL di metodo SubmitChanges ...
    • E voglio commettere un aggiornare sul tavolo Bambino con un ADO.NET dataset adattatore tabella .

Qui ci sono due note interessanti:

  1. Questo intere cose funziona in inverso. Cioè, se volevo inviare le modifiche alla tabella padre con un adattatore di dati e modifiche la tabella figlio con LINQ to SQL ... che funzionerebbe .
  2. Ho cercato di collegare esplicitamente la transazione al DataAdapter, ma il compilatore non lo permette, perché è un diverso tipo di transazioni.

                    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();
    
È stato utile?

Soluzione

Io non so nulla di transazioni di Oracle ... ma sul lato dotnet si dovrebbe andare bene per controllare la transazione da soli. Assicurarsi che entrambe le tecnologie stanno utilizzando la stessa istanza di connessione.

Quando controlliamo le transazioni attraverso la connessione anziché attraverso l'ORM, usiamo ambito della transazione: http://msdn.microsoft.com/en-us/library/ms172152.aspx

Altri suggerimenti

Ho avuto lo stesso problema, incontrando questi due errori:

  • violazione del vincolo di integrità (ORA-02291)
  • "Impossibile inserire entità con la stessa chiave se la chiave non è un database generato"

Il problema era che colonna di identità dell'oggetto bambino non è stato impostato correttamente. Se dotConnect LINQ non si assume una chiave di identità, quindi gli oggetti proprietà sembrano essere impostato ad hoc, con conseguente aggiornamenti non sequenziali, che porta a violazioni dell'integrità.

Ecco la correzione:

  • LINQ ha bisogno di sapere che la chiave primaria del bambino è una chiave di entità e auto-generata.
  • In Oracle, impostare una chiave auto-incrementato per l'oggetto figlio.
  • Per prima cosa creare una sequenza:

      DROP SEQUENCE MyChild_SEQ;
      CREATE SEQUENCE MyChild_SEQ
          MINVALUE 1
          MAXVALUE 999999999999999999999999999
          START WITH 1
          INCREMENT BY 1
          CACHE 20;
    
  • Avanti creare il trigger 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
    
  • Modificare il modello di archiviazione per incorporare la nuova chiave primaria generata automaticamente:

    • Nella EntityDeveloper per dotConnect, aprire il modello di archiviazione (file .LQML) LINQ.
    • Imposta chiave dell'oggetto bambino soggetto di 'Auto valore generato', e Auto-Synch a 'OnInsert'.
    • Salvare il modello di archiviazione, e in Visual Studio, e ricostruire la soluzione.
    • Rimuovere qualsiasi codice che imposta in modo esplicito la chiave primaria del bambino.
      • LINQ sarà implicitamente riconoscere questo come auto-incrementato, e recuperare l'ID di trigger-creato.
  • Nel codice, dopo aver creato l'oggetto figlio, collegarlo al genitore, come di seguito:

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

Qui ci sono ulteriori risorse:

Cheers!

Utilizzare una classe TransactionScope.

Attenzione che se si utilizza diversi database (o risiedono su server diversi) è necessario controllare la configurazione del DTC.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top