天天看點

EFCore中如何移除主外鍵關系

目錄

我用EFCore寫了一個blog程式,我要通過寫文章來分享自己的知識,我定義了一個

Article

用來存放文章資訊,我還定義了一個

Category

用來存放文章的分類,

Category

Article

是一對的關系。我的代碼實作如下:

Article

public class Article
{
    public int Id {get;set;}
    
    public int CategoryId {get;set;}
    
    //導航屬性,efcore會自動建立主外鍵關系
    public Category Category {get;set;}
}           

Category

public class Category
{
    public int Id {get;set;}
    
    //導航屬性,efcore會自動建立主外鍵關系
    public List<Article> Articles { get; set; }
}           

MyBlogDbContext

public class MyBlogDbContext:DbContext
{
    public MyBlogDbContext(DbContextOptions options):base(options)
    {}
    
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        //Article
        var articleBuilder = modelBuilder.Entity<Article>();
        articleBuilder.ToTable("Article");
        articleBuilder.HasKey(article => article.Id);
        articleBuilder.HasIndex(article => article.CategoryId);
        
        //Category
        var categoryBuilder=modelBuilder.Entity<Category>();
        categoryBuilder.ToTable("Category");
        categoryBuilder.HasKey(category => category.Id);
    }
}           

  1. 當我想添加一片文章的時候,主外鍵要求我先添加這個文章的分類才允許我添加文章
  2. 當我想删除一個分類的時候,主要建會将我的文章也删除
  3. 總之,級聯給我帶來了很多煩惱

解決辦法

  1. 修改資料,禁用級聯功能
  2. 删除我們代碼中的導航屬性,阻止生成級聯關系

以上兩種辦法都不是我想要的:

​ 我不想去操作資料庫,因為我用了code first,ef會去操作資料庫,是以我不想去修改資料庫的級聯功能(實際項目中我還是回去禁用資料庫的級聯關系)。

​ 我也不想去删除導航屬性,因為我想用ef core的Include功能。

​ 在不修改資料的設定,也不删除導航屬性的前提下實作禁用級聯功能,我的做法是禁止級聯關系的生成,可能你會說你這等于變相修改了sql,但是我确實沒有寫sql删除級聯關系,也沒有删除導航屬性,總之,我的目的達到了,效果還不錯。那麼我是這麼實作的?

​ 我要做的是取翻看EFCore的代碼,找到真正生成級聯sql的地方然後重寫,幸運的是我找到了,這個類就是

SqlServerMigrationsSqlGenerator

,我的實作如下:

CustomMigrationsSqlGeneratore

public class CustomMigrationsSqlGeneratore : SqlServerMigrationsSqlGenerator
    {
        public CustomMigrationsSqlGeneratore( MigrationsSqlGeneratorDependencies dependencies,  IMigrationsAnnotationProvider migrationsAnnotations) : base(dependencies, migrationsAnnotations)
        {
        }
        //重寫這個方法
        protected override void Generate(CreateTableOperation operation, IModel model, MigrationCommandListBuilder builder)
        {
            //删除級聯關系
            RemoveForeignKeysHelper.ExecuForeignKeys(operation);
            base.Generate(operation, model, builder);
        }
    }           

RemoveForeignKeysHelper

public class RemoveForeignKeysHelper
    {
        //定義個全局變量,用來存儲需要移除的級聯屬性
        internal static ConcurrentDictionary<string, List<string>> RemoveForeignKeys = new ConcurrentDictionary<string, List<string>>();

        public  static void ExecuForeignKeys(CreateTableOperation operation)
        {
            if (RemoveForeignKeys.TryGetValue(operation.Name, out List<string> columns))
            {
                operation.ForeignKeys
                    .Where(item => item.Columns.Intersect(columns).Count() > 0)
                    .ToList()
                    .ForEach(item => operation.ForeignKeys.Remove(item));
            }
        }
    }           

EntityTypeBuilderExtensions

為EntityTypeBuilder添加擴充方法,通過擴充方法紀錄那些需要被移除的級聯關系

public static class EntityTypeBuilderExtensions
    {
        public static EntityTypeBuilder<T> RemoeForeignKey<T>(this EntityTypeBuilder<T> builder,string name) where T : class
        {
            var tableName = builder.Metadata.FindAnnotation("Relational:TableName").Value.ToString();
            RemoveForeignKeysHelper.RemoveForeignKeys.AddOrUpdate(tableName, new List<string> { name },(value,values)=> {
                values.Add(name);
                return values.Distinct().ToList();
            });
            return builder;
        }
    }           

DbContextOptionsBuilderExtensions

通過依賴注入,将生産sql的服務替換成我們自己的

public static class DbContextOptionsBuilderExtensions
    {
        public static DbContextOptionsBuilder UseRemoveForeignKeyService(this DbContextOptionsBuilder options)
        {
            options.ReplaceService<IMigrationsSqlGenerator, CustomMigrationsSqlGeneratore>();
            return options;
        }
    }           

到此,所有的代碼都已經搞定,我們來看看怎麼在我們的代碼中引入這些功能。

首先,我們在建立Model的時候設定我要移除的級聯關系,修改我們之前定義的

MyBlogDbContext

MyBlogDbContext

public class MyBlogDbContext:DbContext
{
    public MyBlogDbContext(DbContextOptions options):base(options)
    {}
    
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        //Article
        var articleBuilder = modelBuilder.Entity<Article>();
        articleBuilder.ToTable("Article");
        articleBuilder.HasKey(article => article.Id);
        articleBuilder.HasIndex(article => article.CategoryId);
        
        ///你沒有看錯就是這麼順滑
        articleBuilder.RemoeForeignKey("CategoryId");
        
        //Category
        var categoryBuilder=modelBuilder.Entity<Category>();
        categoryBuilder.ToTable("Category");
        categoryBuilder.HasKey(category => category.Id);
    }
}           

然後,将

IMigrationsSqlGenerator

替換成我們自定義的類

CustomMigrationsSqlGeneratore

//AddDbContxt記得吧,在Startup中或者在你自己擴充的IServiceCollection方法中
service.AddDbContext<MicroBlogDbContext>(options =>
            {
                //核心操作就在這裡
                options.UseRemoveForeignKeyService();
                options.UseMySql(connStr,config=> {
                    config.CharSetBehavior(CharSetBehavior.AppendToAllColumns);
                    config.AnsiCharSet(CharSet.Latin1);
                    config.UnicodeCharSet(CharSet.Utf8mb4);
                });
            });           

完!

​ 我寫了個擴充,目前支援mysql和sqlserver,如果有機會我也會實作其它資料庫的擴充。

MicroFX.EntityFrameworkCore.RemoveForeignKey Micro.EntityFrameworkCore.RemoveForeignKey,SqlServer Micro.EntityFrameworkCore.RemoveForeignKey.MySql