Každý, kdo někdy vyvíjel aplikace využívající databázi, pravděpodobně čelil problému aktualizace struktury databáze, když je aplikace nasazena a aktualizována.
Nejběžnějším přístupem je vytvoření sady SQL skriptů pro úpravu struktury databáze z verze na verzi. Samozřejmě existují placené nástroje, ale ty ne vždy vyřeší problém plné automatizace aktualizace.
Technologie migrace, poprvé představená v Hibernate ORM a implementovaná v Linq, je velmi dobrá a pohodlná, ale zahrnuje strategii „nejdřív kód“ pro vývoj struktury databáze, což je u stávajících projektů velmi pracné a použití spouštěčů, uložených procedur a funkcí v databázi téměř znemožňuje přechod na strategii „code first“.
Tento článek navrhuje alternativní přístup k řešení tohoto problému – uložení referenční databázové struktury do souboru XML a automatické generování SQL skriptu na základě porovnání reference a stávající struktura. Takže, začněme…
Generování souboru XML se strukturou databáze
Budeme používat databázi DbSyncSample. Skript pro vytvoření databáze je uveden níže.
USE [DbSyncSample] GO /****** Object: Table [dbo].[Orders] Script Date: 06/01/2017 10:37:43 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TABLE [dbo].[Orders]( [Id] [int] IDENTITY(1,1) NOT NULL, [OrderNumber] [nvarchar](50) NULL, [OrderTime] [datetime] NULL, [TotalCost] [decimal](18, 2) NOT NULL, CONSTRAINT [PK_Orders] PRIMARY KEY CLUSTERED ( [Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO CREATE NONCLUSTERED INDEX [IX_Orders_OrderNumber] ON [dbo].[Orders] ( [OrderNumber] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] GO /****** Object: Table [dbo].[Details] Script Date: 06/01/2017 10:37:43 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TABLE [dbo].[Details]( [Id] [int] IDENTITY(1,1) NOT NULL, [Descript] [nvarchar](150) NULL, [OrderId] [int] NULL, [Cost] [decimal](18, 2) NOT NULL, CONSTRAINT [PK_Details] PRIMARY KEY CLUSTERED ( [Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO /****** Object: Trigger [Details_Modify] Script Date: 06/01/2017 10:37:43 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TRIGGER [dbo].[Details_Modify] ON [dbo].[Details] AFTER INSERT,UPDATE AS BEGIN UPDATE Orders SET TotalCost = s.Total FROM ( SELECT i.OrderId OId, SUM(d.Cost) Total FROM Details d JOIN inserted i ON d.OrderId=i.OrderId GROUP BY i.OrderId ) s WHERE Id=s.OId END GO /****** Object: Trigger [Details_Delete] Script Date: 06/01/2017 10:37:43 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TRIGGER [dbo].[Details_Delete] ON [dbo].[Details] AFTER DELETE AS BEGIN UPDATE Orders SET TotalCost = s.Total FROM ( SELECT i.OrderId OId, SUM(d.Cost) Total FROM Details d JOIN deleted i ON d.OrderId=i.OrderId GROUP BY i.OrderId ) s WHERE Id=s.OId END GO /****** Object: Default [DF_Details_Cost] Script Date: 06/01/2017 10:37:43 ******/ ALTER TABLE [dbo].[Details] ADD CONSTRAINT [DF_Details_Cost] DEFAULT ((0)) FOR [Cost] GO /****** Object: Default [DF_Orders_TotalCost] Script Date: 06/01/2017 10:37:43 ******/ ALTER TABLE [dbo].[Orders] ADD CONSTRAINT [DF_Orders_TotalCost] DEFAULT ((0)) FOR [TotalCost] GO /****** Object: ForeignKey [FK_Details_Orders] Script Date: 06/01/2017 10:37:43 ******/ ALTER TABLE [dbo].[Details] WITH CHECK ADD CONSTRAINT [FK_Details_Orders] FOREIGN KEY([OrderId]) REFERENCES [dbo].[Orders] ([Id]) GO ALTER TABLE [dbo].[Details] CHECK CONSTRAINT [FK_Details_Orders] GO
Vytvořte konzolovou aplikaci a propojte s ní balíček Shed.DbSync nuget-package.
Struktura databáze XML je následující:
class Program
{
private const string OrigConnString = "data source=.;initial catalog=FiocoKb;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework";
static void Main(string[] args)
{
// getting XML with the database structure
var db = new Shed.DbSync.DataBase(OrigConnString);
var xml = db.GetXml();
File.WriteAllText("DbStructure.xml", xml);
}
} Po spuštění programu vidíme v souboru DbStructure.xml následující:
<?xml version="1.0"?>
<DataBase xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="https://www.w3.org/2001/XMLSchema">
<Version>0</Version>
<Tables>
<Table Name="Orders" ObjectId="2137058649" ParentObjectId="0">
<Columns>
<Column Name="Id">
<ColumnId>1</ColumnId>
<Type>int</Type>
<MaxLength>4</MaxLength>
<IsNullable>false</IsNullable>
<IsIdentity>true</IsIdentity>
<IsComputed>false</IsComputed>
</Column>
<Column Name="OrderNumber">
<ColumnId>2</ColumnId>
<Type>nvarchar</Type>
<MaxLength>100</MaxLength>
<IsNullable>true</IsNullable>
<IsIdentity>false</IsIdentity>
<IsComputed>false</IsComputed>
</Column>
<Column Name="OrderTime">
<ColumnId>3</ColumnId>
<Type>datetime</Type>
<MaxLength>8</MaxLength>
<IsNullable>true</IsNullable>
<IsIdentity>false</IsIdentity>
<IsComputed>false</IsComputed>
</Column>
<Column Name="TotalCost">
<ColumnId>4</ColumnId>
<Type>decimal</Type>
<MaxLength>9</MaxLength>
<IsNullable>false</IsNullable>
<IsIdentity>false</IsIdentity>
<IsComputed>false</IsComputed>
</Column>
</Columns>
<Indexes>
<Index Name="PK_Orders">
<IndexId>1</IndexId>
<Type>CLUSTERED</Type>
<IsUnique>true</IsUnique>
<IsPrimaryKey>true</IsPrimaryKey>
<IsUniqueConstraint>false</IsUniqueConstraint>
<Columns>
<IndexColumn>
<TableColumnId>1</TableColumnId>
<KeyOrdinal>1</KeyOrdinal>
<IsDescendingKey>false</IsDescendingKey>
</IndexColumn>
</Columns>
</Index>
<Index Name="IX_Orders_OrderNumber">
<IndexId>2</IndexId>
<Type>NONCLUSTERED</Type>
<IsUnique>false</IsUnique>
<IsPrimaryKey>false</IsPrimaryKey>
<IsUniqueConstraint>false</IsUniqueConstraint>
<Columns>
<IndexColumn>
<TableColumnId>2</TableColumnId>
<KeyOrdinal>1</KeyOrdinal>
<IsDescendingKey>false</IsDescendingKey>
</IndexColumn>
</Columns>
</Index>
</Indexes>
<PrimaryKey Name="PK_Orders" ObjectId="5575058" ParentObjectId="2137058649">
<UniqueIndexId>1</UniqueIndexId>
</PrimaryKey>
<ForeignKeys />
<Defaults>
<Default Name="DF_Orders_TotalCost" ObjectId="69575286" ParentObjectId="2137058649">
<ParentColumnId>4</ParentColumnId>
<Definition>((0))</Definition>
</Default>
</Defaults>
</Table>
<Table Name="Details" ObjectId="85575343" ParentObjectId="0">
<Columns>
<Column Name="Id">
<ColumnId>1</ColumnId>
<Type>int</Type>
<MaxLength>4</MaxLength>
<IsNullable>false</IsNullable>
<IsIdentity>true</IsIdentity>
<IsComputed>false</IsComputed>
</Column>
<Column Name="Descript">
<ColumnId>2</ColumnId>
<Type>nvarchar</Type>
<MaxLength>300</MaxLength>
<IsNullable>true</IsNullable>
<IsIdentity>false</IsIdentity>
<IsComputed>false</IsComputed>
</Column>
<Column Name="OrderId">
<ColumnId>3</ColumnId>
<Type>int</Type>
<MaxLength>4</MaxLength>
<IsNullable>true</IsNullable>
<IsIdentity>false</IsIdentity>
<IsComputed>false</IsComputed>
</Column>
<Column Name="Cost">
<ColumnId>4</ColumnId>
<Type>decimal</Type>
<MaxLength>9</MaxLength>
<IsNullable>false</IsNullable>
<IsIdentity>false</IsIdentity>
<IsComputed>false</IsComputed>
</Column>
</Columns>
<Indexes>
<Index Name="PK_Details">
<IndexId>1</IndexId>
<Type>CLUSTERED</Type>
<IsUnique>true</IsUnique>
<IsPrimaryKey>true</IsPrimaryKey>
<IsUniqueConstraint>false</IsUniqueConstraint>
<Columns>
<IndexColumn>
<TableColumnId>1</TableColumnId>
<KeyOrdinal>1</KeyOrdinal>
<IsDescendingKey>false</IsDescendingKey>
</IndexColumn>
</Columns>
</Index>
</Indexes>
<PrimaryKey Name="PK_Details" ObjectId="117575457" ParentObjectId="85575343">
<UniqueIndexId>1</UniqueIndexId>
</PrimaryKey>
<ForeignKeys>
<ForeignKey Name="FK_Details_Orders" ObjectId="149575571" ParentObjectId="85575343">
<ReferenceTableId>2137058649</ReferenceTableId>
<References>
<Reference>
<ColumnId>1</ColumnId>
<ParentColumnId>3</ParentColumnId>
<ReferenceColumnId>1</ReferenceColumnId>
</Reference>
</References>
<DeleteAction>NO_ACTION</DeleteAction>
<UpdateAction>NO_ACTION</UpdateAction>
</ForeignKey>
</ForeignKeys>
<Defaults>
<Default Name="DF_Details_Cost" ObjectId="101575400" ParentObjectId="85575343">
<ParentColumnId>4</ParentColumnId>
<Definition>((0))</Definition>
</Default>
</Defaults>
</Table>
</Tables>
<Views />
<ProgrammedObjects>
<ProgObject Name="Details_Modify" ObjectId="165575628" ParentObjectId="0">
<Definition>CREATE TRIGGER [dbo].[Details_Modify]
ON dbo.Details
AFTER INSERT,UPDATE
AS
BEGIN
UPDATE Orders
SET TotalCost = s.Total
FROM (
SELECT i.OrderId OId, SUM(d.Cost) Total
FROM Details d
JOIN inserted i ON d.OrderId=i.OrderId
GROUP BY i.OrderId
) s
WHERE Id=s.OId
END</Definition>
<Type>SQL_TRIGGER</Type>
</ProgObject>
<ProgObject Name="Details_Delete" ObjectId="181575685" ParentObjectId="0">
<Definition>CREATE TRIGGER [dbo].[Details_Delete]
ON dbo.Details
AFTER DELETE
AS
BEGIN
UPDATE Orders
SET TotalCost = s.Total
FROM (
SELECT i.OrderId OId, SUM(d.Cost) Total
FROM Details d
JOIN deleted i ON d.OrderId=i.OrderId
GROUP BY i.OrderId
) s
WHERE Id=s.OId
END</Definition>
<Type>SQL_TRIGGER</Type>
</ProgObject>
</ProgrammedObjects>
</DataBase> Nasazení/aktualizace struktury databáze pomocí XML
Vytvořte další prázdnou databázi DbSyncSampleCopy a do programového kódu konzoly přidejte následující kód:
class Program
{
private const string OrigConnString = "data source=.;initial catalog=DbSyncSample;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework";
private const string TargetConnString = "data source=.;initial catalog=DbSyncSampleCopy;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework";
static void Main(string[] args)
{
// getting XML with the structure of the reference database
var dborig = new Shed.DbSync.DataBase(OrigConnString);
var xml = dborig.GetXml();
File.WriteAllText("DbStructure.xml", xml);
// if you need to clear the structure of the target database, use
// Shed.DbSync.DataBase.ClearDb(TargetConnString);
// update the structure of the target database
var dbcopy = Shed.DbSync.DataBase.CreateFromXml(xml);
dbcopy.UpdateDb(TargetConnString);
// in fact, you can use one line:
// dborig.UpdateDb(TargetConnString);
// create dbcopy only to demonstrate the creation of a database object from XML
}
} Po spuštění programu můžete ověřit, že DbSyncSampleCopy má nyní strukturu tabulky shodnou s referenční databází. Nebojte se experimentovat se změnou referenční struktury a aktualizací cílové.
V testovacích scénářích budete možná muset vytvořit testovací databázi pokaždé od začátku. V tomto případě bude užitečné použít funkci Shed.DbSync.DataBase.ClearDb(string connString).
Automatické sledování struktury databáze
Sledování struktury je samostatnou funkcí, která by měla být volána při spuštění/restartu aplikace nebo na jiném místě na žádost vývojáře.
Sledování struktury je samostatnou funkcí. P>static void SyncDb()
{
// autotracking of database structure
Shed.DbSync.DataBase.Syncronize(OrigConnString,
@"Struct\DbStructure.xml", // path to the structure file
@"Struct\Logs", // path to synchronization log folder
@"Struct\update_script.sql" // (optional) in case of defining this parameter
// the script generated for the database update
// will be stored within it
);
}SCRIPT Sledování se provádí pomocí parametru Verze (tag) v XML. Scénář použití postupu je následující:
Přiřaďte verzi databázi. V Microsoft SQL Server Management Studio klikněte pravým tlačítkem na uzel požadované databáze a vyberte Vlastnosti.
Poté klikněte na Rozšířené vlastnosti a přidejte vlastnost Verze s hodnotou 1 do tabulky vlastností. Při každé další úpravě struktury by se tato vlastnost měla zvýšit o 1.
Když spustíte aplikaci, soubor se vytvoří, pokud neexistuje žádný soubor XML nebo je jeho verze menší než verze databáze.
Pokud je verze souboru XML větší než verze databáze, je vygenerován a spuštěn skript pro aktualizaci databáze.
Pokud se během provádění skriptu vyskytnou chyby, všechny změny budou vráceny zpět.
Výsledky synchronizace se zapisují do souboru protokolu vytvořeného ve složce určené parametrem logDitPath.
Pokud je zadán parametr SqlScriptPath, vytvoří se soubor se skriptem z položky 4.