天天看點

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;");
    }
}      
EntityFramework Core 遷移忽略主外鍵關系
EntityFramework Core 遷移忽略主外鍵關系

忽略外鍵限制

上述即使我們沒有顯式通過注解或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);      
EntityFramework Core 遷移忽略主外鍵關系

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

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就失去了很大的意義,我認為

你所看到的并非事物本身,而是經過诠釋後所賦予的意義