EfCore3的OwnedType會導致Sql效率問題

LeoLaw 發表於 2020-10-17
SQL

最近主導了旗下某核心專案升級到EfCore3

由於之前Core2升級時候也踩過不少的坑很多東西都有規劃和準備,整體上還是沒出太大問題

 

但是最近突然發現efcore對於使用了ownedType的生成語句有問題

查詢了一下資料發現已經有人在efcore的github上開了issus了,並且還討論的蠻多的了

https://github.com/dotnet/efcore/issues/18299 

 

鑑於很多人看到一堆E文後會直接放棄,下面我簡單闡述下這個問題

EfCore自2.0的時候引入了一個叫OwnedType的特性,是用於完善之前EfCore1.x相比於Ef6使其少了的ComplexType特性

正常來說我們用Ef的時候是一個類對映到一個表,但是有時候某些表欄位過多的情況下,我們可能會想整理下把一個表裡某些資訊放到一個子類裡,但是其他資訊還是在主類

 

形如

class Order
{
    public int Id { get; set; }
    public string Title { get; set; }
    public Address Address { get; set; }
}

class Address
{
    public string Street { get; set; }
    public string City { get; set; }
}

然後DbContext裡配置下  
modelBuilder.Entity<Order>().OwnsOne(x => x.Address);  

這種情況下最理想生成的語句應該是類似  
select Id,Title,Street,City from Orders
 這個樣子的形式才對,然後EfCore內部再通過自己對映的形式把後面4個欄位對映到Author類裡的Address這個類裡

但是在EfCore3裡他生成的語句是形如  
SELECT o."Id", o."Title", t."Id", t."Address_City", t."Address_Street"
      FROM "Orders" AS o
      LEFT JOIN (
          SELECT o0."Id", o0."Address_City", o0."Address_Street", o1."Id" AS "Id0"
          FROM "Orders" AS o0
          INNER JOIN "Orders" AS o1 ON o0."Id" = o1."Id"
          WHERE (o0."Address_Street" IS NOT NULL) OR (o0."Address_City" IS NOT NULL)
      ) AS t ON o."Id" = t."Id"
      WHERE (t."Id" IS NULL)

儘管嚴格來說這個並不影響邏輯,但是這樣子join的話對Sql的效能和效率有挺不好的負面影響

就那個issus裡也有人做了測試,結果下圖

image

縱座標是每秒執行的查詢數(簡單理解為併發數吧)

橫座標是表裡有多少資料

可以看到表的資料量上升之後使用了OwnedType的EfCore3會出現顯著下滑(自己join自己多了)

 

另外該問題在EfCore2的時候是沒有的,就Core3才有(直覺是Core3的OwnedType被魔改了可以支援OwnsMany等的支援的時候引入的)

 

接下來就是一個好訊息和一個壞訊息了

好訊息是EfCore那邊認可了這個問題然後當前EfCore 5 preview-3修復了這個問題

 

壞訊息是至少目前確認這個修復不會合併到EfCore3.1.x(後期會不會有變數不清楚)

 

感覺這個蠻坑的,一般公司用的話都會是優先選用LTS,而當前的LTS就是3.1

而接下來的NetCore5(含EfCore5)並不是LTS所以對於公司組織的線上採用率應該會相對較低

那難道修復這個問題還要等一個目前還沒規劃的NetCore5.1?那是不是要等的有點兒太久了

 

最後,如果有升級EfCore3的且用了OwnedType(相當於Ef6時期的ComplexType)請謹慎評估下這個問題對你可能造成的影響

畢竟目前看起來,降級回去不大可能,燒香保佑下fix path能到3.1.x要麼就只能指望Core5.1了