摘要:[Architecture] Database Migration (下)
接續...
[Architecture] Database Migration (上)
實作
範列下載
實作說明請參照範例程式內容:DatabaseMigrationSample點此下載
範列實作
首先建立封裝資料庫更新邏輯的DatabaseUpdater,以及定義DatabaseUpdater會使用到的各個介面。執行DatabaseUpdater的Update方法,系統會依照資料庫更新邏輯,執行對應的DatabaseUpdatePlan。並且反覆執行,直到資料庫版本為目前DatabaseUpdatePlan能更新的最新版本。
public interface IDatabaseVersionManager
{
// Methods
string GetCurrentDatabaseVersion();
void SetCurrentDatabaseVersion(string databaseVersion);
}
public interface IDatabaseUpdatePlanRepository
{
// Methods
IEnumerable<IDatabaseUpdatePlan> GetAllUpdatePlan();
}
public interface IDatabaseUpdatePlan
{
// Properties
string TargetDatabaseVersion { get; }
string ResultDatabaseVersion { get; }
// Methods
void Execute();
}
public class DatabaseUpdater
{
// Fields
private readonly IDatabaseVersionManager _databaseVersionManager = null;
private readonly IDatabaseUpdatePlanRepository _databasePlanRepository = null;
// Constructor
public DatabaseUpdater(IDatabaseVersionManager databaseVersionManager, IDatabaseUpdatePlanRepository databasePlanRepository)
{
#region Contracts
if (databaseVersionManager == null) throw new ArgumentNullException();
if (databasePlanRepository == null) throw new ArgumentNullException();
#endregion
_databaseVersionManager = databaseVersionManager;
_databasePlanRepository = databasePlanRepository;
}
// Methods
public void Update()
{
for (; ; )
{
if (this.UpdateDatabase() == false)
{
return;
}
}
}
private bool UpdateDatabase()
{
// Get DatabaseVersion
string databaseVersion = this.GetDatabaseVersion();
// Get DatabaseUpdatePlan
IDatabaseUpdatePlan databaseUpdatePlan = this.GetDatabaseUpdatePlan(databaseVersion);
if (databaseUpdatePlan == null) return false;
// Execute DatabaseUpdatePlan
databaseUpdatePlan.Execute();
databaseVersion = databaseUpdatePlan.ResultDatabaseVersion;
// Set DatabaseVersion
this.SetDatabaseVersion(databaseVersion);
// Return
return true;
}
private string GetDatabaseVersion()
{
string databaseVersion = _databaseVersionManager.GetCurrentDatabaseVersion();
if (databaseVersion == null) databaseVersion = string.Empty;
return databaseVersion;
}
private void SetDatabaseVersion(string databaseVersion)
{
#region Contracts
if (string.IsNullOrEmpty(databaseVersion) == true) throw new ArgumentException();
#endregion
_databaseVersionManager.SetCurrentDatabaseVersion(databaseVersion);
}
private IDatabaseUpdatePlan GetDatabaseUpdatePlan(string databaseVersion)
{
#region Contracts
if (databaseVersion == null) throw new ArgumentNullException();
#endregion
foreach (IDatabaseUpdatePlan databaseUpdatePlan in _databasePlanRepository.GetAllUpdatePlan())
{
if (databaseUpdatePlan.TargetDatabaseVersion == databaseVersion)
{
return databaseUpdatePlan;
}
}
return null;
}
}
接著實作封裝資料庫版本管理職責的DatabaseVersionManager。實作範例中是將資料庫版本的資料,存放到資料庫中,由資料庫描述自己目前的版本。並且DatabaseVersionManager在取得版本資料的時候,會特別檢查資料庫是否已經建立,這是用來處理建立第1.0版資料庫的相關邏輯。
public class SqlDatabaseVersionManager : IDatabaseVersionManager
{
// Fields
private readonly string _connectionString = null;
// Constructor
public SqlDatabaseVersionManager(string connectionString)
{
#region Contracts
if (string.IsNullOrEmpty(connectionString) == true) throw new ArgumentException();
#endregion
_connectionString = connectionString;
}
// Methods
public string GetCurrentDatabaseVersion()
{
// Check Database Exist
if ((int)(SqlHelper.ExecuteScalar(_connectionString, @"SELECT COUNT(*) FROM sysdatabases WHERE name = N'MyDatabase'")) == 0)
{
return string.Empty;
}
// Get DatabaseVersion
string databaseVersion = (string)(SqlHelper.ExecuteScalar(_connectionString, @"SELECT CurrentVersion FROM [MyDatabase].[dbo].[DatabaseVersion]"));
if (databaseVersion == null) databaseVersion = string.Empty;
// Return
return databaseVersion;
}
public void SetCurrentDatabaseVersion(string databaseVersion)
{
#region Contracts
if (string.IsNullOrEmpty(databaseVersion) == true) throw new ArgumentException();
#endregion
// Clear DatabaseVersion
SqlHelper.ExecuteScalar(_connectionString, @"DELETE FROM [MyDatabase].[dbo].[DatabaseVersion]");
// Set DatabaseVersion
SqlHelper.ExecuteScalar(_connectionString, @"INSERT INTO [MyDatabase].[dbo].[DatabaseVersion](CurrentVersion) VALUES('" + databaseVersion + "')");
}
}
接著建立第1.0版的DatabaseUpdatePlan。DatabaseUpdatePlan裡,封裝了目標版本、Schema變更、資料轉換…等等職責。在第1.0版的DatabaseUpdatePlan最主要是為系統資料庫加入了User這個資料表。並且第1.0版的DatabaseUpdatePlan跟其他版本的DatabaseUpdatePlan會有一個主要的差異在於:第1.0版的DatabaseUpdatePlan另外還要負責建立系統使用的資料庫(MyDatabase)、記錄資料庫版本的資料表(DatabaseVersion)…等等必要設定。
public class SqlDatabaseUpdatePlanV1_0 : IDatabaseUpdatePlan
{
// Fields
private readonly string _connectionString = null;
// Constructor
public SqlDatabaseUpdatePlanV1_0(string connectionString)
{
#region Contracts
if (string.IsNullOrEmpty(connectionString) == true) throw new ArgumentException();
#endregion
_connectionString = connectionString;
}
// Properties
public string TargetDatabaseVersion
{
get { return string.Empty; }
}
public string ResultDatabaseVersion
{
get { return @"MyDatabase V1.0"; }
}
// Methods
public void Execute()
{
// Create Database
SqlHelper.ExecuteNonQuery(_connectionString, @"CREATE DATABASE MyDatabase");
// Add DatabaseVersion Table
SqlHelper.ExecuteNonQuery(_connectionString, @"CREATE TABLE [MyDatabase].[dbo].[DatabaseVersion] (CurrentVersion nvarchar(50))");
// Add User Table
SqlHelper.ExecuteNonQuery(_connectionString, @"CREATE TABLE [MyDatabase].[dbo].[User] (UserId nvarchar(50), UserName nvarchar(50))");
}
}
接著建立第1.3版的DatabaseUpdatePlan。在第1.3版的DatabaseUpdatePlan最主要是為系統資料庫加入了Company這個資料表。
public class SqlDatabaseUpdatePlanV1_3 : IDatabaseUpdatePlan
{
// Fields
private readonly string _connectionString = null;
// Constructor
public SqlDatabaseUpdatePlanV1_3(string connectionString)
{
#region Contracts
if (string.IsNullOrEmpty(connectionString) == true) throw new ArgumentException();
#endregion
_connectionString = connectionString;
}
// Properties
public string TargetDatabaseVersion
{
get { return @"MyDatabase V1.0"; }
}
public string ResultDatabaseVersion
{
get { return @"MyDatabase V1.3"; }
}
// Methods
public void Execute()
{
// Add Order Table
SqlHelper.ExecuteNonQuery(_connectionString, @"CREATE TABLE [MyDatabase].[dbo].[Company] (CompanyId nvarchar(50), CompanyName nvarchar(50))");
}
}
再來建立封裝更新計畫管理職責的DatabaseUpdatePlanRepository。在範例程式中,為了簡化所以採用Hard Code的方式,來管理DatabaseUpdatePlan。實際的專案中可以採用各種DI framework來完成DatabaseUpdatePlan生成注入的工作。
public class SqlDatabaseUpdatePlanRepository : IDatabaseUpdatePlanRepository
{
// Fields
private readonly string _connectionString = null;
// Constructor
public SqlDatabaseUpdatePlanRepository(string connectionString)
{
#region Contracts
if (string.IsNullOrEmpty(connectionString) == true) throw new ArgumentException();
#endregion
_connectionString = connectionString;
}
// Methods
public IEnumerable<IDatabaseUpdatePlan> GetAllUpdatePlan()
{
IDatabaseUpdatePlan[] databaseUpdatePlanArray = new IDatabaseUpdatePlan[]
{
new SqlDatabaseUpdatePlanV1_0(_connectionString),
new SqlDatabaseUpdatePlanV1_3(_connectionString),
};
return databaseUpdatePlanArray;
}
}
最後執行生成建立DatabaseUpdater的ConsoleApplication程式。就可以看到原本空白的資料庫,建立了系統必要的資料庫、資料表。而檢查程式建立的資料表內容,可以發現第1.3版本才加入的Company資料表,這也就是說資料庫已經升級成為了第1.3版的資料庫。(記得重新整理,才能看到更新資料)
class Program
{
static void Main(string[] args)
{
// Setting
string connectionString = @"Data Source=CLARK-HOME\SQLEXPRESS;Integrated Security=True";
// Create
DatabaseUpdater databaseUpdater = CreateDatabaseUpdater(connectionString);
// Update
databaseUpdater.Update();
// End
Console.WriteLine("End...");
Console.ReadLine();
}
static DatabaseUpdater CreateDatabaseUpdater(string connectionString)
{
#region Contracts
if (string.IsNullOrEmpty(connectionString) == true) throw new ArgumentException();
#endregion
// DatabaseVersionManager
IDatabaseVersionManager databaseVersionManager = new SqlDatabaseVersionManager(connectionString);
// DatabasePlanRepository
IDatabaseUpdatePlanRepository databasePlanRepository = new SqlDatabaseUpdatePlanRepository(connectionString);
// DatabaseUpdater
DatabaseUpdater databaseUpdater = new DatabaseUpdater(databaseVersionManager, databasePlanRepository);
// Return
return databaseUpdater;
}
}
到目前為止,範例程式驗證了從無到有的生成資料庫。接下來的範例程式,用來示範升級資料庫版本的功能。首先建立第2.0版的DatabaseUpdatePlan。在第2.0版的DatabaseUpdatePlan最主要是為系統資料庫加入了Order這個資料表。
public class SqlDatabaseUpdatePlanV2_0 : IDatabaseUpdatePlan
{
// Fields
private readonly string _connectionString = null;
// Constructor
public SqlDatabaseUpdatePlanV2_0(string connectionString)
{
#region Contracts
if (string.IsNullOrEmpty(connectionString) == true) throw new ArgumentException();
#endregion
_connectionString = connectionString;
}
// Properties
public string TargetDatabaseVersion
{
get { return @"MyDatabase V1.3"; }
}
public string ResultDatabaseVersion
{
get { return @"MyDatabase V2.0"; }
}
// Methods
public void Execute()
{
// Add Order Table
SqlHelper.ExecuteNonQuery(_connectionString, @"CREATE TABLE [MyDatabase].[dbo].[Order] (OrderId nvarchar(50), OrderName nvarchar(50))");
}
}
接著將建立完成的第2.0版DatabaseUpdatePlan加入DatabaseUpdatePlanRepository。
public class SqlDatabaseUpdatePlanRepository : IDatabaseUpdatePlanRepository
{
// Fields
private readonly string _connectionString = null;
// Constructor
public SqlDatabaseUpdatePlanRepository(string connectionString)
{
#region Contracts
if (string.IsNullOrEmpty(connectionString) == true) throw new ArgumentException();
#endregion
_connectionString = connectionString;
}
// Methods
public IEnumerable<IDatabaseUpdatePlan> GetAllUpdatePlan()
{
IDatabaseUpdatePlan[] databaseUpdatePlanArray = new IDatabaseUpdatePlan[]
{
new SqlDatabaseUpdatePlanV1_0(_connectionString),
new SqlDatabaseUpdatePlanV1_3(_connectionString),
new SqlDatabaseUpdatePlanV2_0(_connectionString),
};
return databaseUpdatePlanArray;
}
}
最後執行先前建立的ConsoleApplication程式。就可以看到原本第1.3版的資料庫內,加入第2.0版的Order資料表,這也就是說資料庫已經升級成為了第2.0版的資料庫。(記得重新整理,才能看到更新資料)
後記
礙於篇幅的關係,DatabaseUpdater簡化了很多的功能設計,例如:在DatabaseUpdater加入資料庫的備份與還原功能,以提高系統的可用性。或者是在DatabaseUpdater裡加入升級到特定版本的功能,以增加工具的重用性。還有其他一些重要的功能,這些功能都有很大的用處。開發人員在實際專案的建置時,可以視專案需求來加入實作。
另外不管是微軟產品還是其他軟體產品,都有與Database Migration功能相近的軟體實作。
相關資料可以參考:
-Entity Framework - 使用Code First的Enabling Migrations(Code-Base)
-在Django裡做Database Migration
能以更簡潔的文字與程式碼,傳達出程式設計背後的精神。
真正做到「以形寫神」的境界。