Entity Framework - 理清關系 - 基於共享主鍵的單向一對一關系


在上篇文章中,我們理了一下基於外鍵關聯的單向一對一關系。在這篇文章中,我們理一理“基於共享主鍵的單向一對一關系”,找出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();


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM