注:本文針對的是 Entity Framework Code First 場景。
之前寫過三篇文章試圖理清Entity Framework中的一對一關系(單相思(單向一對一), 兩情相悅(雙向一對一), 兩情相悅-續),但當時理得不夠清,新的一年重新理一理。
當時“一對一”的實體關系,對應的數據庫關系是外鍵關聯(實際上是一種“一對多”關系,所以映射時用了WithMany)。而數據庫中的“一對一”關系是共享主鍵(這是我個人的理解,不妥之處,歡迎指出),下篇文章將要理的就是這個關系。
由於雙向“一對一”關系很少用到,而且不推薦使用,為了更清楚地理解,我們這里只談單向一對一關系,也就是“基於外鍵關聯的單向一對一關系(One-to-one Unidirectional relationships)”,對應的之前的文章是單相思(單向一對一)。
1. 類圖
2. 類的定義
public class BlogSite
{
public int BlogID { get; set; }
public string BlogApp { get; set; }
public bool IsActive { get; set; }
public Guid UserID { get; set; }
public virtual BlogUser BlogUser { get; set; }
}
public class BlogUser
{
public Guid UserID { get; set; }
public string Author { get; set; }
public int BlogID { get; set; }
}
3. 數據庫結構
4. Enitity Framework映射關系定義
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<BlogSite>()
.HasRequired(b => b.BlogUser)
.WithMany();
}
怎么理解這里的HasRequired與WithMany呢?
我的理解是:HasRequired是針對BlogSite與BlogUser的關系,WithMany是針對BlogUser與BlogSite的關系。.HasRequired(b => b.BlogUser).WithMany()表示BlogSite與BlogUser存在Required關聯關系(One-To-One, 每一個BlogSite都有一個對應的BlogUser),而這個關聯對BlogUser來說是One-To-Many(一個BlogUser可以有多個BlogSite)。
你也許會疑惑?明明是單向一對一的實體關系,這里怎么弄出個一對多的關系?
如果有這樣的疑惑,屬正常現象,我從去年7月份寫那篇文章開始疑惑,一直疑惑到現在,寫篇文章時才有點搞明白。
注意,“單向一對一”是什么?是實體關系;Entity Framework是什么?是O/RM,是用來映射實體關系與數據庫關系;還少了什么?數據庫關系。
從上面的圖中的數據庫結構可以看出,BlogSite與BlogUser之間是外鍵關聯關系,下面的圖可以更清楚地看出這一點。
這個外鍵關聯表示的就是BlogUser與BlogSite之間是一對多的數據庫關系。
總結一下:
實體關系 —— BlogSite與BlogUser之間的單向一對一
數據庫關系 —— BlogUser與BlogSite之間的一對多
是不是這樣呢?Entity Framework是不是也是這樣認為的呢?我們來驗證一下。
怎么知道Entity Framework的想法呢?
通過EDM。
可這里是Code First?
不管什么First,都有EDM,因為這是Entity Framework的地圖,沒有它,Entity Framework就會暈頭轉向。Code First的EDM是在EF運行時生成的,不是沒有地圖,只是在Entity Framework的心中,我們看不到而已。
怎么讓Entity Framework說出心里話呢?
從Morteza Manavi大師那學到一招,代碼如下:
using (var context = new Context())
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
using (XmlWriter writer = XmlWriter.Create(@"Model.edmx", settings))
{
EdmxWriter.WriteEdmx(context, writer);
}
}
通過上面的代碼,你就可以拿到EF心中的地圖 —— edmx文件。請看地圖:
果然,EF心中的地圖就是BlogUser與BlogSite的一對多關系。地圖的作用是什么?是讓EF通過地圖在數據庫找到對應的數據。地圖是如何產生的?是我們通過FluentAPI告訴Entity Framework:.HasRequired(b => b.BlogUser).WithMany()。
我們再來剖析一下.HasRequired(b => b.BlogUser).WithMany()。
之前,我一直被困擾,是因為總是把這里的定義當作實體的關系的定義。錯!這里雖然用的是實體進行定義,但定義的是實體與數據庫中的數據之間的映射關系(這本來就是常識,竟然被忽略了),更多的是告訴EF這些實體在數據庫中的數據關系。EF最終是根據這個定義生成相應的SQL。
那我們從生成查詢SQL的角度來理解一下:
.HasRequired(b => b.BlogUser)告訴EF,這是一個INNER JOIN查詢(BlogSite INNER JOIN BlogUser);但INNER JOIN還需要條件,WithMany()告訴EF這是一個外鍵關聯,EF據此進行推斷,從BlogUser中找到主鍵UserID,並檢查BlogSite中是否存在名為UserID的屬性,如果存在,就以此為外鍵進行查詢。而我們的BlogSite中有UserID,於是生成下面的SQL:
SELECT
[Extent1].[BlogID] AS [BlogID],
[Extent1].[BlogApp] AS [BlogApp],
[Extent1].[IsActive] AS [IsActive],
[Extent1].[UserID] AS [UserID],
[Extent2].[UserID] AS [UserID1],
[Extent2].[Author] AS [Author]
FROM [dbo].[BlogSite] AS [Extent1]
INNER JOIN [dbo].[BlogUser] AS [Extent2] ON [Extent1].[UserID] = [Extent2].[UserID]
WHERE 1 = [Extent1].[IsActive]
小結
理清“基於外鍵關聯的單向一對一關系”,關鍵在於對modelBuilder.Entity<A>().HasRequired(A => A.B).WithMany()的理解。
我再來理解一次:
.HasRequired(A => A.B) 表示:1)實體A與實體B是一對一關系,實體A有一個導航屬性A.B;2)在數據庫中表A與表B存在一對一關聯(INNER JOIN)。
.WithMany() 表示:1) 實體B與實體A可以沒有關系,也可以是一對多關系;2)在數據庫中表A與表B存在外鍵關聯。
上面全是我的個人理解,真正理清Entity Framework中的關系需要大家的力量,我只是拋個磚。
除了“基於外鍵關聯的單向一對一關系”,還有“基於共享主鍵的單向一對一關系”,這也是我們開發中經常碰到的一種關系,比如博客文章(BlogPost)與文章內容(PostBody),新聞(NewsItem)與新聞內容(NewsBody)。下一篇文章將會理理這個關系。