【问题标题】:EF Core - error with duplicated key while adding data with unique indexEF Core - 添加具有唯一索引的数据时出现重复键错误
【发布时间】:2019-03-21 07:13:29
【问题描述】:

我正在尝试使用 EF Core 向数据库添加数据,但我无法克服重复键错误:Cannot insert duplicate key row in object 'dbo.Stocks' with unique index 'IX_Stocks_Name'. The duplicate key value is (stock1).

我有实体StockTransaction 具有一对多关系 - 一个Stock 可以与许多Transactions 相关联。

仅当数据库中没有具有给定 ID 的 Stock 时才会出现问题 - 在这种情况下,我可以添加许多具有相同 StockIDTransactions 并且它可以工作:

using(var context = dbContextFactory.CreateDbContext(new string[0]))
     {
        List<Transaction> list = new List<Transaction>();
        //If there's no Stock with ID "stock1" in the database the code works.
        //Executing the code for the second time results in error, however, if I changed stock1 to stock2 it would work 
        var stock1 = new Stock("stock1");
        list.AddRange(new Transaction[]
        {
        //I can add two exactly the same `Transaction`s and it's ok when executed on a fresh database
        new Transaction(stock1, DateTime.Now, 1M, 5),
        new Transaction(stock1, DateTime.Now, 1M, 5),
        });
        context.Transactions.AddRange(list);
        context.SaveChanges();
     }

下面我添加了类的定义及其IEntityTypeConfigurations。

提前感谢任何提示。

Transaction类:

public class Transaction
    {
        public Stock RelatedStock { get; set; }
        public DateTime TransactionTime { get; set; }
        public Decimal Price { get; set; }
        public int Volume { get; set; }
        public Transaction() { }
    }

TransactionConfiguration类:

public class TransactionConfiguration : IEntityTypeConfiguration<Transaction>
    {
        public void Configure(EntityTypeBuilder<Transaction> builder)
        {
            builder
                .ToTable("Transactions");

            builder
                .Property<int>("TransactionID")
                .HasColumnType("int")
                .ValueGeneratedOnAdd()
                .HasAnnotation("Key", 0);

            builder
                .Property(transaction => transaction.Price)
                .HasColumnName("Price")
                .IsRequired();

            builder
                .Property(transaction => transaction.TransactionTime)
                .HasColumnName("Time")
                .IsRequired();

            builder
                .Property(transaction => transaction.Volume)
                .HasColumnName("Volume")
                .IsRequired();

            builder
                .HasIndex("RelatedStockStockID", nameof(Transaction.TransactionTime))
                .IsUnique();
        }
    }

Stock类:

public class Stock
  {
     public string Name { get; set; }
     public ICollection<Transaction> Transactions { get; set; }
     public Stock() { }
  }

StockConfiguration类:

public class StockConfiguration : IEntityTypeConfiguration<Stock>
    {
        public void Configure(EntityTypeBuilder<Stock> builder)
        {
            builder
                .ToTable("Stocks");

            builder
                .Property<int>("StockID")
                .HasColumnType("int")
                .ValueGeneratedOnAdd()
                .HasAnnotation("Key", 0);

            builder
                .Property(stock => stock.Name)
                .HasColumnName("Name")
                .HasMaxLength(25)
                .IsRequired();

            builder
                .HasMany(stock => stock.Transactions)
                .WithOne(transaction => transaction.RelatedStock)
                .IsRequired();

            builder
                .HasIndex(stock => stock.Name)
                .IsUnique();
        }
    }

【问题讨论】:

    标签: c# entity-framework ef-code-first entity-framework-core ef-code-first-mapping


    【解决方案1】:

    dbo.Stocks 表上有一个名为 IX_Stocks_Name 的唯一索引。你违反了这个索引。

    你的问题是这一行:

    var stock1 = new Stock("stock1");
    

    您一遍又一遍地创建“stock1”。相反,您应该首先检索(或存储)“stock1”的Stock 实体并使用它,如果它已经存在的话。如果它不存在,那么创建一个是安全的。

    简而言之,代码是用现有的NameINSERT 转换为dbo.Stocks

    【讨论】:

    • 至于你提到的那一行 - 我手动创建 Stock 是为了描述一个问题。实际上,我解析一个文本文件并将List&lt;Transaction&gt; 传递给函数,然后调用context.Transactions.AddRange(list) 当然,每只股票都被大量交易引用。您能否详细说明解决问题的可能方法?
    【解决方案2】:

    您需要检查是否存在已知的所需 ID 的 Stock。为了你的例子:

    var stock1 = context.Stocks.SingleOrDefault(x => x.StockId == "stock1") ?? new Stock("stock1");
    

    如果数据库中已有库存,这将关联现有库存,如果没有,则关联新库存。

    【讨论】:

    • 感谢您的提示。老实说,我希望 Entity Framework 能够自动以这种方式关联实体。
    • 自动执行此操作没有意义。使用自动生成的数字或 guid 键来避免此问题。
    【解决方案3】:

    感谢@Zer0 和@Steve Py 的建议,我想出了以下解决方案:

    void Insert(IEnumerable<Transaction> data)
            {
                using(var context = dbContextFactory.CreateDbContext(new string[0]))
                {
                    List<Stock> stocks = data.Select(s => s.RelatedStock).Distinct(new StockComparer()).ToList();
                    context.AddRange(stocks);
                    context.SaveChanges();
                    stocks = context.Stocks.ToList();
    
                    List<Transaction> newList = new List<Transaction>(data.Count());
    
                    foreach (var t in data)
                    {
                            Stock relatedStock = stocks.Where(s => s.Name == t.RelatedStock.Name).First();
                            t.RelatedStock = relatedStock;
                            newList.Add(t);
                    }
    
                    context.Transactions.AddRange(newList);
                    context.SaveChanges();
                }
            }
    

    【讨论】:

      猜你喜欢
      • 2017-01-11
      • 2020-12-27
      • 2022-11-19
      • 2015-03-22
      • 2022-07-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多