如何生成 sql 命令以使用其他列更新主键

Posted

技术标签:

【中文标题】如何生成 sql 命令以使用其他列更新主键【英文标题】:How to generate sql command to update primary key with other columns 【发布时间】:2021-12-09 20:32:45 【问题描述】:

Postgres 数据库包含带有自然主键的产品表:

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

以及其他具有自然主键的类似表。

ASP.NET 5 MVC 应用程序用于使用 EF Core 更新它 Npgsql 数据提供者。

如果产品代码也被更改,EF Core 会抛出错误

“Product.Productcode”属性是键的一部分,因此不能修改或标记为已修改。要使用标识外键更改现有实体的主体,首先删除依赖项并调用“SaveChanges”,然后将依赖项与新主体关联。

产品代码在其他表中用作外键(带有 ON UPDATE CASCADE 子句),因此无法删除。

不能更改数据库结构。

如何也允许更新主键列?

一些想法:

    在 EF Core 中阻止此检查,例如在更新之前将旧值强制设置为与新值相同。

    在保存更改之前将主键设置为其他值。

    强制 EF Core 创建更新语句并手动执行

    使用一些 EF Core 扩展或其他框架。

    更改 Npgsql EF 核心提供程序以允许这样做。

在不改变数据库结构的情况下实现这一点的最佳方法是什么?

异常抛出

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()));
        
    

是否可以将其注释掉并编译新的 EF Core dll。

【问题讨论】:

选项 #6:停止更新主键 - 这通常表明设计非常糟糕 - PK应该是稳定且不可变的 用户想要更改产品代码。业务逻辑需要它。这不能禁用。 那么糟糕的数据库设计选择。可更新的 PK 有异味。是的,许多数据库都支持它(主要是出于历史原因),但它不适合 EF Core 更改跟踪/实体身份系统,因此不受支持。换句话说,你不会从 EF Core 获得帮助。 有时用户希望将两个产品合并为一个产品。如何在 EF Core 中实现这一点:可以用新的产品 ID 自动替换所有外键,或者应该使用手动 sql 语句。 如上所述,更新 PK 意味着您选择的密钥/关于密钥稳定性的假设失败。解决方案是引入一个新的surrogate PK(可能小于当前的 12 个字节)并在所有外键中进行改进。然后 Productcode 应该是唯一的键约束/索引。这显然是一个相当大的设计更改,但这是假设自然键存在的代价。 【参考方案1】:

由于您正在更改主键,实际上它不是更新,而是添加新产品。所以

    从现有产品创建新产品,该产品将具有新产品代码

    更新所有具有先前产品代码的表中的所有项目,用新的外键替换先前的外键。

    之后您可以删除之前的产品。

如果您尝试关闭验证并更改代码,在此之后您的数据库将被破坏,您将无法再次使用它,因为您将不断遇到集成错误。

【讨论】:

如何使用 EF Core 自动化步骤 1-3 ?有许多这样的表和数据库结构可以改变。为每个表手动创建和维护不同的 sql 命令是很多工作。数据库具有强大的完整性,使用带有 ON UPDATE CASCADE 的外键实现。 EF Core 无法破坏它。 解决方案可能是执行原始 sql UPDATE product SET productcode='NEW' WHERE productcode='OLD' ,然后使用新产品代码检索产品以进行更新 @Andrus 我会为此创建 sql 脚本,因为您需要一个事务。如果您想使用 EF ,那么如果可能的话,我会创建存储过程并从存储过程运行,或者只运行 sql raw 脚本。 如何将此原始 sql 添加到同一 EF Core 事务中,该事务检索具有新 id 的产品并更新它。看起来存储过程或原始 sql 只能在单独的事务中运行。看起来需要手动生成完整的更新语句。我要了一个可以自动生成它的工具。 @Andrus 您可以在存储过程中的一个存储过程中包含每个tnig。这将是最安全的方式。并且您只需要新的产品主键和以前的产品主键作为存储过程的输入参数。

以上是关于如何生成 sql 命令以使用其他列更新主键的主要内容,如果未能解决你的问题,请参考以下文章

使用显式游标和循环更新列以生成唯一值

如何使用 SMO 和 SQL Server 生成脚本以删除列?

SQL:插入更新主键相同的行的所有其他列

数据库设计

Rails迁移以将主键添加到现有表

sql面试