Entity Framework 實踐系列 —— 搞好關系 - 兩情相悅(雙向一對一)
自從搞好了單向一對一關系,裝滿代碼的心中塞進了揮之不去的情絲 —— 單相思。誰都知道音樂世界離不開情感,可誰又知道代碼世界同樣需要情感。
單相思是星星之火,它存在的唯一目的是點燃兩個人的世界。讓我們緊握心中的火苗,開始兩情相悅的征途吧。
先回顧一下單相思的場景:
BlogSite單相思BlogUser。

BlogSite樣子:
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; }
}
BlogUser樣子:
public class BlogUser
{
public Guid UserID { get; set; }
public string Author { get; set; }
}
OnModelCreating中的定義:
modelBuilder.Entity<BlogSite>().HasRequired(b => b.BlogUser)
.WithMany().HasForeignKey(b => b.UserID);
可以看出,現在的情形是BlogSite的心中有BlogUser,BlogUser的心中沒有BlogSite。
要讓兩情相悅,只要BlogSite能打動BlogUser,讓BlogUser心中有他。在現實世界這談何容易,但在代碼世界你可以隨心所欲。我們可以先強行讓BlogUser心中有BlogSite,代碼如下:
public class BlogUser
{
public Guid UserID { get; set; }
public string Author { get; set; }
public virtual BlogSite BlogSite { get; set; }
}
運行一下測試,看會發生什么?
會不會是這樣?BlogUser抽BlogSite兩巴掌:“這些年你TMD死哪里去了。。。 ”
代碼世界可沒有現實世界那樣“暴力”,只是返回一個異常:
System.Data.SqlClient.SqlException: Invalid column name 'BlogSite_BlogID'.
生成的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],
[Extent3].[BlogSite_BlogID] AS [BlogSite_BlogID]
FROM [dbo].[BlogSite] AS [Extent1]
INNER JOIN [dbo].[BlogUser] AS [Extent2]
ON [Extent1].[UserID] = [Extent2].[UserID]
LEFT OUTER JOIN [dbo].[BlogUser] AS [Extent3]ON [Extent1].[UserID] = [Extent3].[UserID]
WHERE 1 = [Extent1].[IsActive]
兩情相悅的確不容易,剛想悅一下就被拒絕了,而且是莫明的理由。
別泄氣,解決問題和追女孩一樣,要有一種鍥而不舍的精神。
別著急,先讓自己靜下來,來一杯咖啡,或者寫寫博客。。。讓問題在思維中浸泡一會。。。

浸泡之后,馬上回來。
不要急于去找答案,而是要先進一步明確問題,既然是搞關系,就要仔細分析一下BlogSite與BlogUser之間的關系。
看類圖:

BlogSite有一個屬性BlogUser,BlogUser有一個屬性BlogSite;假如BlogSite是男人,BlogUser是女人,那么通過這兩個類的定義,我們知道了(當然EF也知道了) —— 男人可以娶女人,但只能娶一個;女人可以嫁給男人,但只能嫁一個。
看數據庫表結構:

BlogSite表有個UserID字段對應BlogUser的UserID主鍵。所以,一個BlogSite找對應的BlogUser很容易,拿著自己知道的UserID直接在BlogUser表中找出自己的另一半;而一個BlogUser找對應的BlogSite就難一些,先通過自己的UserID在BlogSite表中找到對應的BlogID,然后通過BlogID找到對應的BlogSite。
打個比方,兩情相悅的愛情密碼藏在男人心里,男人一眼就能看出屬于自己的女人,而女人需要先找出男人心里的愛情密碼,然后看這個密碼是不是自己。難怪男人要主動追求女人。
另外,由于BlogSite表的UserID字段不能為空,所以男人不能沒有女人,也就是男人依賴(Dependent)女人;BlogUser表中沒有BlogID,女人是主角(Principal),是等著男人來追求的。
通過上述的分析,我們可以理出這樣的關系:
男人(BlogSite)需要(HasRequired)女人(BlogUser),女人也需要女人;男人通過愛情密碼(UserID)找到屬于自己的女人,并依賴她(WithRequiredDependent);女人通過愛情密碼(UserID)確定她可以主宰(WithRequiredPrincipal)的男人。
有了這樣的關系描述,我們可以在EF中通過Fluent API寫出來,有兩種寫法,效果一樣:
寫法一(出自男人之手):
modelBuilder.Entity<BlogSite>().HasRequired(b => b.BlogUser)
.WithRequiredDependent(u => u.BlogSite).Map(conf => conf.MapKey("UserID"));
寫法二(出自女人之手):
modelBuilder.Entity<BlogUser>().HasRequired(u => u.BlogSite)
.WithRequiredPrincipal(b => b.BlogUser).Map(conf => conf.MapKey("UserID"));
讓我們測試一下,看看他們是否真的兩情相悅。測試代碼如下:
[TestMethod]
public void GetAllBlogSites_Test()
{
_aggBlogSiteService.GetAllBlogSites().ToList()
.ForEach(
b => { Console.WriteLine("BlogApp:" + b.BlogUser.BlogSite.BlogApp +
", Author:" + b.BlogUser.BlogSite.BlogUser.Author); }
);
}
看看紅色字體部分,測試的就是是否“你中有我,我中有你”。
在測試之前,我們需要將愛情密碼隱藏,也就是把BlogSite的UserID屬性注釋掉。不然會出現錯誤 —— Each property name in a type must be unique. Property name 'UserID' was already defined.
運行測試,愛情大考驗:


pass! 愛情測試通過,可以步入婚姻的殿堂。。。

相愛容易,相處難,婚姻生活才是對愛情的真正考驗。
代碼世界也是一樣,測試通過了,但背后的代碼是否以我們期望的方式運行呢?
打開Server Server Profiler,看個究竟:
當我們獲取一個BlogSite列表時,實際執行的SQL是:
SELECT
[Extent1].[BlogID] AS [BlogID],
[Extent1].[BlogApp] AS [BlogApp],
[Extent1].[IsActive] AS [IsActive],
[Join1].[UserID1] AS [UserID],
[Join1].[Author] AS [Author],
[Join3].[BlogID] AS [BlogID1]
FROM [dbo].[BlogSite] AS [Extent1]
LEFT OUTER JOIN (SELECT [Extent2].[UserID] AS [UserID1], [Extent2].[Author] AS [Author]
FROM [dbo].[BlogUser] AS [Extent2]
LEFT OUTER JOIN [dbo].[BlogSite] AS [Extent3]
ON [Extent2].[UserID] = [Extent3].[UserID] ) AS [Join1]
ON [Extent1].[UserID] = [Join1].[UserID1]
LEFT OUTER JOIN (SELECT [Extent4].[UserID] AS [UserID2], [Extent5].[BlogID] AS [BlogID]
FROM [dbo].[BlogUser] AS [Extent4]
LEFT OUTER JOIN [dbo].[BlogSite] AS [Extent5]
ON [Extent4].[UserID] = [Extent5].[UserID] ) AS [Join3]
ON [Extent1].[UserID] = [Join3].[UserID2]
WHERE 1 = [Extent1].[IsActive]
當我們獲取一個BlogUser列表時,實際執行的SQL是:
SELECT
1 AS [C1],
[Extent1].[UserID] AS [UserID],
[Extent1].[Author] AS [Author],
[Extent3].[BlogID] AS [BlogID],
[Extent3].[BlogApp] AS [BlogApp],
[Extent3].[IsActive] AS [IsActive],
[Extent4].[UserID] AS [UserID1]
FROM [dbo].[BlogUser] AS [Extent1]
LEFT OUTER JOIN [dbo].[BlogSite] AS [Extent2] ON [Extent1].[UserID] = [Extent2].[UserID]
LEFT OUTER JOIN [dbo].[BlogSite] AS [Extent3] ON [Extent2].[BlogID] = [Extent3].[BlogID]
LEFT OUTER JOIN [dbo].[BlogSite] AS [Extent4] ON [Extent2].[BlogID] = [Extent4].[BlogID]
看到這樣的SQL,你也許會感嘆:為了兩情相悅,付出這么大的代價,值得嗎?
值得!目前的代價只是暫時的,兩情相悅,共同努力,一切都可以改變!
這個SQL的問題目前還沒找到解決方法,先放著,隨著博客園團隊的成長,一定會解決這個問題!
更新1:17:30左右找到SQL問題的解決方法,下一篇文章的內容就是這個。
更新2:SQL生成問題的解決方法見Entity Framework 實踐系列 —— 搞好關系 - 兩情相悅(雙向一對一)- 續
浙公網安備 33010602011771號