EF Core 2.x 升級 3.x 問題兩則
1 |
我的部落格這兩天悄悄從 .NET 5 升級到 .NET 6。
過程遇到 EF Core 2.2 升 6.0 產生的奇怪問題,原本以為要搞很久,但修掉兩個 Issue 後意外沒再發現其他問題,簡單測試主要功能都正常,我就當改好了直接上線全民公測,大家若有發現問題請再回報唄。
【本文開始】
2020 年底部落格從 ASP.NET Core 3.1 升到 5.0,.NET 6 在去年 11 月推出,拖了大半年,眼看 .NET 7 都快出來了,總算抽出時間把部落格平台再從 .NET 5 升級到 .NET 6。(明明是 .NET 5 升 6,這文章標題怎麼是 EF Core 2 升 3?就繼續看下去吧)
心想 .NET 5 到 6 改動不大,預期不需要改太多程式,不料升級之後,EF Core 部分直接爆炸,問題出在我的 EF Core 相關程式庫還在 2.2 版,用 .NET 5 可以跑,升到 .NET 6,一執行 ctx.Database.GetDbConnection() 便冒出錯誤,完全沒法連資料庫:
Exception thrown: 'System.TypeInitializationException' in Microsoft.EntityFrameworkCore.dll
The type initializer for 'Microsoft.EntityFrameworkCore.Query.ResultOperators.Internal.TrackingExpressionNode' threw an exception.
TypeInitializationException: The type initializer for 'Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions' threw an exception.
InvalidOperationException: Sequence contains more than one matching element
簡單,把 EF Core 2.2 升到 6.0 不就好了?於是我將幾個 EF Core 相關的 2.2.2 版程式庫升到 6.0.8:
升級後,資料庫可以連線了,但變成以下這段程式出錯:
ctx.Posts.FirstOrDefault(p =>
p.Slug.Equals(slug, StringComparison.OrdinalIgnoreCase));
錯誤訊息指出 EF Core 6 無法將 string.Equals(value, StringComparison.OrdinalIgnoreCase) 轉譯成對映的 SQL 比對語法。媽呀,該不會有一堆不相容吧?
System.InvalidOperationException: 'The LINQ expression 'DbSet<Post>()
.Where(p => p.Slug.Equals(
value: __slug_0,
comparisonType: OrdinalIgnoreCase))' could not be translated.
Additional information: Translation of the 'string.Equals' overload with a 'StringComparison' parameter is not supported.
See https://go.microsoft.com/fwlink/?linkid=2129535 for more information.
Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly
by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'.
See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.'
本到官方文件,由於比對是否區分大小寫實務上是由 DB Collation 決定,更何況 string.Equals() 還有國別區域相關參數,C# 很難依據 DB 設定對映出正確邏輯;另一方面,若要強轉 Collation 再比對將導致無法使用 Index 加速,故新版 EF Core 只接受 Equals 轉成 SQL =。更進一步發現,這是 2.2 轉 3.1+ 就會遇到的差異,我撐到今天才遇到,噗。
SQLite 資料庫預設不分大小寫,故這段改成 p.Slug == slug 就算過關。
接著著冒出更離奇的錯誤。
post.Comments = ctx.Comments.Where(o => o.PostKey == post.PostKey)
.OrderByDescending(o => o.PubDate).ToList();
冒出 Microsoft.Data.Sqlite.SqliteException: 'SQLite Error 1: 'no such column: c.PostKey1'.' 錯誤,不知為何 EF Core 在轉譯這段成 SQL 語法時會出現 c.PostKey 及 c.PostKey1 兩個欄位,後者並非 Comment 包含的欄位,是 EF Core 產生出來的。
爬文查到相似案例,一樣是 2.2 升 3.1 就該遇到的問題。推敲關鍵在於我的 Post 定義了 IList<Comment> Comments 屬性,Comment 有個 PostKey 欄位指向 Post 的 PostKey,但我沒明確定義 Post 與 Comment 的 Primary Key / Foreign Key 對映,EF Core 試著自己產生時出了差錯。參考 Stackoverflow 討論,在 DbContext 的 OnModelCreating 加入以下這段,問題解決。
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// ... 略 ...
modelBuilder.Entity<Post>().HasMany(p => p.Comments).WithOne().HasPrincipalKey("PostKey").HasForeignKey("PostKey");
// ... 略 ...
}
When upgrade my web site from ASP.NET Core 5.0 to 6.0, I got two issues from upgrading EFCore 2.2 to 3.1.
Comments
# by 樂透無名
眼花了,感謝指導