在插入时手动设置ID时发生错误DuplicateKeyException

时间:2019-02-04 07:44:43

标签: c# .net sql-server linq-to-sql

我有一个表,该表根据序列生成其主键:

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];

为了能够使用这种密钥生成机制,我选择使用this answer中提出的数据上下文的Insert部分方法。

对于第一个插入,它的工作就像一个魅力:在对象中执行插入操作并更新ID,但是一旦插入第二个对象,我就会得到一个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;
    }
}

我认为它在内部仍希望id0。如何强制LINQ反映真实ID?

PS:因为已将问题标记为LINQ to SQL insert primary key index的重复项:IsPrimaryKey = trueIsDbGenerated = true不起作用,因为它们使LINQ to SQL生成查询{ {1}} ID,即IDENTITY,如果ID是使用默认值SELECT CONVERT(Int,SCOPE_IDENTITY())创建的,则返回NULL

1 个答案:

答案 0 :(得分:0)

您可以通过为每个插入操作实例化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();

如果您想了解发生了什么,请尝试在断点插入表TestTest方法内放置一个断点。您将看到在第二次调用SubmitChanges()之后没有调用它。数据上下文的每个实例都维护一个缓存,其中包含插入,更新或检索的每个实体。缓存的行为就像字典一样,使用实体的主键作为字典键。由于某种原因,LINQ对每个缓存的实体仅运行一次自定义插入逻辑。 IMO,这是LINQ中的错误,我找不到任何证明这种行为的文档。

要了解我的意思,请尝试将每个实体的ID设置为单独的值,然后您会看到自定义插入行为实际上可以正常工作:

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时,通常应将数据上下文视为短期的,可抛弃的对象。