Ошибка DuplicateKeyException при установке идентификатора вручную при вставке - PullRequest
0 голосов
/ 04 февраля 2019

У меня есть таблица, которая генерирует свой первичный ключ из последовательности:

CREATE SEQUENCE [dbo].[seq_PK_testTable] AS [int] START WITH 0 INCREMENT BY 1;
CREATE TABLE [dbo].[testTable](
    [id] [int] NOT NULL,
 CONSTRAINT [PK_testTable] PRIMARY KEY CLUSTERED ([id] ASC)) ON [PRIMARY];
ALTER TABLE [dbo].[testTable] ADD CONSTRAINT [DF_testTable_id] DEFAULT (NEXT VALUE FOR [seq_PK_testTable]) FOR [id];

Чтобы иметь возможность работать с этим механизмом генерации ключей, я решил использовать частичный метод Insertконтекста данных, как предложено в этом ответе .

Для первой вставки это работает как талисман: вставка выполняется, и идентификатор обновляется в объекте, но как толькоЯ вставляю второй объект, и я получаю System.Data.Linq.DuplicateKeyException: 'Cannot add an entity with a key that is already in use.'.

MCVE :

using System.Data.Linq.Mapping;

namespace test
{
    static class Program
    {
        static void Main(string[] args)
        {
            var db = new DataClasses1DataContext(@"Data Source=");
            var testTableRecord1 = new testTable();
            var testTableRecord2 = new testTable();
            db.GetTable<testTable>().InsertOnSubmit(testTableRecord1);
            db.SubmitChanges();
            db.GetTable<testTable>().InsertOnSubmit(testTableRecord2);
            db.SubmitChanges();
        }
    }

    [Database(Name = "TestDB")]
    public class DataClasses1DataContext : System.Data.Linq.DataContext
    {
        public DataClasses1DataContext(string fileOrServerOrConnection) : base(fileOrServerOrConnection) { }

        void InserttestTable(testTable instance)
        {
            using(var cmd = Connection.CreateCommand())
            {
                cmd.CommandText = "SELECT NEXT VALUE FOR [dbo].[seq_PK_testTable] as NextId";
                cmd.Transaction = Transaction;
                instance.id = (int)cmd.ExecuteScalar();
                ExecuteDynamicInsert(instance);
            }
        }
    }

    [Table(Name = "dbo.testTable")]
    public class testTable
    {
        [Column(DbType = "Int NOT NULL", IsPrimaryKey = true)]
        public int id;
    }
}

Я предполагаю, что внутри все еще ожидает, что id будет 0.Как заставить LINQ отображать реальный идентификатор?

PS: поскольку вопрос был помечен как дубликат LINQ to SQL, вставьте индекс первичного ключа : IsPrimaryKey = true и IsDbGenerated = trueне работает, потому что они заставляют LINQ to SQL генерировать код, который запрашивает идентификатор IDENTITY, то есть SELECT CONVERT(Int,SCOPE_IDENTITY()), и возвращает NULL, если идентификатор был создан со значением по умолчанию SEQUENCE.

1 Ответ

0 голосов
/ 04 февраля 2019

Вы можете обойти эту проблему, создав новый экземпляр DataContext для каждой операции вставки.

var db = new DataClasses1DataContext(@"Data Source=");   
var testTableRecord1 = new testTable();
db.GetTable<testTable>().InsertOnSubmit(testTableRecord1);
db.SubmitChanges();

db = new DataClasses1DataContext(@"Data Source=");    
var testTableRecord2 = new testTable();
db.GetTable<testTable>().InsertOnSubmit(testTableRecord2);
db.SubmitChanges();

Если вы хотите понять, что происходит, попробуйте поместить точку останова в метод Parital InsertTestTable.Вы увидите, что он не вызывается после второго вызова SubmitChanges ().Каждый экземпляр контекста данных поддерживает кэш, содержащий каждый объект, который вставляется, обновляется или извлекается.Кеш ведет себя как словарь, используя первичный ключ объекта в качестве ключа словаря.По какой-то причине LINQ запускает пользовательскую логику вставки только один раз для каждой кэшированной сущности.IMO, это ошибка в LINQ, и я не могу найти никакой документации, которая бы оправдывала это поведение.

Чтобы понять, что я имею в виду, попробуйте установить для каждого объекта отдельное значение, и вы увидите, чтопользовательское поведение вставки на самом деле работает правильно:

var db = new DataClasses1DataContext(@"Data Source=");   
var testTableRecord1 = new testTable(){ id = -1 };
var testTableRecord2 = new testTable(){ id = -2 };
db.GetTable<testTable>().InsertOnSubmit(testTableRecord1);
db.SubmitChanges();
db.GetTable<testTable>().InsertOnSubmit(testTableRecord2);
db.SubmitChanges();

Мой совет - создавать новый экземпляр контекста данных перед каждым вызовом SubmitChanges (), ИЛИ пакетировать ваши вставки в один SubmitChanges (это лучше всего, есливозможный).При использовании LINQ контекст данных обычно следует рассматривать как недолговечный одноразовый объект.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...