"Нарушение ограничения PRIMARY KEY '...'. Невозможно вставить повторяющийся ключ в объект - PullRequest
0 голосов
/ 13 апреля 2020

Я только начал создавать свой первый проект с Codefirst-Approach с C#, Linq и MSSQLSERVER и столкнулся с проблемой при попытке вставить новую запись DB, которая содержит ссылку на уже существующий элемент из другой таблицы.

InnerException {"Нарушение ограничения PRIMARY KEY 'PK_dbo.Manufacturers'. Невозможно вставить дубликат ключа в объект 'dbo.Manufacturers'. Значение дублирующего ключа равно (1d262e43-b9b6-4752-9c79-95d955d460ab ). \ r \ nОпределение завершено. "} System.Exception {System.Data.SqlClient.SqlException}

Я разбил проблему до простого проекта, который я буду загружать в общий ресурс. , Моя структура данных содержит класс. Продукт, который ссылается на объект «Производитель» и список возможных поставщиков.

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

Следующий метод используется для хранения / обновления записей в БД

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

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

к моему объекту данных, но у меня не было бы идеи, как это сделать с моим списком возможных поставщиков ??

Может кто-нибудь подсказать sh меня в правильном направлении? *

!! Большое спасибо за быстрые повторы !!

Я обновил свою DataStructure следующим образом:

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

Но я все еще получаю «Нарушение ограничения PRIMARY KEY» PK_dbo .Производители ". Не удается вставить дубликат ключа ..." ошибка при попытке вставить вторую запись.

Я прикрепил, как выглядит DB в SQL -Server

Ответы [ 2 ]

0 голосов
/ 14 апреля 2020

, как упомянуто в командах, я хотел бы поделиться моим обновленным и рабочим проектом для дальнейшего использования ....

MainWindow, которое генерирует тестовые данные и выполняет чтение / запись в 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: Из-за того, что между Продуктом и Поставщиком существует связь между многими, я должен добавить

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

к классу Поставщика. Я также решил добавить коллекцию Продуктов к своему производителю, чтобы сделать некоторые запросы запросов более удобными

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

    }

Перед добавлением нового продукта в БД важно загрузить возможных БД / Поставщиков из БД и присвойте их текущему продукту.

Добавление новых продуктов теперь работает нормально, но, как вы можете видеть, загрузка и назначение возможных поставщиков не очень хороши. Поэтому я постараюсь внести некоторые изменения в этот процесс в ближайшие дни .... Я вернусь, если найду «решение».

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

Я закончил тем, что написал методы, которые обрабатывают решение между обновлением / вставкой для каждого отдельного дочернего элемента, потому что я не нашел способ обновления возможных офлайн-изменений уже существующих dbEntries путем одновременного добавления несуществующих dbEntries. Основная проблема заключалась в том, что я получил дублированные записи в БД при добавлении второго продукта. Странно было то, что это событие дубликатов нарушает уникальность 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 .

Дополнительно я хотел бы поделиться следующей ссылкой. Возможно глава Пример 4.18: Создание универсального c метода, который может применять состояние через любой график может помочь другим в реализации более удобного решения.

0 голосов
/ 13 апреля 2020

Хорошо, я думаю, что знаю, в чем твоя проблема. Это несколько связано с этой частью здесь:

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

Прямо сейчас, когда вы go вставляете второй продукт, он выдаст исключение дублирующегося ключа, потому что вы не ссылаются на существующую сущность в вашем контексте. Вы должны изменить его на что-то вроде следующего:

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 будет считать, что вы пытаетесь добавить нового, не ссылающегося на существующий.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...