能否在单个事务中包含 linq-to-sql 更改和 ADO.NET 数据集表适配器更新?

发布于 2024-07-19 21:36:01 字数 3132 浏览 6 评论 0原文

以下是我正在使用的相关技术:

  • Devart 的 dot Connect for Oracle(以促进 Oracle 的 Linq-to-Sql)。
  • 强类型 ADO.NET 数据集。
  • Oracle 数据库。

挑战如下:

  • 我的旧代码使用 ADO.NET 数据集和表适配器提交数据库更新。
  • 我想开始将该代码转换为 Linq-to-Sql,但我想逐步进行,以最大限度地减少代码改动和风险。

这是我的概念证明模式:

Parent Table

  • Parent.Id
  • Parent.Name

Child Table

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

这是我的概念证明代码块:

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

  • 正如上面的注释所示,线程将在子数据适配器上无限期地停止update 调用,因为它将无限期地等待子表上的写锁。 【注意外键关系:Parent.Id -> Child.ParentId (M:N)]

这是我的问题:

  • 我想包装整个代码块 在一笔交易中。
  • 我可以这样做吗? 考虑到:
    • 我想在父表上提交更新 Linq-to-Sql 的 SubmitChanges 方法...
    • 我想承诺 更新子表 ADO.NET 数据集表适配器

这里有两个有趣的脚注:

  1. 这一切有效 撤销。 也就是说,如果我想 将更改提交到父表 使用数据适配器并更改为 具有 linq-to-sql 的子表... 这会起作用
  2. 我尝试将事务显式附加到数据适配器,但编译器不允许这样做,因为它是不同类型的事务。

     CHILDTableAdapter cta = new CHILDTableAdapter(); 
                  cta.Transaction = 交易;   // 不允许,因为您无法将源类型“System.Data.Common.DbTransaction”转换为目标类型“System.Data.OracleClient.OracleTransaction”。 
                  cta.更新(dt); 
                  事务.Commit(); 
      

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

我不在是我 2024-07-26 21:36:01

我对 Oracle 的事务一无所知...但在 dotnet 方面,您应该可以自己控制事务。 确保两种技术使用相同的连接实例。

当我们通过连接而不是通过 ORM 控制事务时,我们使用事务范围: http://msdn.microsoft.com/en-us/library/ms172152.aspx

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

脱离于你 2024-07-26 21:36:01

我遇到了同样的问题,遇到这两个错误:

  • 完整性约束冲突(ORA-02291)
  • “如果密钥不是数据库生成的,则无法插入具有相同密钥的实体”

问题是子对象的标识列设置不正确。 如果 DotConnect LINQ 不采用身份密钥,则对象属性似乎是临时设置的,导致非连续更新,从而导致完整性违规。

修复方法如下:

  • LINQ 需要知道子项的主键是实体键并且是自动生成的。
  • 在 Oracle 中,为子对象设置一个自动递增键。
  • 首先创建一个序列:

    <块引用>

     删除序列 MyChild_SEQ; 
        创建序列 MyChild_SEQ 
            最小值 1 
            最大值 999999999999999999999999999 
            从 1 开始 
            增加 1 
            高速缓存20; 
      
  • 接下来创建 OnInsert 触发器:

    <块引用>

    创建或替换触发器 MyChild_AUTOINC  
      插入前 
      ON MyChild 对象 
      对于每一行 
      开始 
        选择 MyChild_SEQ.nextval 
        INTO :NEW.MyChild_ID 
        从双; 
      结束 MyChild_AUTOINC ;  
      更改触发器 MyChild_AUTOINC ENABLE 
      
  • 修改存储模型以合并新的自动生成的主键:

    • 在 EntityDeveloper for dotConnect 中,打开 LINQ 存储模型(.LQML 文件)。
    • 将子对象的实体键设置为“自动生成的值”,并将自动同步设置为“OnInsert”。
    • 保存存储模型,然后在 Visual Studio 中清理并重建解决方案。
    • 删除任何显式设置子主键的代码。
      • LINQ 会隐式地将其识别为自动递增,并检索触发器创建的 ID。
  • 在代码中,创建子对象后,将其附加到父对象,如下所示:

    <块引用>

    ChildType newChild = new ChildType(); 
      DataContext.InsertOnSubmit(newChild); 
      父级.子级 = newChild; 
      

以下是更多资源:

干杯!

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!

两人的回忆 2024-07-26 21:36:01

使用 TransactionScope 类。

请注意,如果您使用不同的数据库(或者它们驻留在不同的服务器上),则需要检查您的 DTC 配置。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文