“违反 PRIMARY KEY 约束 '...'。无法在对象中插入重复键

"Violation of PRIMARY KEY constraint '...'. Cannot insert duplicate key in object

我刚开始使用 C#、Linq 和 MSSQLSERVER 的 Codefirst-Approach 和 运行 来制作我的第一个项目,但在尝试插入包含对已存在元素的引用的新数据库条目时遇到了问题来自另一个 table.

InnerException {"Violation of PRIMARY KEY constraint 'PK_dbo.Manufacturers'. Cannot insert duplicate key in object 'dbo.Manufacturers'. The duplicate key value is (1d262e43-b9b6-4752-9c79-95d955d460ab).\r\nThe statement has been terminated."} System.Exception {System.Data.SqlClient.SqlException}

我将问题分解为一个我将上传到共享的简单项目。 我的数据结构包含一个链接到制造商对象的 class.Product 和一个可能的供应商列表。

public class Product
    {
        [Key]
        public Guid Id { get { return _id; } set { _id = value; } }
        private Guid _id = Guid.NewGuid();

        public string Name { get; set; }

        public Manufacturer Manuf { get; set; }

        public List<Supplier> PossibleSupplier { get { return _possibleSupplier; } set { _possibleSupplier = value; } }
        private List<Supplier> _possibleSupplier = new List<Supplier>();
    }


    public class Supplier
    {
        [Key]
        public Guid Id { get { return _id; } set { _id = value; } }
        private Guid _id = Guid.NewGuid();

        public string Name { get; set; }
    }


    public class Manufacturer
    {
        [Key]
        public Guid Id { get { return _id; } set { _id = value; } }
        private Guid _id = Guid.NewGuid();

        public string Name { get; set; }
    }

我现在生成 2 个产品。

private void GenerateProducts()
        {
            Manufacturer manufactuer1 = new Manufacturer() { Name = "mainManuf 1" };
            Supplier supplier1 = new Supplier() { Name = "first Supplier" };
            Supplier supplier2 = new Supplier() { Name = "second Supplier" };

            Product firstProduct = new Product() { Name = "Product 1", Manuf = manufactuer1, PossibleSupplier = new List<Supplier>() { supplier1, supplier2 } };
            Product secondProduct = new Product() { Name = "Product 2", Manuf = manufactuer1, PossibleSupplier = new List<Supplier>() { supplier1 } };
            productList_ = new List<Product>() { firstProduct, secondProduct };
        }

以下方法用于storing/updating 数据库条目

public static class DbHandler
    {
        public static bool StoreProduct(Product product)
        {
            using (ProductDbContext dbObject = new ProductDbContext())
            {
                try
                {
                    dbObject.Products.AddOrUpdate(product);
                    dbObject.SaveChanges();

                }
                catch (Exception ex)
                {
                    //
                    return false;
                }
            }
            return true;
        }
    }


    public class ProductDbContext : DbContext
    {
        public ProductDbContext()
        {
            Database.SetInitializer<ProductDbContext>(new DropCreateDatabaseAlways<ProductDbContext>());
            this.Database.Connection.ConnectionString = sqlConnection.ConnectionString;
        }

        public DbSet<Product> Products { get; set; }
        public DbSet<Supplier> Suppliers { get; set; }
        public DbSet<Manufacturer> Manufacturers { get; set; }

        private static SqlConnectionStringBuilder sqlConnection = new SqlConnectionStringBuilder()
        {
            DataSource = "localhost\MSSQLSERVER2019",   // update me 
            UserID = "",              // update me
            Password = "",      // update me
            InitialCatalog = "ProductDb",
            IntegratedSecurity = true
        };

    }

第一个产品的插入可以毫无问题地完成。

另外,插入具有唯一制造商和供应商的其他产品也可以正常工作。 **所以我没有主键唯一性的问题。 **

只有当我想添加一个具有外键的新条目到现有条目时,我才会收到此错误。

使用 dbObject.Products.AddOrUpdate(product); 而不是 dbObject.Products.Add(product); 并没有解决我的问题。 我也无法在添加第二个产品之前删除制造商条目,因为这会违反我第一个产品的外键...... 我通过为 ManufacturerId

添加一个额外的 属性 找到了 possible solution for manufacturer
        public Guid? ManuId { get; set; }
        [ForeignKey("ManuId")]
        public Manufacturer Manuf { get; set; }

到我的数据对象,但我不知道如何用我的 List PossibleSupplier 做到这一点??

有人可以把我推向正确的方向吗?

!!非常感谢您的快速回放!

我更新了我的数据结构如下:

public class Product
    {
        [Key]
        public Guid Id { get { return _id; } set { _id = value; } }
        private Guid _id = Guid.NewGuid();

        public string Name { get; set; }


        public virtual Manufacturer Manufacturer { get; set; }

        public virtual ICollection<Supplier> PossibleSupplier { get; set; }
    }




    public class Supplier
    {
        [Key]
        public Guid Id { get { return _id; } set { _id = value; } }
        private Guid _id = Guid.NewGuid();

        public string Name { get; set; }

        [ForeignKey("Product")]
        public Guid ProductId { get; set; }
        public virtual Product Product { get; set; }
    }


    public class Manufacturer
    {
        [Key]
        public Guid Id { get { return _id; } set { _id = value; } }
        private Guid _id = Guid.NewGuid();

        public string Name { get; set; }


        [ForeignKey("Product")]
        public Guid ProductId { get; set; }
        public virtual ICollection<Product> Product { get; set; }
    }

但是我在尝试插入第二个条目时仍然遇到 "Violation of PRIMARY KEY constraint 'PK_dbo.Manufacturers'. Cannot insert duplicate key..." 错误。

我附上了如何 DB looks in SQL-Server

好的,我相信我知道你的问题是什么。这部分有点在于此:

private void GenerateProducts()
    {
        Manufacturer manufactuer1 = new Manufacturer() { Name = "mainManuf 1" };
        Supplier supplier1 = new Supplier() { Name = "first Supplier" };
        Supplier supplier2 = new Supplier() { Name = "second Supplier" };

        Product firstProduct = new Product() { Name = "Product 1", Manuf = manufactuer1, PossibleSupplier = new List<Supplier>() { supplier1, supplier2 } };
        Product secondProduct = new Product() { Name = "Product 2", Manuf = manufactuer1, PossibleSupplier = new List<Supplier>() { supplier1 } };
        productList_ = new List<Product>() { firstProduct, secondProduct };
    }

当您在下面的两个部分中指定 Manuf = manufacturer1 时,它将适用于第一个插入,因为制造商尚不存在。现在,在第二次插入时它不起作用的原因是因为您的代码如下:

using (ProductDbContext dbObject = new ProductDbContext())
        {
            try
            {
                dbObject.Products.AddOrUpdate(product);
                dbObject.SaveChanges();

            }
            catch (Exception ex)
            {
                //
                return false;
            }
        }

现在当您插入第二个产品时,它会抛出重复键异常,因为您没有在上下文中引用现有实体。您应该将其更改为如下所示:

using (ProductDbContext dbObject = new ProductDbContext())
{
    try
    {
        //Need to check if the manufacturer already exists in the db, if it does
        //make sure your project references the EXISTING entity within your context
        var check = dbObjec.Manufacturer.Where(x => x.Id == product.Manufacturer.Id).FirstOrDefault();
        if (check != null)
            product.Manufacturer = check;

        dbObject.Products.Add(product);
        dbObject.SaveChanges();

    }
    catch (Exception ex)
    {
        //
        return false;
    }
}

如果您没有在上下文中引用现有制造商然后分配它,EF 将假定您正在尝试添加一个新制造商而不是引用现有制造商。

如命令中所述,我想分享我的更新和 工作 项目以供进一步使用....

生成测试数据并执行read/write到DB

的MainWindow
    public partial class MainWindow : Window
    {
        private List<Product> productList_;

        public MainWindow()
        {
            GenerateProducts();
            InitializeComponent();
        }

        private void InsertFirst_Click(object sender, RoutedEventArgs e)
        {
            DbHandler.StoreProduct(productList_[0]);
        }

        private void InsertSecond_Click(object sender, RoutedEventArgs e)
        {
            DbHandler.StoreProduct(productList_[1]);
        }

        private void Read_Click(object sender, RoutedEventArgs e)
        {
            var productList = DbHandler.GetAllProducts();
        }



        private void GenerateProducts()
        {
            Manufacturer manufactuer1 = new Manufacturer() { Name = "mainManuf 1" };
            Supplier supplier1 = new Supplier() { Name = "first Supplier" };
            Supplier supplier2 = new Supplier() { Name = "second Supplier" };
            Supplier supplier3 = new Supplier() { Name = "third Supplier" };

            Product firstProduct = new Product() { Name = "Product 1", Manufacturer = manufactuer1, PossibleSupplier = new List<Supplier>() { supplier1, supplier2 } };
            Product secondProduct = new Product() { Name = "Product 2", Manufacturer = manufactuer1, PossibleSupplier = new List<Supplier>() { supplier2, supplier3 } };
            productList_ = new List<Product>() { firstProduct, secondProduct };
        }


    }

DataStructure:由于 Product 和 Supplier 之间存在多对多关系,我必须添加

        [ForeignKey("Product")]
        public ICollection<Guid> ProductId { get; set; }
        public virtual ICollection<Product> Product { get; set; }

供应商class。我还决定向我的制造商添加一个产品集合,以使一些查询调用更加舒适

    public class Product
    {
        [Key]
        public Guid Id { get { return _id; } set { _id = value; } }
        private Guid _id = Guid.NewGuid();

        public string Name { get; set; }

        public virtual Manufacturer Manufacturer { get; set; }

        public virtual ICollection<Supplier> PossibleSupplier { get; set; }
    }




    public class Supplier
    {
        [Key]
        public Guid Id { get { return _id; } set { _id = value; } }
        private Guid _id = Guid.NewGuid();

        public string Name { get; set; }

        [ForeignKey("Product")]
        public ICollection<Guid> ProductId { get; set; }
        public virtual ICollection<Product> Product { get; set; }
    }


    public class Manufacturer
    {
        [Key]
        public Guid Id { get { return _id; } set { _id = value; } }
        private Guid _id = Guid.NewGuid();

        public string Name { get; set; }


        //only nice for reverse object from Man --> Product
        [ForeignKey("Product")]
        public ICollection<Guid> ProductId { get; set; }
        public virtual ICollection<Product> Product { get; set; }

    }

在将新产品添加到数据库之前,重要的是从数据库加载 Manufacturer/Suppliers 并将它们分配给当前产品。

添加新产品现在工作正常,但如您所见,可能的供应商的加载和分配并不是很漂亮。因此,我将在接下来的几天尝试对此过程进行一些修改....如果我找到 "solution".

,我会回来
    public static class DbHandler
    {

        public static List<Product> GetAllProducts()
        {
            using (ProductDbContext dbObject = new ProductDbContext())
            {
                //loading with childs and their reverse objects to products
                var productList = dbObject.Products.Include("Manufacturer").Include("Manufacturer.Product").Include("PossibleSupplier").Include("PossibleSupplier.Product").Where(i => i.Id != null).ToList();
                //loding with childs but without reverse objects
                //var productList = dbObject.Products.Include("Manufacturer").Include("PossibleSupplier").Where(i => i.Id != null).ToList();

                return productList;
            }

        }

        public static bool StoreProduct(Product product)
        {
            using (ProductDbContext dbObject = new ProductDbContext())
            {
                try
                {
                    //this does not solve the loading problem, even when property _id is changed to "private Guid _id = new Guid();
                    //dbObject.Entry(product).State = product.Id == new Guid() ? EntityState.Added : EntityState.Modified;
                    //dbObject.Entry(product.Manufacturer).State = product.Manufacturer.Id == new Guid() ? EntityState.Added : EntityState.Modified;
                    //foreach (var supplier in product.PossibleSupplier)
                    //{
                    //    dbObject.Entry(supplier).State = supplier.Id == new Guid() ? EntityState.Added : EntityState.Modified;                        
                    //}
                    //Therefore loading must be done manually


                    Guid manufacturerId = product.Manufacturer.Id;
                    //Need to check if the manufacturer already exists in the db, if it does
                    //make sure your project references the EXISTING entity within your context
                    var checkManuf = dbObject.Manufacturers.Where(x => x.Id == manufacturerId).FirstOrDefault();
                    if (checkManuf != null)
                        product.Manufacturer = checkManuf;



                    List<Supplier> dbSuppliers = new List<Supplier>();
                    foreach (var posSupplier in product.PossibleSupplier)
                    {
                        var checkSupplier = dbObject.Suppliers.FirstOrDefault(x => x.Id == posSupplier.Id);
                        if (checkSupplier != null)
                        {
                            dbSuppliers.Add(checkSupplier);
                        }
                    }

                    foreach (var dbSup in dbSuppliers)
                    {
                        product.PossibleSupplier.Remove(product.PossibleSupplier.Single(i => i.Id == dbSup.Id));
                        product.PossibleSupplier.Add(dbSup);
                    }


                    dbObject.Products.Add(product);
                    dbObject.SaveChanges();

                }
                catch (Exception ex)
                {
                    //
                    return false;
                }
            }
            return true;
        }
    }


    public class ProductDbContext : DbContext
    {
        public ProductDbContext()
        {
            Database.SetInitializer<ProductDbContext>(new DropCreateDatabaseIfModelChanges<ProductDbContext>());
            this.Database.Connection.ConnectionString = sqlConnection.ConnectionString;
        }

        public DbSet<Product> Products { get; set; }
        public DbSet<Supplier> Suppliers { get; set; }
        public DbSet<Manufacturer> Manufacturers { get; set; }

        private static SqlConnectionStringBuilder sqlConnection = new SqlConnectionStringBuilder()
        {
            DataSource = "localhost\MSSQLSERVER2019",   // update me 
            UserID = "",              // update me
            Password = "",      // update me
            InitialCatalog = "ProductDb",
            IntegratedSecurity = true
        };

    }

一种方法是使用 EntityStates 接缝,但它们没有按预期工作 --> 我在数据库中收到了重复的条目。

我已将项目的当前状态上传到共享 - 文件名 SqlTestporject_20200414_2027.zip

Br,

------------------------更新2020-04-15------------ --------------

我最终编写了处理 update/insert 之间的决定的方法,因为我没有找到一种方法来同时更新现有 dbEntries 的可能离线更改添加不存在的 dbEntries。 主要问题是我在添加第二个产品时在数据库中收到了重复的条目。奇怪的是,这个重复事件在没有 error/exception...

的情况下违反了 PK 唯一性

所以我必须调用 AddOrUpdate() 方法,直到我找到完整数据结构的最后一个子节点。


        public static Product AddOrUpdateProduct(Product product)
        {
            using (ProductDbContext dbObject = new ProductDbContext())
            {
                try
                {
                    product.Manufacturer = AddOrUpdateManufacturer(dbObject, product.Manufacturer);

                    List<Supplier> dbSupplierList = new List<Supplier>();
                    foreach (var supplier in product.PossibleSupplier)
                    {
                        dbSupplierList.Add(AddOrUpdateSupplier(dbObject, supplier));
                    }
                    product.PossibleSupplier.Clear();
                    product.PossibleSupplier = dbSupplierList;



                    if (product.Id == new Guid())
                    {
                        //add new product
                        dbObject.Products.Add(product);
                        dbObject.SaveChanges();
                        return product;
                    }
                    else
                    {
                        //update existing product
                        var dbProduct = dbObject.Products.Single(x => x.Id == product.Id);
                        dbProduct.Name = product.Name;
                        dbObject.SaveChanges();
                        return dbProduct;
                    }

                }
                catch (Exception)
                {

                    throw;
                }
            }
        }

        private static Supplier AddOrUpdateSupplier(ProductDbContext dbObject, Supplier supplier)
        {
            supplier.Address = AddOrUpdateAdress(dbObject, supplier.Address);

            if (supplier.Id == new Guid())
            {
                //add new product
                dbObject.Suppliers.Add(supplier);
                dbObject.SaveChanges();
                return supplier;
            }
            else
            {
                //update existing product
                var dbSupplier = dbObject.Suppliers.Single(x => x.Id == supplier.Id);
                dbSupplier.Name = supplier.Name;

                dbObject.SaveChanges();
                return dbSupplier;
            }
        }

        private static Manufacturer AddOrUpdateManufacturer(ProductDbContext dbObject, Manufacturer manufacturer)
        {
            manufacturer.Address = AddOrUpdateAdress(dbObject, manufacturer.Address);

            if (manufacturer.Id == new Guid())
            {
                //add new product
                dbObject.Manufacturers.Add(manufacturer);
                dbObject.SaveChanges();
                return manufacturer;
            }
            else
            {
                //update existing product
                var dbManufacturer = dbObject.Manufacturers.Single(x => x.Id == manufacturer.Id);
                dbManufacturer.Name = manufacturer.Name;

                dbObject.SaveChanges();
                return dbManufacturer;
            }
        }

        private static Address AddOrUpdateAdress(ProductDbContext dbObject, Address address)
        {
            if (address.Id == new Guid())
            {
                //add new product
                dbObject.Addresses.Add(address);
                dbObject.SaveChanges();
                return address;
            }
            else
            {
                //update existing product
                var dbAddress = dbObject.Addresses.Single(x => x.Id == address.Id);
                dbAddress.Street = address.Street;
                dbAddress.HouseNumber = address.HouseNumber;
                dbAddress.PLZ = address.PLZ;
                dbAddress.City = address.City;

                dbObject.SaveChanges();
                return dbAddress;
            }
        }

可以在此处找到此版本 - 文件 SqlTestporject_20200415_1033.zip

另外我想分享以下内容link。也许第 Example 4.18: Creating a Generic Method That Can Apply State Through Any Graph 章可以帮助其他人实施更舒适的解决方案。