Можете ли вы включить изменения linq-to-sql и обновления адаптера таблицы набора данных ADO.NET в одну транзакцию?
-
23-08-2019 - |
Вопрос
Вот соответствующие технологии, с которыми я работаю:
- Dot Connect for Oracle от Devart (для облегчения Linq-to-Sql для Oracle).
- Строго типизированные наборы данных ADO.NET.
- База данных Oracle.
Вот задача:
- Мой устаревший код отправляет обновления базы данных с помощью наборов данных ADO.NET и адаптеров таблиц.
- Я хотел бы начать преобразование этого кода в Linq-to-Sql, но мне бы хотелось делать это постепенно, чтобы минимизировать отток кода и риск.
Вот мое доказательство концептуальной схемы:
Родительская таблица
- Родитель.Идентификатор
- Родитель.Имя
Детский стол
- Child.Id
- Child.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's Метод SubmitChanges...
- И я хочу сделать обновленную информацию о детской таблице с набором данных 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 для dotConnect откройте свою модель хранения LINQ (файл .LQML).
- Установите для ключа сущности дочернего объекта значение «Автосгенерированное значение», а для параметра «Автосинхронизация» — «OnInsert».
- Сохраните модель хранилища, а затем в Visual Studio очистите и перестройте решение.
- Удалите любой код, который явно устанавливает первичный ключ дочернего элемента.
- LINQ неявно распознает это как автоматически увеличивающееся и получит идентификатор, созданный триггером.
В коде после создания дочернего объекта прикрепите его к родительскому, как показано ниже:
ChildType newChild = new ChildType(); DataContext.InsertOnSubmit(newChild); Parent.Child = newChild;
Вот дополнительные ресурсы:
- Вставить строки с возрастающим первичным ключом
- Проблема сохранения контекста сущности с помощью каскадной последовательности ORA
- Поддержка автоматического увеличения столбцов (LinqConnect+Oracle)
Ваше здоровье!
Используйте класс TransactionScope.
Помните: если вы используете разные базы данных (или они расположены на разных серверах), вам необходимо проверить конфигурацию DTC.