配合不存在的外部索引鍵進行工作
這個月我正在寫關於問題找到了自己説明有經常晚的人:相關的類定義在代碼第一次然後在大多數情況下,模型-視圖-控制器 (MVC) 框架中使用的問題。開發商所遇到的問題並不是特定于代碼第一次。他們的基礎實體框架 (EF) 行為的事實上,常見的大多數物件-關係映射 (ORMs) 的結果。但似乎因為開發商要來代碼第一次與某些預期問題浮出水面。MVC 造成他們痛苦因為其高度已斷開連接的性質。
而不是只向您顯示正確的代碼,我會使用此列可以説明您瞭解 EF 行為,這樣就可以將這種知識應用於許多方案,您可能會遇到當設計您的類或編寫您的資料存取碼與 EF Api。
代碼的第一個公約是能夠檢測到並正確推斷與您的類中的屬性的不同組合的各種關係。在此示例中,而我寫此信作為葉正在轉向壯觀的顏色在佛蒙特州在我家附近的樹上,我將為我相關的類使用樹和葉。一個一對多關聯性中,最簡單的方法可以說明,在您的類中,並且具有代碼首次承認你的意圖是集合的要導航屬性在樹類表示某種類型的葉類型。葉類需要重新指向樹沒有屬性。圖 1 顯示的樹和葉類。
圖 1 相關的樹和葉類
public class Tree
{
public Tree()
{
Leaves = new List<Leaf>();
}
public int TreeId { get; set; }
public string Type { get; set; }
public double Lat { get; set; }
public double Long { get; set; }
public string Notes { get; set; }
public ICollection<Leaf> Leaves { get; set; }
}
public class Leaf
{
public int LeafId { get; set; }
public DateTime FellFromTreeDate { get; set; }
public string FellFromTreeColor { get; set; }
}
按照約定,代碼第一次會知道在資料庫中的葉表需要外鍵。 它將假設一個外鍵欄位名稱為"Tree_TreeId,"並在中繼資料中創建的代碼第一次運行時提供此資訊,與 EF 會明白如何算出的查詢和更新使用該外鍵。 EF 依靠相同的過程,它使用與"獨立協會"利用了這種行為 — — 唯一的我們可以使用之前向微軟的關聯類型。NET 框架 4 — — 而不需要依賴類中的外鍵屬性。
這是漂亮、 乾淨的方法定義類,當你有信心你不必過導航從葉回其在應用程式中的樹。 不過,無需直接便捷鍵,您需要進行編碼時要額外勤奮。
創建新的相關類型沒有外鍵或導航屬性
雖然您可以輕鬆使用這些類,顯示您的 ASP 中一棵樹和葉。NET MVC 應用與編輯葉,開發人員經常會遇到問題通常設計 MVC 應用程式中創建新葉。 我使用的範本從 MVCScaffolding NuGet 包 (mvcscaffolding.codeplex.com),讓 Visual Studio 自動◆ 建立我控制器、 意見和簡單的存儲庫,通過選擇"MvcScaffolding:控制器具有讀/寫操作和視圖,使用資料庫"請注意因為葉類中有沒有外鍵屬性,腳手架範本不會承認一個一對多的關係。 我作出一些次要更改視圖和控制器,以允許使用者從一棵樹導航到樹葉,你可以看到在你下載的示例。
所創建的回發葉操作需要從創建視圖返回的葉和告訴存儲庫中添加它,然後保存它,如圖所示,在圖 2。
圖 2 添加和保存到存儲庫中的葉
[HttpPost]
public ActionResult Create(Leaf leaf)
{
if (ModelState.IsValid)
{
leafRepository.InsertOrUpdate(leaf);
leafRepository.Save();
return RedirectToAction("Index",
new { treeId = Request.Form["TreeId"] });
}
else
{
return View();
}
}
存儲庫中採用的葉,檢查,看看它是新,如果是這樣,則將其添加到上下文實例創建的回發的結果:
public void InsertOrUpdate(Leaf leaf,int treeId){
if (leaf.LeafId == default(int)) {
// New entity
context.Leaves.Add(leaf);
} else {
// Existing entity
context.Entry(leaf).State = EntityState.Modified;
}
}
當調用 Save 時,EF 創建插入的命令,向資料庫中添加新的一頁:
exec sp_executesql N'insert [dbo].[Leaves]([FellFromTreeDate], [FellFromTreeColor],
[Tree_TreeId]) values (@0, @1, null)
select [LeafId]
from [dbo].[Leaves]
where @@ROWCOUNT > 0 and [LeafId] = scope_identity()',
N'@0 datetime2(7),@1 nvarchar(max) ',
@0='2011-10-11 00:00:00',@1=N'Pale Yellow'
請注意命令的第二行中傳遞的值:@ 0 (表示日期) ; 1 (為修改後的顏色) ; 的 和 null。 空值將被用於 Tree_TreeId 欄位。 請記住漂亮、 乾淨的葉類代表 TreeId,所以有沒有辦法傳遞該值在創建獨立葉時沒有外鍵的屬性。
當依賴的類型 (在本例中,葉) 有沒有知識及其主體類型 (樹) 的時有做插入只有一種方式:葉實例和樹實例必須添加到上下文一起作為同一個圖表的一部分。 這將為 EF 提供找出正確的值將插入到資料庫的外鍵 (例如,Tree_TreeId) 所需的所有資訊。 但在這種情況下,您正在與葉,僅有沒有 EF 確定樹的關鍵屬性的值為記憶體中的資訊。
如果你有葉類中的外鍵屬性,生活就會如此簡單。 它不是太難控制器和視圖之間移動時保持在手邊單個值。 事實上,如果你看看創建行動圖 2,您可以看到該方法具有訪問要為其創建葉的 TreeId 的值。
有多種方法來傳送 MVC 應用程式中的資料。 我選擇了最簡單的此演示:填充到 MVC ViewBag TreeId 和在必要時利用 Html.Hidden 欄位。 這使值可以作為視圖的 Request.Form 專案之一。
因為我有權訪問 TreeId,我能夠生成樹/葉圖形中將插入命令提供的 TreeId。 快速修改存儲庫類允許接受該 TreeId 變數從視圖的 InsertOrUpdate 方法,並使用 DbSet.Find 方法從資料庫中檢索的樹實例。 這裡是方法的受影響的部分:
public void InsertOrUpdate(Leaf leaf,int treeId)
{
if (leaf.LeafId == default(int)) {
var tree=context.Trees.Find(treeId);
tree.Leaves.Add(leaf);
}
...
上下文現在跟蹤樹和意識到我到樹中添加葉。 這一次,當上下文。被稱為 SaveChanges,EF 是能夠從葉導航到樹中發現的金鑰值,並在插入命令中使用它。
圖 3 展示了使用新版本的 InsertOrUpdate 的修改的控制器代碼。
圖 3 新版本的 InsertOrUpdate
[HttpPost]
public ActionResult Create(Leaf leaf)
{
if (ModelState.IsValid)
{
leafRepository.InsertOrUpdate(leaf);
leafRepository.Save();
return RedirectToAction("Index",
new { treeId = Request.Form["TreeId"] });
}
else
{
return View();
}
}
[HttpPost]
public ActionResult Create(Leaf leaf)
{
if (ModelState.IsValid)
{
var _treeId = Request.Form["TreeId"] as int;
leafRepository.InsertOrUpdate(leaf, _treeId);
leafRepository.Save();
return RedirectToAction("Index", new { treeId = _treeId });
}
else
{
return View();
}
}
隨著這些改變,insert 方法終於有外鍵,你可以看到在名為"2"的參數的值:
exec sp_executesql N'insert [dbo].[Leaves]([FellFromTreeDate], [FellFromTreeColor], [Tree_TreeId])
values (@0, @1, @2)
select [LeafId]
from [dbo].[Leaves]
where @@ROWCOUNT > 0 and [LeafId] = scope_identity()',
N'@0 datetime2(7),@1 nvarchar(max) ,
@2 int',@0='2011-10-12 00:00:00',@1=N'Orange-Red',@2=1
最後,此替代方法迫使我對資料庫進行另一次旅行。 這是我會選擇在這種情況下,在我不想在我依賴類的外鍵屬性中支付的價格。
更新時有沒有外鍵的問題
還有其他方法,您可以繪製自己到一個角落裡當你沒有您的類中的外鍵屬性的綁定已確定。 這裡是另一個例子。
我將添加一個名為 TreePhoto 的新域類。 因為我不想要從此類導航回樹,沒有導航屬性,並再次,我是跟著模式在不使用外鍵屬性:
[Table("TreePhotos")]
public class TreePhoto
{
public int Id { get; set; }
public Byte[] Photo { get; set; }
public string Caption { get; set; }
}
樹類提供了兩個類之間的唯一連接並指定每一棵樹必須有一張照片。 這是我到樹類添加新屬性:
[Required]
public TreePhoto Photo { get; set; }
這不會離開的可能性,孤立的照片,但是我使用此示例,因為多次看到 — — 隨請求説明 — — 所以我想解決它。
再次,代碼第一次公約 》 的阻嚇采,外鍵屬性將需要在資料庫中,並創建了一個,Photo_Id,以我的名義。 請注意它是不可為空。 這是因為 Leaf.Photo 屬性是必需的 (請參見圖 4)。
圖 4 使用代碼第一次約定,樹獲取非可空 TreePhotos 外鍵
您的應用程式可能會讓你創建樹之前已經採取了照片,但樹仍然需要填充該圖片屬性。 我會將邏輯添加到樹存儲庫的插入或更新方法來創建預設情況下,新樹時並不提供一個空圖片:
public void InsertOrUpdate(Tree tree)
{
if (tree.TreeId == default(int)) {
if (tree.Photo == null)
{
tree.Photo = new TreePhoto { Photo = new Byte[] { 0 },
Caption = "No Photo Yet" };
}
context.Trees.Add(tree);
}
...
我想在這裡集中更大的問題是這一問題如何影響更新。 想像一下你有一棵樹和它所需的照片,已存儲在資料庫中。 你想要能夠編輯一棵樹並不需要與照片進行交互。 您要檢索的樹,也許代碼如"上下文。特es。Find(someId)"。保存時間時,您將獲得一個驗證錯誤,因為樹需要一張照片。 但樹都有一張照片 ! 它是在資料庫中 ! 這是怎麼回事?
問題是:當你首先執行查詢以檢索表,忽視了相關的照片,將從資料庫返回只樹的標量值和照片將為空 (請參見圖 5)。
從它的照片沒有資料庫中檢索的圖 5 樹實例
MVC 模式粘結劑和 EF 有能力驗證所需的注釋。 當保存已編輯的樹的時候,它的照片還將為空。 如果你讓 MVC 控制器代碼中執行 ModelState.IsValid 檢查,就會看到這張照片是缺少。 IsValid 將虛假和控制器甚至不會去打擾調用存儲庫。 在我的應用程式,我已經刪除模型聯程式設計序驗證,這樣我可以讓我負責任何伺服器端驗證的存儲庫代碼。 當存儲庫調用 SaveChanges 時,EF 驗證將檢測丟失的照片,並引發異常。 但在存儲庫中,我們有機會處理這個問題。
如果樹類有外鍵屬性 — — 例如,int PhotoId — — 這是必需的 (允許您刪除照片導航性能要求),從資料庫外的鍵值會已經被用來填充 PhotoId 樹實例的屬性。 樹將是有效的和 SaveChanges 將能夠更新命令發送到資料庫。 換句話說,如果有外鍵屬性,樹本來正確,甚至沒有照片實例。
但是沒有外鍵,您再次需要某種機制提供在保存更改前的照片。 如果您有您的代碼第一類和上下文設置為執行延緩載入,任何提及的照片,在您的代碼將導致 EF 將從資料庫載入該實例。 我還是有些過時,懶的時候載入時,我個人的選擇可能會從資料庫中執行顯式載入。 在新行的代碼 (在以下示例中,在我打電話負載的最後一行) 使用 DbContext 方法載入相關的資料:
public void InsertOrUpdate(Tree tree)
{
if (tree.TreeId == default(int)) {
...
} else {
context.Entry(tree).State = EntityState.Modified;
context.Entry(tree).Reference(t => t.Photo).Load();
}
}
這使得 EF 快樂。 因為照片很重要,和 EF 將發送更新到資料庫,修改樹將會驗證。 這裡的關鍵是你要確保照片不是空的; 我已經給你們滿足該約束的一種方法。
比較的角度
如果樹類只是有一個 PhotoId 屬性,這並不會有必要。 PhotoId 整型屬性的直接影響是圖片屬性不再需要所需的注釋。 為數值型別,它必須始終具有價值,滿足要求一棵樹必須有一張照片,即使它並不表示為一個實例。 只要在 PhotoId 中有一個值,將滿足要求,因此下麵的代碼工作:
public class Tree
{
// ...
Other properties
public int PhotoId { get; set; }
public TreePhoto Photo { get; set; }
}
當該控制器的編輯方法從資料庫中檢索一棵樹時,都將填充 PhotoId 標量屬性。 只要您強制 MVC (或您使用的任何應用程式框架) 往返價值,更新樹中,到時候 EF 將關心空圖片屬性。
更容易的但不是神奇
雖然 EF 團隊提供了更多的 API 邏輯,以説明斷開連接的情況下,它仍然是你的工作,瞭解 EF 的工作原理和其期望是當您正在移動的資料。 是的編碼是簡單得多的如果您在您的類中包含外鍵,但他們是您的類和你是什麼應該和不應該在他們的最好的判斷。 不過,如果您的代碼是我的責任,我一定會迫使你說服我,你的原因不外鍵屬性包括抵銷的好處包括他們。 EF 都會這樣做的一些工作為您的外鍵都有。 但如果他們缺席,只要你瞭解 EF 所期望和如何滿足這些期望,您應該能夠獲得您斷開連接的應用程式列為您希望的方式。
Julie Lerman 是 Microsoft MVP。淨的導師和顧問住在山上的佛蒙特。您可以找到她提交資料訪問和其他的微軟。使用者組和世界各地的會議的淨主題。在她的博客 thedatafarm.com/blog 和"程式設計實體框架"的作者是 (2010 年) 和"程式設計實體框架:代碼第一次"(2011 年),都從 O'Reilly 介質。跟她在 Twitter 上 twitter.com/julielerman。
多虧了以下技術專家,檢討這篇文章: Jeff Derstadt 和Rick Strahl