it-swarm.com.de

SQL Server - Hinzufügen einer nicht nullbaren Spalte zu einer vorhandenen Tabelle - SSDT Publishing

Aufgrund der Geschäftslogik benötigen wir eine neue Spalte in einer Tabelle, die wichtig ist, um sicherzustellen, dass sie immer ausgefüllt ist. Daher sollte es als NOT NULL Zur Tabelle hinzugefügt werden. Im Gegensatz zu vorherige Fragen , die erklären, wie dies manuell gemacht wird, muss dies von der SSDT-Veröffentlichung verwaltet werden.

Ich habe meinen Kopf wegen dieser einfach klingenden Aufgabe wegen einiger Erkenntnisse eine Weile gegen die Wand geschlagen:

  1. Ein Standardwert ist nicht geeignet und kann keine berechnete Spalte sein. Vielleicht ist es eine Fremdschlüsselspalte, aber für andere können wir keinen falschen Wert wie 0 oder -1 verwenden, da diese Werte möglicherweise eine Bedeutung haben (z. B. numerische Daten).
  2. Das Hinzufügen der Spalte zu einem Skript vor der Bereitstellung schlägt beim Veröffentlichen fehl, wenn automatisch ein zweites Mal versucht wird, dieselbe Spalte zu erstellen (selbst wenn das Skript vor der Bereitstellung als idempotent geschrieben wurde). (Dieser ist wirklich ärgerlich, da ich mir sonst eine einfache Lösung vorstellen kann)
  3. Das Ändern der Spalte in NOT NULL in einem Skript nach der Bereitstellung wird jedes Mal zurückgesetzt, wenn die Aktualisierung des SSDT-Schemas erfolgt (sodass zumindest unsere Codebasis nicht mit der Quellcodeverwaltung und dem, was sich tatsächlich auf dem Server befindet, übereinstimmt).
  4. Das Hinzufügen der Spalte als jetzt nullbar mit der Absicht, in Zukunft auf NOT NULL zu ändern, funktioniert nicht über mehrere Zweige/Gabeln in der Quellcodeverwaltung hinweg, da die Zielsysteme beim nächsten Upgrade nicht unbedingt alle im selben Status haben müssen (nicht, dass dies sowieso ein guter Ansatz ist IMO)

Der Ansatz, den ich von anderen gehört habe, besteht darin, die Tabellendefinition direkt zu aktualisieren (damit die Schemaaktualisierung konsistent ist) und ein Skript vor der Bereitstellung zu schreiben, das den gesamten Inhalt verschiebt der Tabelle in eine temporäre Tabelle mit der neuen Spaltenpopulationslogik, um dann die Zeilen in einem Postdeployment-Skript zurück zu verschieben. Dies scheint jedoch verdammt riskant zu sein und ist immer noch verärgert über die Veröffentlichungsvorschau, wenn festgestellt wird, dass einer Tabelle mit vorhandenen Daten eine NOT NULL-Spalte hinzugefügt wird (da diese Validierung vor dem Scripting vor der Bereitstellung ausgeführt wird).

Wie kann ich eine neue, nicht nullfähige Spalte hinzufügen, ohne verwaiste Daten zu riskieren oder Daten bei jeder Veröffentlichung mit langwierigen Migrationsskripten, die von Natur aus riskant sind, hin und her zu verschieben?

Vielen Dank.

10
Elaskanator

Ich werde erzählen, wie ich das in der Vergangenheit gemacht habe. Es wurde entwickelt, um die spezifische Einschränkung von Skripten vor der Bereitstellung zu lösen, die Sie in Ihrem zweiten Punkt aufrufen:

Das Hinzufügen der Spalte zu einem Skript vor der Bereitstellung schlägt beim Veröffentlichen fehl, wenn automatisch ein zweites Mal versucht wird, dieselbe Spalte zu erstellen (selbst wenn das Skript vor der Bereitstellung als idempotent geschrieben wurde).

Warum Skripte vor der Bereitstellung hierfür nicht funktionieren

Wenn Sie ein SSDT-Projekt bereitstellen, sieht es folgendermaßen aus (etwas vereinfacht, aber im Allgemeinen):

  1. Führen Sie den "Schema-Vergleich" zwischen der Quelle (Dacpac-Datei) und dem Ziel (Datenbank) durch.
  2. Generieren Sie ein Bereitstellungsskript basierend auf den Ergebnissen dieses Vergleichs
  3. Verarbeiten Sie alle Skripts vor der Bereitstellung im Dacpac (Token-Ersetzung usw.) und fügen Sie den Inhalt am Anfang des Bereitstellungsskripts ein
  4. Machen Sie dasselbe für Skripte nach der Bereitstellung und hängen Sie sie an das Ende des Bereitstellungsskripts an

Wenn eine neue Spalte im Dacpac und nicht in der Zieldatenbank vorhanden ist, generiert Schritt 2 Code zum Hinzufügen dieser Spalte. Wenn das Skript vor der Bereitstellung diese Spalte hinzufügt, schlägt der Hauptteil des Skripts fehl (da davon ausgegangen wird, dass die Spalte nicht vorhanden ist, basierend auf den Ergebnissen des Schema-Vergleichs in Schritt 1).

Lösung: Pre-SSDT-Skript

Martin Smith erwähnte diese Option in einem Kommentar und es ist die Lösung, die bisher für mich am besten funktioniert hat:

Wir verwenden Premodel-Skripte in unserer Deplyment-Pipeline. Dies ist nicht Teil von SSDT, sondern ein Schritt, der vor der Veröffentlichung von dacfx ausgeführt wird. In diesem Fall könnte das Premodel-Skript die Spalte mit den gewünschten Werten hinzufügen und nicht null machen. Zum Zeitpunkt der Veröffentlichung befindet sie sich bereits in dem von SSDT erwarteten Zustand, sodass sie nichts zu tun hat. Ich habe noch nicht viel Verwendung für vorbereitete Skripte gefunden. - Martin Smith1. Juni um 21:45

Die Schritte zur Implementierung dieser Lösung im Allgemeinen sind:

  1. Erstellen Sie im SSDT-Projekt ein Skript für Ihren T-SQL-Code "vor SSDT"
    • Abhängig von der Funktionsweise Ihres Bereitstellungsprozesses sollte der Code in diesen Dateien wahrscheinlich idempotent sein
  2. Stellen Sie sicher, dass dieses Skript auf "Aktion erstellen = Keine" und "In Ausgabeverzeichnis kopieren = Immer kopieren" eingestellt ist
    • die Option "Immer kopieren" ist besonders wichtig, da der Bereitstellungsprozess dieses Skript in Ihren Bereitstellungsartefakten finden muss
  3. Suchen Sie in Ihrem Bereitstellungsprozess dieses Skript (oder diese Skripte) und führen Sie es aus, bevor der Vergleich des SSDT-Schemas erfolgt
  4. Sobald dieses Skript erfolgreich ausgeführt wurde, können Sie DacServices/DacFx/Whatever aktivieren, um Ihre Bereitstellung wie gewohnt abzuschließen

Auf diese Weise können Sie die Spalte im Pre-SSDT-Skript mit einem beliebigen benutzerdefinierten Code hinzufügen, der mit komplexer Geschäftslogik gefüllt ist.

Sie fügen auch die Spaltendefinition im SSDT-Projekt hinzu (sodass die Quellcodeverwaltung immer noch dem tatsächlichen Status der Datenbank entspricht). Wenn der Schema-Vergleich ausgeführt wird, werden keine Änderungen in Bezug auf diese Spalte angezeigt (da Sie sie bereits bereitgestellt haben).

Andere Verwendungen von Pre-SSDT

Beim Testen von Bereitstellungen stelle ich häufig fest, dass SSDT eine "Tabellenwiederherstellung" * ausführt, wenn dies völlig unnötig ist. Hier wird eine neue Tabelle mit dem aktualisierten Schema erstellt, alle Daten werden in diese Tabelle kopiert, die alte Tabelle wird gelöscht und die neue Tabelle wird umbenannt, um die alte Tabelle zu ersetzen.

Dies kann zu massivem Wachstum der Transaktionsprotokolldatei und anderen Problemen führen, wenn die Tabelle groß ist. Wenn ich bemerke, dass eine Schemaänderung dies verursacht, nehme ich die Änderung stattdessen selbst vor der SSDT vor (was normalerweise eine einfache ALTER TABLE - Anweisung ist) und vermeide die Neuerstellung der Tabelle.

Ist das eine gute Idee?

Ich glaube schon. Wenn Sie Kritisieren von zwei verschiedenen Ansätzen zur Bereitstellung von Datenbanken: Migrationen gegen Status von Alex Yates lesen, kombiniert dies im Wesentlichen die beiden Ansätze ein wenig. SSDT ist zustandsbasiert, aber wir integrieren einen Migrationsschritt (vor SSDT), um einige der komplexeren Szenarien zu behandeln, mit denen SSDT einfach nicht allgemein umgehen kann.

Wenn Sie beim Schreiben dieser Antwort nach etwas suchen, ist dies ein sehr häufiger Ansatz, der in der SSDT-Benutzergemeinschaft diskutiert wird, sobald Sie wissen, wonach Sie suchen müssen. Ich habe gesehen, dass es heißt:

  • vorvergleichen
  • vormodell
  • pre-DAC
  • pre-SSDT

Hier ist ein großartiger Artikel, der viele der Punkte behandelt, die ich oben erwähnt habe:

Pre-Compare & Pre-Deployment-Skripte für SSDT

Und eine von Red Gate (im Abschnitt # 4 - Wechsel vom Systemtyp zum benutzerdefinierten Typ ), die dies auch als Vorvergleich bezeichnet:

So beheben Sie zehn SSDT-Bereitstellungsprobleme mit oder ohne ReadyRoll

Was ist der Sinn von Skripten vor der Bereitstellung?

Martin weist darauf hin, dass er " nicht viel Verwendung für vorbereitete Skripte gefunden hat. " Ich neige dazu, genauso zu denken. Es gibt jedoch Szenarien, in denen sie nützlich sein können.

Ein Beispiel, auf das mich ein Mitarbeiter hingewiesen hat, war das Speichern einiger Daten in einer temporären Tabelle, die im Skript nach der Bereitstellung verwendet werden sollen (sagen wir, Sie verschieben eine Spalte von einer Tabelle in eine andere).


* Der Tabellenumbau sieht so aus, was schrecklich ist, oder?

GO
PRINT N'Starting rebuilding table [dbo].[MyTable]...';


GO
BEGIN TRANSACTION;

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

SET XACT_ABORT ON;

CREATE TABLE [dbo].[tmp_ms_xx_MyTable] (
    [Id] BIGINT IDENTITY (1, 1) NOT NULL,
    -- etc, other columns
);

IF EXISTS (SELECT TOP 1 1 
           FROM   [dbo].[MyTable])
    BEGIN
        SET IDENTITY_INSERT [dbo].[tmp_ms_xx_MyTable] ON;
        INSERT INTO [dbo].[tmp_ms_xx_MyTable] ([Id], ...)
        SELECT   [Id],
                 -- etc, other columns
        FROM     [dbo].[MyTable]
        ORDER BY [Id] ASC;
        SET IDENTITY_INSERT [dbo].[tmp_ms_xx_MyTable] OFF;
    END

DROP TABLE [dbo].[MyTable];

EXECUTE sp_rename N'[dbo].[tmp_ms_xx_MyTable]', N'MyTable';

COMMIT TRANSACTION;

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
11
Josh Darnell