在上篇文章中,我們理了一下基於外鍵關聯的單向一對一關系。在這篇文章中,我們理一理“基於共享主鍵的單向一對一關系”,找出Entity Framework中正確的映射關系。
一、明確需求,也就是Entity Framework正確映射之后要達到的效果
1)數據庫結構要符合要求——共享主鍵。所下圖所示,數據庫中表A與表B的主鍵都是AID。
2)實體關系要符合要求——單向一對一關系。我們通過下面的UML類圖來表達:
上圖中只有A到B的關聯箭頭,這就是“單向”,這個箭頭也表示A依賴B,在代碼中的表現就是A有一個導航屬性A.B。
上圖中箭頭兩頭的兩個1就是“一對一”,存在一個A,必然存在一個對應的B;存在一個B,必然存在一個對應的A。
EDM中的實體關系要與UML類圖中的關系一致。
實體A的定義:
public class A
{
public int AID { get; set; }
public string Title { get; set; }
public B B { get; set; }
}
實體B的定義:
public class B
{
public int AID { get; set; }
public string Body { get; set; }
}
3)持久化操作要符合要求
只允許A與B一起進行持久化,測試代碼如下:
var a = new A();
a.Title = "title test";
a.B = new B();
a.B.Body = "body test";
using (EfUnitOfWork ef = new EfUnitOfWork())
{
ef.Set<A>().Add(a);
ef.SaveChanges();
}
不允許A與B各自單獨的持久化,測試代碼如下:
//不允許的持久化操作
var a = new A();
a.Title = "title a";
using (EfUnitOfWork ef = new EfUnitOfWork())
{
ef.Set<A>().Add(a);
ef.SaveChanges();
}
var b = new B();
b.Body = "body b";
using (EfUnitOfWork ef = new EfUnitOfWork())
{
ef.Set<B>().Add(b);
ef.SaveChanges();
}
4)生成的SQL查詢語句要符合要求
比如這樣的查詢:
using (EfUnitOfWork ef = new EfUnitOfWork())
{
ef.Set<A>()
.Include(a => a.B)
.Where(a => a.AID == 1)
.ToList();
}
生成的SQL查詢語句應該是:
SELECT
[Extent1].[AID] AS [AID],
[Extent1].[Title] AS [Title],
[Extent2].[AID] AS [AID1],
[Extent2].[Body] AS [Body]
FROM [dbo].[A] AS [Extent1]
INNER JOIN [dbo].[B] AS [Extent2] ON [Extent1].[B_AID] = [Extent2].[AID]
WHERE 1 = [Extent1].[AID]
二、用最笨的方法找出答案
這個最笨的方法是,對四種映射關系逐一進行測試,看哪個與我們想要的效果最一致。
下面我們分別來看看在不同的映射關系配置下Entity Framework的行為。
1).HasRequired(a => a.B).WithMany();
FluentAPI:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<B>().HasKey(b => b.AID);
modelBuilder.Entity<A>().HasRequired(a => a.B).WithMany();
}
a) EF生成的數據庫結構:
表A多了一個字段B_AID,並通過B_AID關聯至表B的主鍵AID。數據庫結構不一致,不符合要求。
b) EF生成的EDM圖:
EDM與UML中的關系定義不一致,這里是一對多關系,我要的是一對一關系,不符合要求。
c) 不允許實體A的單獨持久化,但允許實體B的單獨持久化,不符合要求。
d) 生成的SQL查詢語句符合要求。
【小結】數據庫結構不符合要求,實體關系不符合要求,持久化不符合要求,生成的SQL查詢語句符合要求。
2).HasRequired(a => a.B).WithOptional();
FluentAPI:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<B>().HasKey(b => b.AID);
modelBuilder.Entity<A>().HasRequired(a => a.B).WithOptional();
}
a) EF生成的數據庫結構:
數據庫結構符合要求。
b) EF生成的EDM圖:
關聯的一端是0..1,也就是允許“存在一個B,不存在一個A”的情況,實體關系不符合要求。
c) 不允許實體A的單獨持久化,但允許實體B的單獨持久化,不符合要求。
d) 生成的SQL查詢語句符合要求。
【小結】數據庫結構符合要求,實體關系不符合要求,持久化不符合要求,SQL查詢語句符合要求。
3).HasRequired(a => a.B).WithRequiredDependent();
FluentAPI:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<B>().HasKey(b => b.AID);
modelBuilder.Entity<A>().HasRequired(a => a.B).WithRequiredDependent();
}
a) 生成的數據庫結構與.WithOptional();一樣,符合要求。
b) EF生成的EDM圖:
實體關系是“單向一對一”關系,與UML類圖一致。但類的擺放位置不一致,在EDM中,B在A的前面,也就是B是Principal,我們希望A是Pricipal,有點不一致。
c) 不允許實體A的單獨持久化,但允許實體B的單獨持久化,不符合要求。
d) 生成的SQL查詢語句符合要求。
【小結】數據庫結構符合要求,實體關系有點不符合要求(Pricipal不一致),持久化不符合要求,SQL查詢語句符合要求。
4).HasRequired(a => a.B).WithRequiredPrincipal();
FluentAPI:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<B>().HasKey(b => b.AID);
modelBuilder.Entity<A>().HasRequired(a => a.B).WithRequiredPrincipal();
}
a) 生成的數據庫結構與.WithOptional();一樣,符合要求。
b) EF生成的EDM圖:
實體關系是“單向一對一”關系,與UML類圖一致,A是Pricipal,符合要求。
c) 允許實體A的單獨持久化,不允許實體B的單獨持久化,不符合要求。
d) 生成的SQL查詢語句:
SELECT
[Extent1].[AID] AS [AID],
[Extent1].[Title] AS [Title],
[Extent3].[AID] AS [AID1],
[Extent3].[Body] AS [Body]
FROM [dbo].[A] AS [Extent1]
LEFT OUTER JOIN [dbo].[B] AS [Extent2] ON [Extent1].[AID] = [Extent2].[AID]
LEFT OUTER JOIN [dbo].[B] AS [Extent3] ON [Extent2].[AID] = [Extent3].[AID]
WHERE 1 = [Extent1].[AID]
我們想要的是INNER JOIN,這里卻是兩個LEFT OUTER JOIN,不符合要求。
等等。。。我們改一下查詢的LINQ語句試試,改為:
using (EfUnitOfWork ef = new EfUnitOfWork())
{
ef.Set<A>()
.Include(a => a.B)
.Where(a => a.B.AID == 1)//原來是a => a.AID == 1
.ToList();
}
改過之后,生成的SQL查詢語句符合要求:
[Extent1].[AID] AS [AID],
[Extent1].[Title] AS [Title],
[Extent2].[AID] AS [AID1],
[Extent2].[Body] AS [Body]
FROM [dbo].[A] AS [Extent1]
INNER JOIN [dbo].[B] AS [Extent2] ON [Extent1].[AID] = [Extent2].[AID]
WHERE 1 = [Extent2].[AID]
【小結】數據庫結構符合要求,實體關系符合要求,持久化不符合要求,SQL查詢語句符合要求。
三、總結與分析
數據庫結構 | 實體關系 | 持久化 | SQL查詢語句 | |
WithMany() | 不符合 | 不符合 | 不符合 | 符合 |
WithOptional() | 符合 | 不符合 | 不符合 | 符合 |
WithRequiredDependent() | 符合 | 不符合 | 不符合 | 符合 |
WithRequiredPrincipal() | 符合 | 符合 | 不符合 | 符合 |
從上面的表中可以出,成績最好的是WithRequiredPrincipal(),但它有一個地方不符合要求, 就是允許實體A的單獨持久化。
為什么實體B不能單獨持久化?看數據庫的外鍵關系就知道答案(A_B外鍵約束的功勞):
那我們只要解決“不允許實體A的單獨持久化”的問題,就能完成“基於共享主鍵的單向一對一關系”的完美映射。
既然數據庫中不好下手,那就從實體類下手吧。給實體A的導航屬性A.B加一個[Required]屬性,在實體驗證時就要求A.B必須有一個對應的實體B的實例。修改后的實體A的代碼如下:
public class A
{
public int AID { get; set; }
public string Title { get; set; }
[Required]
public B B { get; set; }
}
經過努力,我們終於找到了最佳答案——
對於“基於共享主鍵的單向一對一”關系,Entity Framework中正確的映射關系定義是:
modelBuilder.Entity<A>().HasRequired(a => a.B).WithRequiredPrincipal();