我習慣性使用OData,它的$expand與層級查詢非常好用,這個功能非常依賴於資料庫的導航屬性,也就是外鍵結構。最近想著把一個單體的系統拆分為多個小系統,首先需要處理外鍵依賴的問題。 多個服務各自有各自的資料庫,資料庫層面並不互通,也就無法使用外鍵約束。 我使用EF Core來描述資料庫的結構,有 ...
我習慣性使用OData,它的$expand與層級查詢非常好用,這個功能非常依賴於資料庫的導航屬性,也就是外鍵結構。最近想著把一個單體的系統拆分為多個小系統,首先需要處理外鍵依賴的問題。
多個服務各自有各自的資料庫,資料庫層面並不互通,也就無法使用外鍵約束。
我使用EF Core來描述資料庫的結構,有兩個實體類如下:
public class AD_Insect_Info
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id {get;set;}
public string Name { get; set; }
public virtual List<AD_Insect_Datum> Results { get; set; }
}
public class AD_Insect_Datum
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public virtual AD_Insect_Info AttachDevice { get; set; }
[NotMapped]
public override string AttachId => AttachDevice.AttachDeviceId;
}
可以看到他們之間有一個導航屬性,實現一個一對多的關係。如果系統是新系統,大可直接進行拆解,想怎麼弄就怎麼弄。但是對於已經有較多數據的現有系統,最好使用漸進拆分的方式。
思路:通過多次,每次只修改一點,維持對舊有系統的相容性,併在完全拆分前保持系統高度可用,破壞性最小。
指定外鍵
跨資料庫的訪問,數據完整性不能由資料庫通過外鍵實現,我們需要在應用層實現自己的邏輯。由於外鍵不存在,我們需要在數據表中有表示對外引用的欄位。而對於導航屬性而言,外鍵已經由EF Core自動建立,為了防止數據結構變化導致的問題,我們可以明確指定外鍵。
查詢數據表結構
EF Core有預設的外鍵命名規則,通常是欄位名+外鍵的欄位名,對於本例,則是AttachDeviceId,保險起見,我們可以查詢資料庫獲得外鍵列名稱。
顯式指定外鍵名稱
導航屬性的外鍵名字不是那麼直觀,我們可以使用EF Core的ForeignKey特性。
public class AD_Insect_Datum : AttachDataBase
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[ForeignKey("AttachDeviceId")]
public virtual AD_Insect_Info AttachDevice { get; set; }
public int AttachDeviceId { get; set; }
}
這樣,這個對象Id就被顯式指定了。
查詢替代
前一步指定了外鍵,但是實際上並沒有對EF Core表做任何更改,原來的程式可以正常運行,我們需要在這個基礎上繼續改造,將使用導航屬性的地方修改一下。
public virtual async Task<IActionResult> Data(string key)
{
return Ok(_context.AD_Insect_Data.Where(w => w.AttachDevice.Name == key));
}
修改為:
public virtual async Task<IActionResult> Data(string key)
{
var instances = _context.AD_Insect_Infos.Where(w=>w.Name == key).Select(w=> w.Id).ToList();
return Ok(_context.AD_Insect_Data.Where(w => instances.Contains(w.AttachDeviceId)));
}
這樣就避免使用導航屬性依賴。
我這裡也沒有使用連表查詢方法,因為將來需要拆分。
刪除外鍵
由於不再使用導航屬性,可以安全地刪除外鍵。註意,可能需要補充一些業務邏輯以確保資料庫中的數據完整性。
拆分DbContext
將來DbContext將不再擁有AD_Insect_Infos,因此我們需要進一步拆分。
public virtual async Task<IActionResult> Data(string key)
{
IEnumerable<int> instances = Foreign.GetList(key);
return Ok(_context.AD_Insect_Data.Where(w => instances.Contains(w.AttachDeviceId)));
}
public class Foreign
{
public IEnumerable<int> GetList(string key)
{
return _context.AD_Insect_Infos.Where(w=>w.Name == key).Select(w=> w.Id).ToList();
}
}
上面代碼表示大致的思路,請自行處理註入依賴等問題。
然後就是熟悉的動作了,將Foreign類作為一個數據服務,只要返回的類型是IEnumerable<int>
就可以了,數據的來源可以是本身,也可以是外部的服務。