views:

804

answers:

3

Here are the relevant technologies that I'm working with:

  • Devart's dot Connect for Oracle (to facilitate Linq-to-Sql for Oracle).
  • Strongly Typed ADO.NET Datasets.
  • An Oracle database.

Here's the challenge:

  • My legacy code submits database updates with ADO.NET datasets and table adapters.
  • I'd like to begin converting that code over to Linq-to-Sql, but I'd like to do it piecemeal to minimize code churn and risk.


Here's my proof of concept schema:

Parent Table

  • Parent.Id
  • Parent.Name

Child Table

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

Here's my proof of concept code block:

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


  • As the comments above indicate, the thread will halt indefinitely on the child data adapter's update call because it will wait indefinitely for a write lock on the Child table. [Note the foreign key relationship: Parent.Id -> Child.ParentId (M:N)]

Here's my question:

  • I want to wrap the entire code block in a transaction.
  • Can I do this? Considering that:
    • I want to commit an update on the Parent table with Linq-to-Sql's SubmitChanges method...
    • And I want to commit an update on the Child table with an ADO.NET dataset table adapter.


Here are two interesting footnotes:

  1. This whole things works in reverse. That is, if I wanted to submit changes to the parent table with a data adapter and changes to the child table with linq-to-sql... that would work.
  2. I tried to explicitly attach the transaction to the dataadapter, but the compiler won't allow it because it is a different type of transaction.

              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();
    
+1  A: 

I don't know anything about Oracle's transactions... but on the dotnet side you should be fine to control the transaction yourself. Make sure both technologies are using the same connection instance.

When we control transactions through the connection instead of through the ORM, we use transaction scope: http://msdn.microsoft.com/en-us/library/ms172152.aspx

David B
A: 

Use a TransactionScope class.

Beware that if you are using different databases (or they reside on distinct servers) you need to check your DTC configuration.

Giulio Vian
+1  A: 

I had the same issue, encountering these two errors:

  • integrity constraint violation (ORA-02291)
  • "Can not insert entity with the same key if key is not database generated"

The problem was that the child object's identity column was not set properly. If DotConnect LINQ does not assume an identity key, then objects properties seem to be set ad hoc, resulting in non-sequential updates, leading to integrity violations.

Here's the fix:

  • LINQ needs to know that the child's primary key is an entity key and auto-generated.
  • In Oracle, setup an auto-incremented key for the child object.
  • First create a sequence:

      DROP SEQUENCE MyChild_SEQ;
      CREATE SEQUENCE MyChild_SEQ
          MINVALUE 1
          MAXVALUE 999999999999999999999999999
          START WITH 1
          INCREMENT BY 1
          CACHE 20;
    
  • Next create the OnInsert trigger:

    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
    
  • Modify the storage model to incorporate the new auto-generated primary key:

    • In the EntityDeveloper for dotConnect, open your LINQ storage model (.LQML file).
    • Set the child object's entity key to 'Auto Generated Value', and Auto-Synch to 'OnInsert'.
    • Save the storage model, and in Visual Studio, clean and rebuild the solution.
    • Remove any code that explicitly sets the child's primary key.
      • LINQ will implicitly recognize this as auto-incremented, and retrieve the trigger-created ID.
  • In code, after creating the child object, attach it to the parent, as below:

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

Here are further resources:

Cheers!

MAbraham1