能否在单个事务中包含 linq-to-sql 更改和 ADO.NET 数据集表适配器更新?
-
23-08-2019 - |
题
以下是我正在使用的相关技术:
- Devart 的 dot Connect for Oracle(以促进 Oracle 的 Linq-to-Sql)。
- 强类型 ADO.NET 数据集。
- Oracle 数据库。
这是挑战:
- 我的旧代码使用 ADO.NET 数据集和表适配器提交数据库更新。
- 我想开始将该代码转换为 Linq-to-Sql,但我想逐步进行,以最大限度地减少代码改动和风险。
这是我的概念验证架构:
父表
- 父 ID
- 父母名字
子表
- 孩子ID
- 子.ParentId
- 孩子的名字
这是我的概念验证代码块:
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();
}
}
}
- 正如上面的注释所示,线程将无限期地停止子数据适配器的更新调用,因为它将无限期地等待子表上的写锁。【注意外键关系:Parent.Id -> Child.ParentId (M:N)]
这是我的问题:
- 我想包装整个代码块 在交易中。
- 我可以这样做吗?考虑到:
- 我想在父表上提交更新 Linq-to-SQL 的 提交更改方法...
- 我想提交一个 在 Child 表上更新,并使用 ADO.NET 数据集 工作台适配器.
这里有两个有趣的脚注:
- 这整件事 作品 在 反向。也就是说,如果我想 提交对父表的更改 使用数据适配器并更改为 具有 LINQ-to-SQL 的子表...那 会工作.
我尝试将事务显式附加到数据适配器,但编译器不允许这样做,因为它是不同类型的事务。
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();
解决方案
我对Oracle的交易一无所知......但在 dotnet 方面,您应该可以自己控制事务。确保两种技术使用相同的连接实例。
当我们通过连接而不是通过 ORM 控制事务时,我们使用事务范围: http://msdn.microsoft.com/en-us/library/ms172152.aspx
其他提示
我遇到了同样的问题,遇到这两个错误:
- 违反完整性约束(ORA-02291)
- “如果密钥不是数据库生成的,则无法插入具有相同密钥的实体”
问题是子对象的标识列设置不正确。如果 DotConnect LINQ 不采用身份密钥,则对象属性似乎是临时设置的,导致非连续更新,从而导致完整性违规。
这是修复方法:
- LINQ 需要知道子项的主键是实体键并且是自动生成的。
- 在 Oracle 中,为子对象设置一个自动递增键。
首先创建一个序列:
DROP SEQUENCE MyChild_SEQ; CREATE SEQUENCE MyChild_SEQ MINVALUE 1 MAXVALUE 999999999999999999999999999 START WITH 1 INCREMENT BY 1 CACHE 20;
接下来创建 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
修改存储模型以合并新的自动生成的主键:
- 在 EntityDeveloper for dotConnect 中,打开 LINQ 存储模型(.LQML 文件)。
- 将子对象的实体键设置为“自动生成的值”,并将自动同步设置为“OnInsert”。
- 保存存储模型,然后在 Visual Studio 中清理并重建解决方案。
- 删除任何显式设置子主键的代码。
- LINQ 将隐式地将其识别为自动递增,并检索触发器创建的 ID。
在代码中,创建子对象后,将其附加到父对象,如下所示:
ChildType newChild = new ChildType(); DataContext.InsertOnSubmit(newChild); Parent.Child = newChild;
以下是更多资源:
- 插入具有递增主键的行
- 使用级联序列ORA保存实体上下文的问题
- 自动增量列支持(LinqConnect+Oracle)
干杯!
使用 TransactionScope 类。
请注意,如果您使用不同的数据库(或者它们驻留在不同的服务器上),则需要检查您的 DTC 配置。
不隶属于 StackOverflow