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);
}
}
主外鍵關系的問題
- 當我想添加一片文章的時候,主外鍵要求我先添加這個文章的分類才允許我添加文章
- 當我想刪除一個分類的時候,主要建會將我的文章也刪除
- 總之,級聯給我帶來了很多煩惱
解決辦法
- 修改數據,禁用級聯功能
- 刪除我們代碼中的導航屬性,阻止生成級聯關系
以上兩種辦法都不是我想要的:
我不想去操作數據庫,因為我用了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);
});
});
完!
MicroFX.EntityFrameworkCore.RemoveForeignKey擴展
我寫了個擴展,目前支持mysql和sqlserver,如果有機會我也會實現其它數據庫的擴展。
MicroFX.EntityFrameworkCore.RemoveForeignKey