EntityFramework Core 遷移忽略主外鍵關係

前言

本文來源於一位公衆號童鞋私信我的問題,在我若加思索後給出了其中一種方案,在此之前我也思考過這個問題,藉此機會我稍微看了下,目前能夠想到的也只是本文所述方案。

爲何要忽略主外鍵關係

我們不僅疑惑爲何要忽略主外鍵關係呢?不難想到,相對於大型企業而言大部分都會採用不建立主外鍵關係(簡稱,外鍵約束),外鍵約束毫無疑問維護了數據一致性,但對其進行操作時很容易造成問題,級聯刪除只是其一。如果對於經常需要操作的表建立了外鍵約束,那麼會嚴重影響插入、刪除和更新的性能,因爲在執行這些操作之前,數據庫需要檢查其是否違反數據完整性,這也就是爲何大多數不管是DBA或者架構師完全放棄使用外鍵約束的原因,在分析數據庫,它們並不能以事務方式(一次一行)來處理數據,而是批量處理,性能是一切,這是其二。隨着業務需求變化在設計數據庫時,可能需要存儲歷史數據庫中的舊數據,而這些舊數據可能對數據質量和完整性沒有嚴格要求。爲了能夠容納舊的髒數據,可直接清理和轉換舊數據,而放棄在數據庫級別上強制執行參照完整性,這是其三。所以基於以上幾點理由,忽略外鍵約束是有其原因所在,當然,是否放棄外鍵約束,可能取決於架構師或者DBA,反正決策權不在於搬磚的我們,我們知道其原因就好。

示例程序

以下示例皆在控制檯中進行,老規矩,我們先給出示例模型,依然是Blog和Post兩個實體,如下:

public class Blog
{
    public int Id { get; set; }
    public string Name { get; set; }
    public List<Post> Posts { get; set; }
}

public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }

    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}

接下來則是定義上下文,如下:

public class EFCoreDbContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer("Server=.;Database=EFCore;Trusted_Connection=True;");
    }
}

 

 

忽略外鍵約束

上述即使我們沒有顯式通過註解或Fluent APi配置關係,但是會根據約定而發現其關係,所以最終會建立外鍵約束,那麼我們怎麼才能在遷移時不建立外鍵約束呢?對依賴實體通過註解顯式配置不映射,如下:

[NotMapped]
public List<Post> Posts { get; set; }

請注意,這裏必須是對依賴實體進行顯式註解不映射,若是對依賴實體上的主體導航屬性配置依然會生成外鍵約束,若是對外鍵進行註解不映射也是同理,只不過生成的外鍵名稱和默認的外鍵名稱不一樣而已。很顯然,進行如上不建立外鍵約束後,當我們通過主體添加依賴體數據時將不會持久化到表中,比如如下通過Blog添加Posts

var context = new EFCoreDbContext();

context.Add(new Blog()
{
    Name = "Jeffcky",
    Posts = new List<Post>()
    {
        new Post()
        {
            Title = "EntityFramework Core"
        }
    }
});

var effectedRows = context.SaveChanges();

同理,當通過主體進行飢餓加載時將會拋出異常(無論是lambda表達式或字符串),比如如下,因爲二者已經沒有任何關聯關係

var blog = context.Blogs.Include(d => d.Posts).FirstOrDefault(d => d.Id == 1);

基於上述,似乎沒有什麼很好的方式,只能採用最原始方式生成外鍵約束後,在遷移類中刪除外鍵約束或數據庫表手動刪除外鍵約束,這樣仍然可以很好的使用飢餓加載導航屬性。一旦實體比較多,手動刪除又顯得比較麻煩,我們可以寫個程序,當遷移完畢後刪除數據庫表所有外鍵,如下截取刪除外鍵的代碼片段,不知是否行得通,理論上應該是可以實現的。

public static class RemoveForeignKeyExetension
    {
        public static ModelBuilder RemoveForeignKeys(this ModelBuilder modelBuilder)
        {
            var entityTypes = modelBuilder.Model.GetEntityTypes().ToList();

            for (int i = 0; i < entityTypes.Count(); i++)
            {
                var entityType = entityTypes[i];

                var references = entityType.GetDeclaredReferencingForeignKeys().ToList();

                using (((Model)entityType.Model).Builder.Metadata.ConventionDispatcher.DelayConventions())
                {
                    foreach (var reference in references)
                    {
                        reference.DeclaringEntityType.RemoveForeignKey(reference);
                    }
                }
            }
            return modelBuilder;
        }
    }

忽略外鍵約束(SQLite)

上述是針對SQL Server所做的測試,理論上MySQL同理,但對於SQLite數據庫,EF Core 3.x提供了全局方案:通過數據連接字符串配置【Foreign Keys = False】全局抑制建立外鍵約束。

optionsBuilder.UseSqlite("Database=sqlite.db;Foreign Keys=False");

總結

官方團隊好像並未提供針對SQL Server或MySQL忽略而不建立外鍵約束而可以加載導航屬性的辦法,只能採取笨拙或者如上所述寫個程序去刪除外鍵約束或者通過註解方式實現,但是一旦使用註解將無法加載導航屬性,那麼用EF Core就失去了很大的意義,我認爲

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章