【问题标题】:Error DuplicateKeyException when setting the id manually on Insert在 Insert 上手动设置 id 时出错 DuplicateKeyException
【发布时间】:2019-02-04 07:44:42
【问题描述】:

我有一个从序列生成主键的表:

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 部分方法,正如 this answer 中所建议的那样。

对于第一个插入,它就像一个魅力:执行插入并在对象中更新 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 反映真实身份?

PS:因为该问题已被标记为 LINQ to SQL insert primary key index 的重复项:IsPrimaryKey = trueIsDbGenerated = true 不起作用,因为它们导致 LINQ to SQL 生成查询 IDENTITY ID 的代码,即 @ 987654334@,如果 ID 是使用默认值 SEQUENCE 创建的,则返回 NULL

【问题讨论】:

  • 评论不用于扩展讨论;这个对话是moved to chat
  • @YvetteColomb 现在这个问题将永远不会被回答,而相同的 cmets 将被其他人发布
  • @PanagiotisKanavos 这里的每个人都有代表聊天。当 30 + cmets 太多时,会出现一个点

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


【解决方案1】:

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

如果您想了解发生了什么,请尝试在部分 InsertTestTable 方法中放置一个断点。您会看到在第二次调用 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 时,数据上下文通常应被视为短暂的一次性对象。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-02
    • 1970-01-01
    • 2013-04-12
    • 2015-12-22
    • 1970-01-01
    相关资源
    最近更新 更多