日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

EntityFramework Core如何映射动态模型?

發布時間:2023/12/4 编程问答 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 EntityFramework Core如何映射动态模型? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
【導讀】本文我們來探討下映射動態模型的幾種方式,相信一部分童鞋項目有這樣的需求,比如每天/每小時等生成一張表,此種動態模型映射非常常見,經我摸索,這里給出每一步詳細思路,希望能幫助到沒有任何頭緒的童鞋

本文以.NET Core 3.1控制臺,同時以SQL Server數據庫作為示例演示(其他數據庫同理照搬),由于會用到內置APi,因版本不同可能比如構造函數需略微進行調整即可。

注:雖為示例代碼,但我將其作為實際項目皆已進行封裝,基本完全通用。本文略長(約5334字),請耐心。

動態映射模型引入前提

首先我們給出所需要用到的特性以及對應枚舉,看注釋一看便知

public?enum?CustomTableFormat {///?<summary>///?每天,(yyyyMMdd)///?</summary>[Description("每天")]DAY,///?<summary>///?每小時,(yyyyMMddHH)///?</summary>[Description("每小時")]HOUR,///?<summary>///?每分鐘(yyyyMMddHHmm)///?</summary>[Description("每分鐘")]MINUTE }[AttributeUsage(AttributeTargets.Class,?AllowMultiple?=?false)] public?class?EfEntityAttribute?:?Attribute {///?<summary>///?是否啟用動態生成表///?</summary>public?bool?EnableCustomTable?{?get;?set;?}?=?false;///?<summary>///?動態生成表前綴///?</summary>public?string?Prefix?{?get;?set;?}///?<summary>///?表生成規則///?</summary>public?CustomTableFormat?Format?{?get;?set;?}?=?CustomTableFormat.DAY;public?override?string?ToString(){if?(EnableCustomTable){return?string.IsNullOrEmpty(Prefix)???Format.FormatToDate()?:?$"{Prefix}{Format.FormatToDate()}";}return?base.ToString();} }public?static?class?CustomTableFormatExetension {public?static?string?FormatToDate(this?CustomTableFormat?tableFormat){return?tableFormat?switch{CustomTableFormat.DAY?=>?DateTime.Now.ToString("yyyyMMdd"),CustomTableFormat.HOUR?=>?DateTime.Now.ToString("yyyyMMddHH"),CustomTableFormat.MINUTE?=>?DateTime.Now.ToString("yyyyMMddHHmm"),_?=>?DateTime.Now.ToString("yyyyMMdd"),};} }

通過定義特性,主要出發點基于以下兩點:

其一:由外部注入模型而非寫死DbSet屬性訪問

其二:每個模型可定義動態映射表規則

動態映射模型方式(一)

首先我們給出需要用到的上下文,為方便演示我們以每分鐘自動映射模型為例

public?class?EfDbContext?:?DbContext {public?string?Date?{?get;?set;?}?=?CustomTableFormat.MINUTE.FormatToDate();public?EfDbContext(DbContextOptions<EfDbContext>?options)?:?base(options){} }

動態模型即指表名不同,比如我們實現每天/每小時/每分鐘動態映射模型和生成一張表,如同流動的水一樣并非一層不變,但本質上還是那滴水。在下面接口中我們需要用到每分鐘生成一張表格式,所以在上下文中定義每分鐘屬性

第一種方式則是通過實現IModelCacheKeyFactory接口,此接口將指定上下文下所有模型表名進行了緩存,所以我們可以根據所需動態模型表名進行更改即可,如下:

public?class?CustomModelCacheKeyFactory?:?IModelCacheKeyFactory {public?object?Create(DbContext?context){var?efDbContext?=?context?as?EfDbContext;if?(efDbContext?!=?null){return?(context.GetType(),?efDbContext.Date);}return?context.GetType();} }

上述其實現貌似感覺有點看不太懂,主要這是直接實現接口一步到位,底層本質則是額外調用實例一個緩存鍵類,我們將上述改為如下兩步則一目了然

public?class?CustomModelCacheKeyFactory?:?ModelCacheKeyFactory {private?string?_date;public?CustomModelCacheKeyFactory(ModelCacheKeyFactoryDependencies?dependencies):?base(dependencies){}public?override?object?Create(DbContext?context){if?(context?is?EfDbContext?efDbContext){_date?=?efDbContext.Date;}return?new?CustomModelCacheKey(_date,?context);} }public?class?CustomModelCacheKey?:?ModelCacheKey {private?readonly?Type?_contextType;private?readonly?string?_date;public?CustomModelCacheKey(string?date,?DbContext?context)?:?base(context){_date?=?date;_contextType?=?context.GetType();}public?virtual?bool?Equals(CustomModelCacheKey?other)=>?_contextType?==?other._contextType?&&?_date?==?other._date;public?override?bool?Equals(object?obj)=>?(obj?is?CustomModelCacheKey?otherAsKey)?&&?Equals(otherAsKey);public?override?int?GetHashCode()?=>?_date.GetHashCode(); }

然后在OnModelCreating方法里面進行掃描特性標識模型進行注冊,如下:

protected?override?void?OnModelCreating(ModelBuilder?modelBuilder) {var?entityMethod?=?typeof(ModelBuilder).GetMethod(nameof(modelBuilder.Entity),?new?Type[]?{?});var?assembly?=?Assembly.GetExecutingAssembly();//【1】使用Entity方法注冊foreach?(var?type?in?assembly.ExportedTypes){if?(!(type.GetCustomAttribute(typeof(EfEntityAttribute))?is?EfEntityAttribute?attribute)){continue;}if?(type.IsNotPublic?||?type.IsAbstract?||?type.IsSealed||?type.IsGenericType||?type.ContainsGenericParameters){continue;}entityMethod.MakeGenericMethod(type).Invoke(modelBuilder,?new?object[]?{?});}//【2】使用IEntityTypeConfiguration<T>注冊modelBuilder.ApplyConfigurationsFromAssembly(assembly);base.OnModelCreating(modelBuilder); }

上述第一種方式則通過反射將模型注冊,其本質則是調用modeBuilder.Entity方法,若我們在模型上使用注解,則對應也會將其應用

但注解不夠靈活,比如要標識聯合主鍵,則只能使用Fluent APi,所以我們通過在外部實現IEntityTypeConfiguration進行注冊,然后EF Core提供針對該接口程序集注冊,其底層本質也是掃描程序集,兩種方式都支持,不用再擔心外部模型注冊問題

緊接著我們給出測試模型,表名為當前分鐘,表名利用注解則不行(值必須為常量),所以我們使用如下第二種映射模型

[EfEntity(EnableCustomTable?=?true,?Format?=?CustomTableFormat.MINUTE)] public?class?Test {[Table(DateTime.Now.ToString("yyyyMMdd"))]public?int?Id?{?get;?set;?}public?string?Name?{?get;?set;?} }public?class?TestEntityTypeConfiguration?:?IEntityTypeConfiguration<Test> {public?void?Configure(EntityTypeBuilder<Test>?builder){builder.ToTable(DateTime.Now.ToString("yyyyMMddHHmm"));} }

上述第二種配置未嘗不可,但我們還有更加簡潔一步到位的操作,所以這里刪除上述第二種方式

因為在OnModelCreating方法里面,我們反射了調用了Entity方法,所以我們直接將反射調用Entity方法強制轉換為EntityTypeBuilder,在已有基礎上,代碼做了重點標識

protected?override?void?OnModelCreating(ModelBuilder?modelBuilder) {var?entityMethod?=?typeof(ModelBuilder).GetMethod(nameof(modelBuilder.Entity),?new?Type[]?{?});var?assembly?=?Assembly.GetExecutingAssembly();//【1】使用Entity方法注冊foreach?(var?type?in?assembly.ExportedTypes){if?(!(type.GetCustomAttribute(typeof(EfEntityAttribute))?is?EfEntityAttribute?attribute)){continue;}if?(type.IsNotPublic?||?type.IsAbstract?||?type.IsSealed||?type.IsGenericType||?type.ContainsGenericParameters){continue;}//?強制轉換為EntityTypeBuildervar?entityBuilder?=?(EntityTypeBuilder)entityMethod.MakeGenericMethod(type).Invoke(modelBuilder,?new?object[]?{?});if?(attribute.EnableCustomTable){entityBuilder.ToTable(attribute.ToString());}}//【2】使用IEntityTypeConfiguration<T>注冊modelBuilder.ApplyConfigurationsFromAssembly(assembly);base.OnModelCreating(modelBuilder); }

最后則是注入上下文,這里我們將內外部容器進行區分(EF Core為何分內部容器,具體原因請參看文章《EntityFramework Core 3.x上下文構造函數可以注入實例呢?》)

因在實際項目中上下文可能需要在上下文構造函數中注入其他接口,比如我們就有可能在上下文構造函數中注入接口從而根據具體接口實現來更改表架構或不同表名規則等等

static?IServiceProvider?Initialize() {var?services?=?new?ServiceCollection();services.AddEntityFrameworkSqlServer().AddDbContext<EfDbContext>((serviceProvider,?options)?=>options.UseSqlServer("server=.;database=efcore;uid=sa;pwd=sa123;").UseInternalServiceProvider(serviceProvider));services.Replace(ServiceDescriptor.Singleton<IModelCacheKeyFactory,?CustomModelCacheKeyFactory>());return?services.BuildServiceProvider(); }

由于我們已區分EF Core內外部容器,所以在替換自定義緩存鍵工廠時,不能再像如下直接調用ReplaceService方法替換,勢必會拋出異常

?options.UseSqlServer("server=.;database=efcore;uid=sa;pwd=sa123;").ReplaceService<IModelCacheKeyFactory,?CustomModelCacheKeyFactory>()

同時謹記在非Web項目中利用EF Core始終要使用作用域(scope)來釋放上下文,不像Web可基于HTTP請求作為scope,最后我們測試如下

using?(var?scope1?=?ServiceProvider.CreateScope()) {var?context1?=?scope1.ServiceProvider.GetService<EfDbContext>();context1.Database.EnsureCreated();var?type?=?context1.Model.FindEntityType(typeof(Test));Console.WriteLine(type?.GetTableName());var?tests?=?context1.Set<Test>().ToList(); }Thread.Sleep(60000);using?(var?scope2?=?ServiceProvider.CreateScope()) {var?context2?=?scope2.ServiceProvider.GetService<EfDbContext>();context2.Database.EnsureCreated();var?type?=?context2.Model.FindEntityType(typeof(Test));Console.WriteLine(type?.GetTableName());var?tests1?=?context2.Set<Test>().ToList(); }

為方便看到實際效果,我們構建兩個scope,然后睡眠一分鐘,在界面上打印輸出表名,若兩分鐘后打印表名不一致,說明達到預期

動態映射模型方式(二)

上述我們使用每分鐘規則動態映射表,同時可針對不同模型有各自規則(前綴,每小時或每天)等等,這是第一種方式

如果對第一種方式實現完全看懂了,可能會有所疑惑,因為第一種方式其接口生命周期為單例,若不需要豈不還是會將上下文中所有模型都會進行緩存嗎

調用OnModelCreating方法只是進行模型構建,我們現直接調用內置APi來手動使用所有模型,此時將不再緩存,所以不再需要IModelCacheKeyFactory接口

對EF Core稍微了解一點的話,我們知道OnModelCreating方法僅僅只會調用一次,我們通過手動構構建和處置所有模型,說了這么多,那么我們到底該如何做呢?

如果看過我之前原理分析的話,大概能知道EntityFramework Core對于模型的處理(除卻模型緩存)分為三步,除卻模型緩存:構建模型,使用模型,處置模型。

我們將OnModelCreating方法代碼全部直接復制過來,只是多了上面三步而已,在我們實例化ModelBuilder時,我們需要提供對應數據庫默認約定,然后使用模型、處置模型,結果變成如下這般

?services.AddEntityFrameworkSqlServer().AddDbContext<EfDbContext>((serviceProvider,?options)?=>?{options.UseSqlServer("server=.;database=efcore;uid=sa;pwd=sa123;").UseInternalServiceProvider(serviceProvider);var?conventionSet?=?SqlServerConventionSetBuilder.Build();var?modelBuilder?=?new?ModelBuilder(conventionSet);//?OnModelCreating方法,代碼復制options.UseModel(modelBuilder.Model);modelBuilder.FinalizeModel();???????????????)};

運行第一種方式測試代碼,然后么有問題

問題來了,要是有多個數據庫,豈不是都要像上述再來一遍?上述實現本質上是每次構造一個上下文則會構建并重新使用新的模型,所以我們將其統一放到上下文構造函數中去,然后寫個擴展方法構建模型,如下:

public?static?class?ModelBuilderExetension {public?static?ModelBuilder?BuildModel(this?ModelBuilder?modelBuilder){var?entityMethod?=?typeof(ModelBuilder).GetMethod(nameof(modelBuilder.Entity),?new?Type[]?{?});var?assembly?=?Assembly.GetExecutingAssembly();//【1】使用Entity方法注冊foreach?(var?type?in?assembly.ExportedTypes){if?(!(type.GetCustomAttribute(typeof(EfEntityAttribute))?is?EfEntityAttribute?attribute)){continue;}if?(type.IsNotPublic?||?type.IsAbstract?||?type.IsSealed||?type.IsGenericType||?type.ContainsGenericParameters){continue;}var?entityBuilder?=?(EntityTypeBuilder)entityMethod.MakeGenericMethod(type).Invoke(modelBuilder,?new?object[]?{?});if?(attribute.EnableCustomTable){entityBuilder.ToTable(attribute.ToString());}}//【2】使用IEntityTypeConfiguration<T>注冊modelBuilder.ApplyConfigurationsFromAssembly(assembly);return?modelBuilder;} }

最后在上下文構造函數中,簡潔調用,如下:

public?class?EfDbContext?:?DbContext {public?string?Date?{?get;?set;?}?=?CustomTableFormat.MINUTE.FormatToDate();public?EfDbContext(DbContextOptions<EfDbContext>?options)?:?base(options){//提供不同數據庫默認約定ConventionSet?conventionSet?=?null;if?(Database.ProviderName?==?"Microsoft.EntityFrameworkCore.SqlServer"){conventionSet?=?SqlServerConventionSetBuilder.Build();}else?if?(Database.ProviderName?==?"Microsoft.EntityFrameworkCore.Sqllite"){conventionSet?=?SqliteConventionSetBuilder.Build();}else?if?(Database.ProviderName?==?"Microsoft.EntityFrameworkCore.MySql"){conventionSet?=?MySqlConventionSetBuilder.Build();}var?modelBuilder?=?new?ModelBuilder(conventionSet);var?optionBuilder?=?new?DbContextOptionsBuilder(options);//使用模型optionBuilder.UseModel(modelBuilder.Model);//處置模型modelBuilder.FinalizeModel();}protected?override?void?OnModelCreating(ModelBuilder?modelBuilder){//構建模型modelBuilder.BuildModel();base.OnModelCreating(modelBuilder);} }

動態映射模型表生成

看到這里,細心的你不知道有沒有發現,我寫的打印結果怎么成功了,居然沒拋出任何異常,實際情況是必須會拋出異常,因為我們只做到了模型動態映射,但表自動生成我在此之前將其忽略了,如下:

表如何生成這個也看實際情況分析,比如SQL Server寫個作業每天自動生成表等,若需兼容多個數據庫,怕是有點麻煩

我沒花太多時間去看源碼,稍微看了下,碰碰運氣或許能直接找到根據模型來創建表的接口實現,結果好像沒有,即使有也比較麻煩,那么我們就手動構建SQL語句或者通過lambda構建也可

上下文中實現其特性需動態生成的模型我們可以獲取得到,然后搞個定時器每分鐘去執行生成對應表,針對不同數據庫類型,我們可以通過如下屬性獲取得到(和包同名)

//?比如SQL Server:Microsoft.EntityFrameworkCore.SqlServer context.Database.ProviderName

這里我以SQL Server數據庫為例,其他數據庫比如MySqL、Sqlite唯一區別則是自增長設置和列類型不同而已,創建表,通過五部分組成:表是否存在,表名,主鍵,所有列,約束。我們定義如下:

internal?sealed?class?CustomTableModel {public?CustomEntityType?CustomEntityType?{?get;?set;?}public?string?TableName?{?get;?set;?}?=?string.Empty;public?string?CheckTable?{?get;?set;?}?=?string.Empty;public?string?PrimaryKey?{?get;?set;?}?=?string.Empty;public?string?Columns?{?get;?set;?}?=?string.Empty;public?string?Constraint?{?get;?set;?}?=?string.Empty;public?override?string?ToString(){var?placeHolder?=?$"{CheckTable}?create?table?{TableName}?({PrimaryKey}?{Columns}";placeHolder?=?string.IsNullOrEmpty(Constraint)???$"{placeHolder.TrimEnd(',')})"?:?$"{placeHolder}{Constraint})";return?placeHolder.Replace("@placeholder_table_name",?CustomEntityType.ToString());} }

由于每次生成只有表名不同,所以我們將整個表數據結構進行緩存,在其內部將表名進行替換就好。整個實現邏輯如下:

public?static?void?Execute() {using?var?scope?=?Program.ServiceProvider.CreateScope();var?context?=?scope.ServiceProvider.GetService<EfDbContext>();context.Database.EnsureCreated();var?cache?=?scope.ServiceProvider.GetService<IMemoryCache>();var?cacheKey?=?context.GetType().FullName;if?(!cache.TryGetValue(cacheKey,?out?List<CustomTableModel>?models)){lock?(_syncObject){if?(!cache.TryGetValue(cacheKey,?out?models)){models?=?CreateModels(context);models?=?cache.Set(cacheKey,?models,?new?MemoryCacheEntryOptions?{?Size?=?100,?Priority?=?CacheItemPriority.High?});}}}Create(context,?models); }private?static?void?Create(EfDbContext?context,?List<CustomTableModel>?models) {foreach?(var?m?in?models){context.Execute(m.ToString());} }internal?static?void?CreateEntityTypes(CustomEntityType?customEntityType) {EntityTypes.Add(customEntityType); }

上述標紅部分很重要,為什么呢?

讓其先執行OnModelCreating方法,也就是說我們必須保證所有模型已經構建完畢,我們才能在上下文中拿到所有模型元數據

接下來則是在OnModeCreating方法中,在啟動自動映射模型的基礎上,添加如下代碼(當然也需檢查表名是否存在重復):

??if?(attribute.EnableCustomTable){entityBuilder.ToTable(attribute.ToString());var?customType?=?new?CustomEntityType(){ClrType?=?type,Prefix?=?attribute.Prefix,Format?=?attribute.Format};var?existTable?=?CreateCustomTable.EntityTypes.FirstOrDefault(c?=>?c.ToString()?==?customType.ToString());if?(existTable?!=?null){throw?new?ArgumentNullException($"Cannot?use?table?'{customType}'?for?entity?type?'{type.Name}'?since?it?is?being?used?for?entity?type?'{existTable.ClrType.Name}'?");}CreateCustomTable.CreateEntityTypes(customType);}

相信構建SQL語句這塊都不在話下,就不再給出了,若有需要的童鞋,可私信我,人比較多的話,我會將兼容不同數據庫的SQL語句構建都會放到github上去以供參考,控制臺入口方法調用如下:

private?const?int?TIME_INTERVAL_IN_MILLISECONDS?=?60000; private?static?Timer?_timer?{?get;?set;?} public?static?IServiceProvider?ServiceProvider?{?get;?set;?} static?void?Main(string[]?args) {ServiceProvider?=?Initialize();//初始化時檢查一次CreateCustomTable.Execute();//定時檢查_timer?=?new?Timer(TimerCallback,?null,?TIME_INTERVAL_IN_MILLISECONDS,?Timeout.Infinite);using?(var?scope1?=?ServiceProvider.CreateScope()){var?context1?=?scope1.ServiceProvider.GetService<EfDbContext>();context1.Database.EnsureCreated();var?type?=?context1.Model.FindEntityType(typeof(Test1));Console.WriteLine(type?.GetTableName());var?tests?=?context1.Set<Test1>().ToList();}Thread.Sleep(60000);using?(var?scope2?=?ServiceProvider.CreateScope()){var?context2?=?scope2.ServiceProvider.GetService<EfDbContext>();context2.Database.EnsureCreated();var?type?=?context2.Model.FindEntityType(typeof(Test2));Console.WriteLine(type?.GetTableName());var?tests1?=?context2.Set<Test2>().ToList();}Console.ReadKey();}

接下來則是通過定義上述定時器,回調調用上述Execute方法,如下:

??static?void?TimerCallback(object?state){var?watch?=?new?Stopwatch();watch.Start();CreateCustomTable.Execute();_timer.Change(Math.Max(0,?TIME_INTERVAL_IN_MILLISECONDS?-?watch.ElapsedMilliseconds),?Timeout.Infinite);}

最后我們來兩個模型測試下實際效果

[EfEntity(EnableCustomTable?=?true,?Prefix?=?"test1",?Format?=?CustomTableFormat.MINUTE)] public?class?Test1 {public?int?Id?{?get;?set;?}public?int?UserId?{?get;?set;?}public?string?Name?{?get;?set;?} }public?class?Test1EntityTypeConfiguration?:?IEntityTypeConfiguration<Test1> {public?void?Configure(EntityTypeBuilder<Test1>?builder){builder.HasKey(k?=>?new?{?k.Id,?k.UserId?});} }[EfEntity(EnableCustomTable?=?true,?Prefix?=?"test2",?Format?=?CustomTableFormat.MINUTE)] public?class?Test2 {public?int?Id?{?get;?set;?}public?int?UserId?{?get;?set;?}public?string?Name?{?get;?set;?} }public?class?Test2EntityTypeConfiguration?:?IEntityTypeConfiguration<Test2> {public?void?Configure(EntityTypeBuilder<Test2>?builder){builder.HasKey(k?=>?new?{?k.Id,?k.UserId?});} }

最后的最后,老規矩,實現動態映射模型有如上兩種方式,通過手動構建SQL語句并緩存,總結如下!

??????使用IModelCacheKeyFactory

?????手動構建模型、處置模型

???????兼容不同數據庫,手動構建SQL語句并緩存

總結

以上是生活随笔為你收集整理的EntityFramework Core如何映射动态模型?的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。