Django 无法使用多个主键修改 SQL Server 中的一行

Posted

技术标签:

【中文标题】Django 无法使用多个主键修改 SQL Server 中的一行【英文标题】:Django unable to modify a row in SQL Server with multiple primary keys 【发布时间】:2021-08-26 14:22:42 【问题描述】:

我正在使用 Django 3.0DjangoRestFramework 3.12django-mssql-backend 2.8.1 构建当前桌面应用程序的 webapp 版本,该版本将所有内容存储在旧版 MS SQL Server 数据库中。因此,我仅限于不改变现有表结构的数据库修改。

我使用内置的遗留数据库模型生成器构建了我的 Django 模型,然后自己进行了最终修改。我一直在关注教程来构建一个 API 来处理数据,每个人似乎都建议使用 DRF。我所有的视图都使用ListAPIViewRetrieveAPIViewRetrieveUpdateAPIView。现在,当我尝试构建 API 的一部分以允许我更改其中一个表中的设置时,我遇到了关于插入重复键值的错误。

数据库表:

dbo.systemDateTimeSettings
  - LocationID (PK, FK, int, not null)  
  - Setting (PK, nvarchar(50), not null)
  - Value (datetime, null)

型号:

class SystemDatetimeSettings(models.Model):
    location_id = models.OneToOneField(Locations, models.DO_NOTHING, db_column='LocationID', primary_key=True)
    setting = models.CharField(db_column='Setting', max_length=50)
    value = models.DateTimeField(db_column='Value', blank=True, null=True)
    
    def get_api_url(self, request=None):
        return reverse("api:datetime-settings-update",
                        kwargs=
                            'location_id': int(self.location_id.location_id),
                            'setting': self.setting
                        ,
                        request=request)

    class Meta:
        managed = False
        db_table = 'SystemDateTimeSettings'
        unique_together = (('location_id', 'setting'),)

序列化器:

class SystemDatetimeSettingsSerializer(serializers.ModelSerializer):
    url = serializers.SerializerMethodField(read_only=True)
    
    class Meta:
        model = SystemDatetimeSettings
        fields = [
            'url',
            'location_id',
            'setting',
            'value'
        ]
        read_only_fields = [
            'location_id',
            'setting',
        ]
        
    def get_url(self, obj):
        request = self.context.get("request")
        return obj.get_api_url(request=request)

网址:

path('locations/<int:location_id>/settings/datetime/<str:setting>/update', DatetimeSettingsUpdate.as_view(), name='datetime-settings-update'),

查看:

class DatetimeSettingsUpdate(RetrieveUpdateAPIView):
    lookup_field        = 'setting'
    serializer_class    = SystemDatetimeSettingsSerializer
    permission_classes  = [permissions.IsAuthenticated]
    # queryset            = SystemDatetimeSettings.objects.all()
    
    def get_object(self):
        location_id = self.kwargs.get('location_id')
        setting = self.kwargs.get('setting')
        return get_object_or_404(SystemDatetimeSettings, location_id=location_id, setting=setting)

我得到的错误是:

IntegrityError at /api/locations/3/settings/datetime/Next Measurement/update
('23000', "[23000] [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]Violation of PRIMARY KEY constraint 'aaaaaSystemDateTimeSettings_PK'. Cannot insert duplicate key in object 'dbo.SystemDateTimeSettings'. The duplicate key value is (3, Next Measurement). (2627) (SQLExecDirectW); [23000] [Microsoft][ODBC Driver 13 for SQL Server][SQL Server]The statement has been terminated. (3621)")

我从所有这些中了解到,真正的潜在问题是 SQL Server 数据库正在使用 LocationID 和 Setting 两者作为主键,但 Django 不允许多个主键。

当我前往那个 URL 时,我可以拉下一个实例。但是当我去更改值时,我只是遇到了关于插入重复键的错误;这很不寻常,因为我没有创建一个新的键值,只是试图修改一个预先存在的键值。

我在这里查看了人们在数据库中有多个主键的其他实例,例如 here、here 和 here,但他们似乎都提到使用 unique_together,我有,但不要提及如何利用该约束。

有没有办法解决这个问题?或者我是否需要以某种方式修改旧数据库?

编辑: 我在 SQL Server 中编写了aaaaaSystemDateTimeSettings_PK 约束并得到:

ALTER TABLE [dbo].[SystemDateTimeSettings] ADD  CONSTRAINT [aaaaaSystemDateTimeSettings_PK] PRIMARY KEY CLUSTERED 
(
    [LocationID] ASC,
    [Setting] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
GO

我尝试删除约束(本地副本 - 不要惊慌),然后尝试更新页面。它通过但覆盖了每个SettingValue,它们具有相同的LocationID,具有相同的setting 名称和新的value。它似乎只是为了防止 LocationIDSetting 一起重复。

为了让我对我想要做的事情一清二楚,这是我的桌子之前的样子:

Before modifications

这是我希望能够对更新页面执行的操作:

After modifying the database

【问题讨论】:

【参考方案1】:

您的错误是告诉您您正在尝试添加/更新数据库中的一行,该行已经存在于 models.py 中的 unique_together = (('location_id', 'setting'),) 约束为避免这种情况,您必须确保您正在添加/更新的值不已经存在于数据库中。在您的情况下,错误告诉您 location_id=3setting='Next Measurement' 已经存在。

要解决此问题,请删除约束并运行迁移,或者在添加/更新这些字段时处理 Integrity 错误。

既然你说

LocationID 和 Setting 都作为主键

您还需要将设置字段更新为唯一。

setting = models.CharField(db_column='Setting', max_length=50, unique=True)

【讨论】:

location_idsetting 都是主键,被更改的值是value。因此,如果我要更新特定行,location_idsetting 将保持不变,但 value 将被更新。我是否误解了 unique_together 约束的工作原理? 不在这里,如果您只更改值字段,则不应出现此错误。表中是否可能已经存在location_id=3setting='Next Measurement' 的重复行?那么对这个问题行有任何更改,数据库是否会抛出这个错误? 可能值得仔细检查数据库中的约束aaaaaSystemDateTimeSettings_PK,以确保它符合您的预期。 我尝试将unique 添加到setting 并没有改变任何东西。我收到了同样的错误信息。 这更像是一个建议,因为它预计在表格中是独一无二的。

以上是关于Django 无法使用多个主键修改 SQL Server 中的一行的主要内容,如果未能解决你的问题,请参考以下文章

sql server 无法创建主键

多个模型 - 一个公共主键 (Django)

用SQL语句创建一个临时表,建立主键约束,并验证主键约束是不是可以被修改。

SQL中主键的使用

Django模型:使用多个值作为键?

我的Django模型中只有一个主键,但是它显示为多个主键时出错