iT邦幫忙

DAY 11
0

Kuick Application & ORM Framework系列 第 11

Kuick -- Schema Sync 規格同步

Kuick 採行 Code First 設計,規格同步過程以程式碼為中心,於系統啟期間依據專案設定的資料庫提供者 (MSSql, MySQL, Oracle) 類型,將程式規格定義同步至對應的資料表。下圖是規格同步的簡要流程,詳細內容請參閱內文。

規格同步程式於 Kuick.Data.DataStart 類別的 DoDatabaseStart 事件處理器觸發:

public void DoDatabaseStart(object sender, EventArgs e)
{
	// DatabaseSynchronizer
	string title = "Kuick.Data.DataStart.DoDatabaseStart";
	using(IntervalLogger il = new IntervalLogger(title, LogLevel.Track)) {
		foreach(IEntity schema in EntityCache.Values) {
			if(schema.EntityName == Constants.Entity.Suffix) { continue; }
			Sync(il, schema);

			IHierarchyEntity hierarchy = schema as IHierarchyEntity;
			if(null != hierarchy) { hierarchy.Regulating(); }
		}
	}
}

程式說明:
從 Entity 規格快取 (EntityCache) 裡取出所有的規格,依序進行規格同步,當 Entity 是實作 IHierarchyEntity 介面的階層式資料時,自動進行階層式資料校正處理。我們先將重點放在規格同步操作說明,階層式資料校正的議題留待後面再談。

規格同步程式實作於 Kuick.Data.DataStart 類別的 Sync 方法:

private void Sync(IntervalLogger il, IEntity schema)
{
	if(!Current.Data.Alterable || schema.IsView || !schema.Alterable) { return; }

	Api api = new Api(schema.EntityName, IsolationLevel.ReadUncommitted, true);
	il.Add("Start: Entity Name", schema.EntityName);
	DbSetting setting = DbSettingCache.Get(schema.EntityName);
	if(Checker.IsNull(setting.Version)) {
		if(DbSettingCache._Versions.ContainsKey(setting.ConnectionString)) {
			setting.Version = DbSettingCache._Versions[setting.ConnectionString];
		} else {
			setting.Version = api.GetDatabaseVersion(il);
		}
	}
	il.Add("Database Version", setting.Version);

	if(Checker.IsNull(setting.TableNames)) {
		if(DbSettingCache._TableNames.ContainsKey(setting.ConnectionString)) {
			setting.TableNames.AddRange(
				DbSettingCache._TableNames[setting.ConnectionString]
			);
		} else {
			setting.TableNames.AddRange(
				api.GetTableNames(il)
			);
		}
	}
	il.Add(
		"Database Tables",
		setting.TableNames.Join(Constants.Symbol.Comma)
	);

	bool tableExists = api.CheckTableExists(il, schema);
	if(tableExists) {
		il.Add(
			"Sync Approach",
			"Table already exists: 1. drop indexes --> 2. sync columns --> 3. create indexes"
		);
		api.DropIndexes(il, schema);

		api.SyncColumns(il, schema);
	} else {
		il.Add(
			"Sync Approach",
			"Table does not exists: 1. create table --> 2. create indexes"
		);
		api.CreateTable(il, schema);
	}

	api.CreateIndexes(il, schema);

	il.Add("End: Entity Name", schema.EntityName);
}

規格同步步驟,請配合本文摘要流程圖一同了解:
1. 檢查系統定義檔是否開啟規格同步
如要進行規格同步,請於 Web.config / App.config 設定如下:

<configuration>
	<Kuick>
		<application>
			<add group="Data" name="Alterable" value="True"/>
		</application>
	</Kuick>
</configuration>

2. 各別 Entity 是否允許規格同步
Entity 預設允許規格同步,如要針對特定的 Entity 關閉規格同步功能,請在該 Entity 裡 override Alterable property 程式如下:

public override bool Alterable
{
	get
	{
		return false;
	}
}

3. 檢查本 Entity 是否為檢視表 (View)
Entity 如果是檢視表,將不進行規格同步。
Entity 預設對應的是資料表 (Table),如果 Entity 對應的是檢視表 (View),請在該 Entity 裡 override IsView property 程式如下:

public override bool IsView
{
	get
	{
		return true;
	}
}

4. 偵測資料庫版本
不同的 MSSql 版本,資料規格與查詢指令會有些微差異,例如 SQL 2000 裡 text, ntext, image 資料型態,在 SQL 2005 以上為 varchar(max), nvarchar(max), varbinary(max),SQL 2012 裡新增資料分頁的查詢語法 (OFFSET, FETCH),這些都是資料提供者實作需特別設計的細節。目前 Kuick 有實作對於 MSSql 2000, 2005, 2008 差異語法,MSSql 2012 的特別語法支援,將在下一個版本實作,雖然如此,目前版本仍可用於 MSSql 2012。

5. 檢查資料表是否存在
資料表不存在的處理相對容易得多,直接建立資料表後再建立索引即可。而資料表存在的同步細節,是實作規格同步裡最複雜的部份。
Oracle 進行 Alter 欄位時,如果欄位規格沒有改變,執行 Alter 指令時會發生錯誤。所以在進行規格同步前,得先檢查規格是否有變,請參考 Kuick.Data.SqlDatabase 類別 SyncColumns 方法:

public void SyncColumns(IntervalLogger il, IEntity schema)
{
	foreach(Column column in schema.Columns) {
		string section = "SyncColumns: begin";
		try {
			if(HasDBColumn(column)) { // --------------------- Alter
				section = "SyncColumns: Alter";
				if(column.Spec.PrimaryKey || column.Spec.Identity) { continue; }
				if(!AlterableColumn(column)) { continue; }
				if(!ColumnSpecChanged(column)) { continue; }

				// 1. alter anyway allow null first
				section = "SyncColumns: Alter -- alter anyway allow null first";
				Column allowNullColumn = column.Clone();
				allowNullColumn.Spec.NotAllowNull = false;
				string sqlAllowNull = Builder.BuildAlterColumnCommandText(allowNullColumn);
				Result result = this.ExecuteNonQuery(il, sqlAllowNull);

				// 2. insert default value
				section = "SyncColumns: Alter -- insert default value";
				object v = schema.GetInitiateValue(column);
				string initiateValue = v as string;
				Result result2 = Sql
					.And(schema.EntityName)
					.SetValue(column.Spec.ColumnName, initiateValue)
					.Where(new SqlExpression(column.Spec.ColumnName).IsNull())
					.Modify();

				// 3. alter again if not allow null
				section = "SyncColumns: Alter -- alter again if not allow null";
				if(column.Spec.NotAllowNull) {
					string sqlNotAllowNull = Builder.BuildAlterColumnCommandText(column);
					Result result3 = this.ExecuteNonQuery(il, sqlNotAllowNull);
				}

				il.Add(
					string.Format("Alter Column '{0}'", column.Spec.ColumnName),
					result.ToString()
				);
				// Audit
				// todo
			} else { // --------------------------------------------------------------- Add
				section = "SyncColumns: Add";
				if(column.Spec.PrimaryKey) { continue; }
				string sql = Builder.BuildAddColumnCommandText(column);
				Result result = this.ExecuteNonQuery(il, sql);
				il.Add(
					string.Format("Add Column '{0}'", column.Spec.ColumnName),
					result.ToString()
				);
				// Audit
				// todo
			}
		} catch(Exception ex) {
			il.Add(
				ex,
				new Any("ColumnName", column.Spec.ColumnName),
				new Any("section", section)
			);
		}
	}
}

程式說明:
首先檢查欄位是否存在,不存在的處理直接新增欄位即可,已經存在的處理步驟如下:
1). 先排除不可 Alter 的欄位 PrimaryKey, Identity。
2). 檢查各別資料庫實作的欄位可否修改 (AlterableColumn) 檢查。
3). 檢查各別資料庫實作的欄位是否改變 (ColumnSpecChanged) 檢查。
4). 先以 allow null 進行欄位修改 (也可能是新增欄位)。
5). 將欄位預設值填入資料為 null 裡。
6). 如果原始規格 not allow null,再一次進行欄位修改。

經過這一連串的處理,資料庫規格程度上是同步的,但是對於欄位移除的情形,基於資料安全性考量,Kuick 並沒有主動將不存在規格裡的欄位刪除,需要開發者額外手動處理。所以當資料欄位更名時,Kuick 將其視為新增欄位,原本欄位保留不處理,這點請特別注意。

========================================
鐵人賽分享列表:Kuick Application & ORM Framework
開放原始碼專案:kuick.codeplex.com
直接下載原始碼:Kuick
下載相關文件檔:C# Code Conventions and Design Guideline
相關教學影片區:Kuick on YouTube


上一篇
Kuick -- Schema Cache 規格快取
下一篇
Kuick -- Data Initialize 資料初始化
系列文
Kuick Application & ORM Framework34

尚未有邦友留言

立即登入留言