1

Postgres database contains product table with natural primary key:

create table product 
(
    productcode char(10) primary key,
    price numeric(12,2),
    ... a lot of other columns
);

and other similar tables with natural primary keys.

ASP.NET 5 MVC application is used to update it using EF Core with Npgsql data provider.

If product code is also changed, EF Core throws error

The property 'Product.Productcode' is part of a key and so cannot be modified or marked as modified. To change the principal of an existing entity with an identifying foreign key, first delete the dependent and invoke 'SaveChanges', and then associate the dependent with the new principal.

Product code is used as foreign key in other tables (with ON UPDATE CASCADE clause) so it cannot deleted.

Database structure change is not an option.

How to allow update of the primary key column also?

Some ideas:

  1. Block this check in EF Core, e.q setting old value to same as new value forcibly before update.

  2. Set primary key to some other value before saving changes.

  3. Force EF Core to create update statement and execute it manually

  4. Use some EF Core extension or other framework.

  5. Change Npgsql EF core provider to allow this.

Which is best way to implement this without changing database structure?

Exception is thrown in line

https://github.com/dotnet/efcore/blob/f62cf1b1fa45d6026e8f98113d6d6712d81094c3/src/EFCore/ChangeTracking/Internal/ChangeDetector.cs#L107

   private static void ThrowIfKeyChanged(InternalEntityEntry entry, IProperty property)
    {
        if (property.IsKey()
            && property.GetAfterSaveBehavior() == PropertySaveBehavior.Throw)
        {
            throw new InvalidOperationException(CoreStrings.KeyReadOnly(property.Name, entry.EntityType.DisplayName()));
        }
    }

Is it OK to comment this out and compile new EF Core dll.

Serge
  • 40,935
  • 4
  • 18
  • 45
Andrus
  • 26,339
  • 60
  • 204
  • 378
  • 2
    Option #6: stop updating the primary key - that's usually a sign of a really bad design - the PK **ought to be stable and immutable** – marc_s Oct 23 '21 at 16:06
  • Users want to change product codes. Business logic requires it. This cannot disabled. – Andrus Oct 23 '21 at 16:16
  • 1
    Bad database design choice then. Updateable PKs smell. Yes, many databases supports it (mostly for historical reasons), but it doesn't fit in EF Core change tracking/entity identity system, hence is not supported. In other words, you won't get help from EF Core for this. – Ivan Stoev Oct 23 '21 at 16:32
  • Sometimes users want to merge two products into single product. How to implement this in EF Core: can replace all foregin keys with new product id automated or shoud manual sql statement used. – Andrus Oct 23 '21 at 22:07
  • As per above, updating a PK means your choice of key / assumptions about key stability have failed. The solution is to introduce a new [surrogate PK](https://stackoverflow.com/a/4813804/314291) (likely narrower than 12 bytes of current one) and retrofit this across all foreign keys. Productcode should then be a unique key constraint / index. This is obviously quite a design change, but it's the price of assuming a natural key existed where it didn't. – StuartLC Oct 24 '21 at 07:48
  • Exception is thrown at line https://github.com/dotnet/efcore/blob/f62cf1b1fa45d6026e8f98113d6d6712d81094c3/src/EFCore/ChangeTracking/Internal/ChangeDetector.cs#L107 Is it OK to comment this out and create new dll from source code or can this check blocked? There is if statements for this calls. Maybe it is possible to change entity state temporarily so it is not called. – Andrus Oct 27 '21 at 06:23

1 Answers1

2

Since you are changing a primary key , as a matter of fact it is not updating , but adding a new product. So

  1. Create a new product from existing one, that will have a new product code

  2. Update ALL items from all tables that have the previous product code, replacing the previous foreign key, with the new one.

  3. After this you can delete the previous product.

If you try to turn off validation and change the code, after this your db will be broken, and you will not be able to use it again, since you will constantly have the integration error.

Serge
  • 40,935
  • 4
  • 18
  • 45
  • How to use EF Core to automate steps 1-3 ? There are number of such tables and database stucture can change. Creating and maintainging manually different sql commands for every table is lot of work. Database has strong integrity implemented using foreign keys with ON UPDATE CASCADE. It is not possible to broke it by EF Core. – Andrus Oct 23 '21 at 16:09
  • Solution may be to execute raw sql `UPDATE product SET productcode='NEW' WHERE productcode='OLD' ` and after that use new product code to retrieve product for update – Andrus Oct 23 '21 at 16:21
  • @Andrus I would create sql script for this, since you need a transaction. If you want to use EF , then I would create stored procedure if it is possible and run from stored procedure , or run just sql raw script. – Serge Oct 23 '21 at 17:13
  • How to add this raw sql to the same EF Core transaction which retrieves product with new id and updates it. It looks like stored procedure or raw sql can run only in separate transaction. It looks like full update statement needs to be generated manually. I asked for a tool which will generate it automatically. – Andrus Oct 23 '21 at 22:03
  • @Andrus You can include everytnig in one trasaction of stored procedure inside of the stored procedure. It will be the safest way. And you only need new product primary keys and previous product primery key as input parameters of stored procedure. – Serge Oct 23 '21 at 22:06
  • There are other columns which also needs to be changed when product code is changed. Product table has 250 columns. Should 250 parameters added to this stored procedure. – Andrus Oct 23 '21 at 22:11
  • @Andrus How do you do it using EF? Do you update all 250 properties? – Serge Oct 23 '21 at 22:16
  • You don't need to update all columns since you use syntax SET columnname=value. YOu can update just one for example. But you need to know which of them should be updated. – Serge Oct 23 '21 at 22:18
  • Columns to be changed are specified by user. Those can be any columns in table dependdin on user input in addition to primary key column. How to update them in single transaction without manually creating dynamic UPDATE command? – Andrus Oct 24 '21 at 06:38
  • Excetption is thrown in line https://github.com/dotnet/efcore/blob/f62cf1b1fa45d6026e8f98113d6d6712d81094c3/src/EFCore/ChangeTracking/Internal/ChangeDetector.cs#L107 . It it OK to comment this out and create new dll – Andrus Oct 24 '21 at 06:59