EF CORE使用反射實現動態DbSet
為什麼要動態配置DbSet?
在各種EF CORE的教程中我們可看到,配置DbContext資料庫上下文的模型時都是手寫具體的實體類名來新增
例如手動寫DbSet:public DbSet<Blog> Blogs { get; set; }
或者在OnModelCreating中進行配置
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property(b => b.Url) .IsRequired(); }
但這隻適用於實體類比較少的情況下,如果業務量很大,用到幾十上百張表,這樣去配置DbContext就比較麻煩了(其實是太懶了)
實現
具體實現其實很簡單,我們需要反射呼叫ModelBuilder.Entity方法的過載ModelBuilder.Entity<TEntity>(),看看MS DOC裡它的描述:
Entity<TEntity>()
返回一個物件,該物件可用於配置模型中給定的實體型別。 如果實體型別不是模型的一部分,則會將其新增到模型中。
正常我們在OnModelCreating
呼叫該方法時是需要寫明泛型類的,但是為了動態生成DbSet這樣去寫顯然是不現實的,那麼就需要通過反射來實現
首先,我們需要建一個專門存放實體型別的專案,或者你也可以放在現有的專案中,這裡我假裝有個叫 Model
的類庫專案
然後建立一個實體類
namespace Model
{
[Table("Blog")]
public class Blog
{
}
}
這裡用Table
這個Attribute來標識這個類是一個實體類,方便後面反射獲取以及自定義表名,當然你也可以自定義Attribute或者介面來標識
當我們的程式引用了Model專案後就可以在執行時通過AppDomain
來獲取程式集中定義的類資訊,當然你也可以通過Assembly.LoadFile()
來手動指定程式集的路徑進行載入
var definedTypes =
AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(s => s.GetName().Name=="Model")
.DefinedTypes
.ToList();
然後我們可以通過獲取介面或者Attribute的資訊來過濾得到的類資訊以免載入不需要的類
var entityTypes = definedTypes
.Where(o => o.GetCustomAttributes<Table>().Any())
.Where(o => o.IsClass && !o.IsAbstract && !o.IsGenericType)
.ToList();
下面通過反射獲取Entity方法var entityMethod = typeof(ModelBuilder).GetMethod("Entity", new Type[] { });
然後迴圈實體類資訊集合,反射呼叫Entity方法生成實體
if (entityTypes is { Count: > 0 })
{
foreach (var type in entityTypes)
{
var entityBuilder = entityMethod
.MakeGenericMethod(type)
.Invoke(modelBuilder, new object[] { });
var hasKeyMethod = entityBuilder.GetType().GetMethod("HasKey", new Type[] { typeof(string[]) });
hasKeyMethod.Invoke(entityBuilder, new object[] { new string[] { "UID" } });
}
}
這裡還通過HasKey
方法設定了主鍵,否則使用時會出現問題。EF CORE預設會把類名作為實體的表名,如果想自定義表名可以通過Table
Attribute獲取自定義的表名然後反射呼叫ToTable
方法來設定
然後就可以通過DbContext.Set<T>()來獲取DbSet<T>進行資料庫操作了