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

送分小仙女□ 提交于 2021-01-29 16:30:48

问题


I just started to make my first project with Codefirst-Approach with C#, Linq and MSSQLSERVER and run into an problem when trying to insert a new DB-entry that contains an reference to an already existing element from another 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}

I broke the problem down to a simple project that I will upload to a share. My data structure contains a class.Product that links to a Manufacturer object and a List of possible Suppliers.

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

I now generate 2 products.

  • Both products are produced by the same manufacturer.
  • The List of PossibleSuppliers does also contain same suppliers
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 };
        }

The following method is used for storing/updating the DB entries

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

    }

The insertion of the first product can be done without problems.

Also inserting additional products that will have unique manufacturers and suppliers will work without problem. **So I do not have the problem of uniqueness of my primary keys. **

I only receive this error, when I like to add a new entry that has a foreign key to an already existing entry.

Using dbObject.Products.AddOrUpdate(product); instead of dbObject.Products.Add(product); have not solved my problem. I am also not able to remove the manufacturer entry before adding the second product, because this will violate the foreign key of my first product… I found a possible solution for manufacturer by adding an additional property for ManufacturerId

        public Guid? ManuId { get; set; }
        [ForeignKey("ManuId")]
        public Manufacturer Manuf { get; set; }

to my data object, but I would not have an idea how to do this with my List PossibleSupplier??

Can someone please push me into the right direction?

!!Many thanks for the fast replays!!

I have updated my DataStructure as following:

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

But I still get the "Violation of PRIMARY KEY constraint 'PK_dbo.Manufacturers'. Cannot insert duplicate key..." error while trying to insert the second entry.

I have attached how the DB looks in SQL-Server


回答1:


Okay so I believe I know what your issue is. It lies somewhat with this portion here:

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

When you assign Manuf = manufacturer1 in both portions below it will work for the first insert because the manufacturer does not exist yet. Now the reason why on the second insert it does not work is because of your code below:

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

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

Right now when you go to insert the second product, it will throw the duplicate key exception because you are not referencing the existing entity within your context. You should change it to something like the following:

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

If you don't reference the existing manufacturer within the context and then assign it, EF will assume you are trying to add a new one not reference the existing one.




回答2:


as mentioned in the commands I'd like to share my updated and working project for further use....

MainWindow that generates the test data and executes read/write to DB

    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: Because of the many to many relation ship between Product and Supplier I have to add

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

to the Supplier class. I also decided to add a collection of Products to my manufacturer to make some query-calls more comfortable

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

    }

Before adding a new product to the DB it is important to load possible Manufacturer/Suppliers from the DB and assign them to the current product.

Adding new products is now working fine but as you can see the loading and assigning of the possible suppliers is not really handsome. Therefore I will try to make some modifications on this process in the upcoming days.... I will come back if I have found a "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
        };

    }

One way seams by using EntityStates but they do not work as expected --> I received duplicated entries in DB.

I have uploaded the current state of the project to the share - filename SqlTestporject_20200414_2027.zip

Br,

-------------------------- Update 2020-04-15 --------------------------

I ended up by writing methods that are handling the decision between update/insert for every single child on its own because I do not have found a way how to update possible offline-changes of already existing dbEntries by simultaneously adding not existing dbEntries. The main problem was that I received duplicated entries in the DB during adding the second product. The strange thing was, that this duplicates event violates the PK-uniqueness without an error/exception....

So I do have to call down the AddOrUpdate() methods until I reach the last child for my complete data structure.


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

This version can be found here - file SqlTestporject_20200415_1033.zip.

Additionally I'd like to share the following link. Maybe chapter Example 4.18: Creating a Generic Method That Can Apply State Through Any Graph can help others to implement a more comfortable solution.



来源:https://stackoverflow.com/questions/61186521/violation-of-primary-key-constraint-cannot-insert-duplicate-key-in-objec

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!