Databasemigraties met mssql-django

In dit artikel wordt uitgelegd hoe Django's migratiesysteem werkt met SQL Server via de mssql-django backend en worden bekende randgevallen gedocumenteerd.

Migraties maken en toepassen

De migratiewerkstroom van Django werkt op dezelfde manier met SQL Server als bij andere databases:

  1. Migraties genereren van modelwijzigingen:

    python manage.py makemigrations myapp
    
  2. Controleer de gegenereerde migratiebestanden in <app>/migrations/.

  3. Migraties toepassen op de database:

    python manage.py migrate myapp
    
  4. Migratiestatus controleren:

    python manage.py showmigrations myapp
    

Initiële projectconfiguratie

Wanneer u een nieuw Django-project instelt met SQL Server, voert u migraties uit om de ingebouwde tabellen van Django te maken (verificatie, sessies, beheerder):

python manage.py migrate

Met deze opdracht worden alle tabellen gemaakt die vereist zijn voor apps die worden vermeld in INSTALLED_APPS.

Aangepaste SQL in migraties

Gebruik migrations.RunSQL dit om onbewerkte SQL-instructies uit te voeren tijdens migraties. Deze methode is handig voor het maken van opgeslagen procedures, triggers of andere SQL Server-specifieke objecten:

from django.db import migrations

class Migration(migrations.Migration):

    dependencies = [
        ("myapp", "0001_initial"),
    ]

    operations = [
        migrations.RunSQL(
            sql="CREATE INDEX IX_myapp_product_name ON myapp_product (name);",
            reverse_sql="DROP INDEX IX_myapp_product_name ON myapp_product;",
        ),
    ]

Bekende randgevallen bij migratie

Voor de volgende migratiebewerkingen zijn tijdelijke oplossingen nodig wanneer SQL Server als doeldatabase wordt gebruikt.

AutoField-wijziging

Het wijzigen van een modelveld van of naar AutoField tijdens de migratie wordt niet ondersteund. SQL Server staat het toevoegen of verwijderen van de IDENTITY eigenschap niet toe uit een bestaande kolom.

Tijdelijke oplossing: Maak een nieuw model met het gewenste veldtype. Migreer gegevens van de oude tabel naar de nieuwe tabel en zet de oude tabel neer.

De naam van een veld of model wijzigen met foreign key-beperkingen

Het wijzigen van de naam van een veld of model met foreign key-beperkingen kan mislukken. SQL Server moet FK-beperkingen verwijderen en opnieuw maken tijdens hernoembewerkingen.

Tijdelijke oplossing: Gebruik migrations.SeparateDatabaseAndState om de FK-constraint te verwijderen, de kolom te hernoemen en de constraint opnieuw aan te maken, en geef daarbij aan dat Django de modelstatus moet bijwerken. In het volgende voorbeeld wordt de product vreemde sleutel op een Order-model hernoemd naar item:

from django.db import migrations

class Migration(migrations.Migration):

    dependencies = [
        ("myapp", "0002_previous"),
    ]

    operations = [
        migrations.SeparateDatabaseAndState(
            database_operations=[
                migrations.RunSQL(
                    sql="ALTER TABLE myapp_order DROP CONSTRAINT FK_order_product;",
                    reverse_sql="ALTER TABLE myapp_order ADD CONSTRAINT FK_order_product FOREIGN KEY (product_id) REFERENCES myapp_product(id);",
                ),
                migrations.RunSQL(
                    sql="EXECUTE sp_rename 'myapp_order.product_id', 'item_id', 'COLUMN';",
                    reverse_sql="EXECUTE sp_rename 'myapp_order.item_id', 'product_id', 'COLUMN';",
                ),
                migrations.RunSQL(
                    sql="ALTER TABLE myapp_order ADD CONSTRAINT FK_order_item FOREIGN KEY (item_id) REFERENCES myapp_product(id);",
                    reverse_sql="ALTER TABLE myapp_order DROP CONSTRAINT FK_order_item;",
                ),
            ],
            state_operations=[
                migrations.RenameField(
                    model_name="order",
                    old_name="product",
                    new_name="item",
                ),
            ],
        ),
    ]

Zoek de werkelijke naam van de beperking in uw database op voordat u deze T-SQL-code uitvoert. Django genereert constraintnamen met een korte hash, waardoor de naam in uw schema niet overeenkomt met de placeholder die hier wordt weergegeven.

Migraties squashen

Nadat veel migraties zich hebben verzameld, kunt u ze in minder bestanden opnemen:

python manage.py squashmigrations myapp 0001 0010

Tip

Test gestaakte migraties altijd op basis van een nieuwe database om ervoor te zorgen dat ze het juiste schema produceren.

Gegenereerde kolommen (berekende kolommen)

De mssql-django backend ondersteunt Django's GeneratedField (Django 5.0 en later), die overeenkomt met berekende kolommen in SQL Server.

Opgeslagen (PERSISTENTED) gegenereerde kolommen

Een opgeslagen gegenereerde kolom wordt fysiek naar schijf geschreven en bijgewerkt wanneer de bronkolommen veranderen:

from django.db import models
from django.db.models import F

class Product(models.Model):
    price = models.DecimalField(max_digits=10, decimal_places=2)
    tax_rate = models.DecimalField(max_digits=5, decimal_places=4)
    total_price = models.GeneratedField(
        expression=F("price") * (1 + F("tax_rate")),
        output_field=models.DecimalField(max_digits=10, decimal_places=2),
        db_persist=True,
    )

Hiermee wordt het volgende gegenereerd: total_price AS ([price] * (1 + [tax_rate])) PERSISTED.

Virtuele gegenereerde kolommen

Een virtuele gegenereerde kolom wordt berekend op het moment van de query en verbruikt geen opslag:

from django.db import models
from django.db.models import F, Value
from django.db.models.functions import Concat

class Employee(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    full_name = models.GeneratedField(
        expression=Concat(F("first_name"), Value(" "), F("last_name")),
        output_field=models.CharField(max_length=101),
        db_persist=False,
    )

Note

SQL Server beperkt indexen op niet-persistente berekende kolommen. Gebruik db_persist=True deze optie als u de gegenereerde kolom moet indexeren.

Tabel- en kolomopmerkingen

De mssql-django back-end ondersteunt de functie van db_comment Django (Django 4.2 en hoger). Opmerkingen worden opgeslagen als MS_Description uitgebreide eigenschappen op het SQL Server-object.

Tabelopmerkingen

class AuditLog(models.Model):
    action = models.CharField(max_length=50)
    timestamp = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table_comment = "Tracks user actions for compliance auditing."

Kolomopmerkingen

class Measurement(models.Model):
    value = models.FloatField(db_comment="Sensor reading in Celsius")
    recorded_at = models.DateTimeField(db_comment="UTC timestamp from the data logger")

Opmerkingen zijn zichtbaar in SQL Server Management Studio onder kolom-/tabeleigenschappen en via sys.extended_properties.

Samengestelde primaire sleutels

Django 5.2 geïntroduceerd CompositePrimaryKey. De mssql-django back-end biedt gedeeltelijke ondersteuning voor samengestelde primaire sleutels, maar sommige Django-testcases worden nog steeds uitgesloten. Valideer migraties en query's met samengestelde sleutels voor uw toepassing voordat u ze in productie neemt.

  • inspectdb genereert samengestelde primaire sleutels niet correct. Definieer ze handmatig na inspectie.
  • Tuple-zoekacties worden niet ondersteund. In de back-end worden samengestelde sleutelvergelijkingen opgesplitst in afzonderlijke kolomvoorwaarden.
  • Voor vergelijking van tuples met subquery’s zijn Django 5.2.4 en nieuwere versies vereist.
  • Sommige migratiebewerkingen hebben nog steeds bekende uitsluitingen. Zie Beperkingen en niet-ondersteunde functies in mssql-django voor de huidige status.
from django.db import models
from django.db.models import CompositePrimaryKey

class OrderItem(models.Model):
    pk = CompositePrimaryKey("order_id", "product_id")
    order = models.ForeignKey("Order", on_delete=models.CASCADE)
    product = models.ForeignKey("Product", on_delete=models.CASCADE)
    quantity = models.IntegerField()

IDENTITY_INSERT Behandeling

Wanneer u in een AutoField expliciete waarden invoegt (bijvoorbeeld bij het herstellen van gegevens uit een back-up met specifieke ID's), verpakt de backend de invoegbewerking automatisch in SET IDENTITY_INSERT ON / SET IDENTITY_INSERT OFF. Er is geen handmatige SQL nodig.

# The backend handles IDENTITY_INSERT automatically
Product.objects.create(id=42, name="Restored Widget", price=9.99)

Note

SQL Server staat toe dat slechts één tabel per sessie tegelijk IDENTITY_INSERT ON heeft. Als u expliciete ID’s in meerdere tabellen in één atomic()-blok invoegt, verwerkt de backend de omschakeling per statement. Gelijktijdige sessies die ook IDENTITY_INSERT op dezelfde tabel gebruiken, kunnen echter met elkaar conflicteren.