データベースを使用するアプリケーションを開発したことがある人は、アプリケーションのデプロイと更新時にデータベース構造を更新するという問題に直面した可能性があります。
最も一般的なアプローチは、SQLスクリプトのセットを作成して、データベース構造をバージョンごとに変更することです。もちろん、有料のツールもありますが、アップデートの完全自動化の問題を常に解決できるとは限りません。
Hibernate ORMで最初に導入され、Linqで実装された移行テクノロジは非常に優れており便利ですが、データベース構造を開発するための「コードファースト」戦略を意味します。これは既存のプロジェクトにとって非常に面倒であり、データベースでトリガー、ストアドプロシージャ、および関数を使用すると、「コードファースト」戦略への移行がほぼ不可能になります。
この記事では、この問題を解決するための代替アプローチを提案します。参照データベース構造をXMLファイルに保存し、参照との比較に基づいてSQLスクリプトを自動的に生成します。既存の構造。では、始めましょう…
データベース構造を使用したXMLファイルの生成
DbSyncSampleデータベースを使用します。データベースを作成するためのスクリプトを以下に示します。
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
コンソールアプリケーションを作成し、Shed.DbSyncnuget-packageをそれにリンクします。
XMLデータベースの構造は次のとおりです。
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);
}
} プログラムを実行すると、DbStructure.xmlファイルに次のように表示されます。
<?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> XMLを使用したデータベース構造の展開/更新
別の空のDbSyncSampleCopyデータベースを作成し、コンソールプログラムコードに次のコードを追加します。
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
}
} プログラムを実行した後、DbSyncSampleCopyが参照データベースと同じテーブル構造になっていることを確認できます。参照構造を変更し、ターゲット構造を更新してみてください。
テストシナリオでは、毎回最初からテストデータベースを作成する必要がある場合があります。この場合、Shed.DbSync.DataBase.ClearDb(string connString)関数を使用すると便利です。
データベース構造の自動追跡
構造追跡は別の関数になります。この関数は、アプリケーションの開始/再起動時、または開発者の要求に応じて別の場所で呼び出す必要があります。
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 追跡は、XMLのバージョンパラメータ(タグ)を使用して実行されます。手順を使用するシナリオは次のとおりです。
-
データベースにバージョンを割り当てます。 Microsoft SQL Server Management Studioで、必要なデータベースのノードを右クリックし、[プロパティ]を選択します。
-
次に、[拡張プロパティ]をクリックし、値1のVersionプロパティをプロパティテーブルに追加します。その後、構造を変更するたびに、このプロパティを1ずつ増やす必要があります。
-
アプリケーションを起動すると、XMLファイルがないか、そのバージョンがデータベースのバージョンよりも小さい場合、ファイルが作成されます。
> -
XMLファイルのバージョンがデータベースのバージョンよりも大きい場合、データベースを更新するためのスクリプトが生成され、実行されます。
-
スクリプトの実行中にエラーが発生した場合、すべての変更がロールバックされます。
-
同期結果は、logDitPathパラメーターで指定されたフォルダーに作成されたログファイルに書き込まれます。
-
SqlScriptPathパラメーターが指定されている場合、項目4のスクリプトを含むファイルが作成されます。