如何从字典动态调用不同类型集合?EF Core实践疑难
需要实现的功能:通过字典存储DbSet(作为键,对应数据库实体)和其他集合(作为值,为IEnumerable或List类型的模型)。遍历字典时,先判断DbSet是否包含数据,若为空则将模型集合中的数据转换为实体后添加到DbSet,最后通过EntityFrameworkCore保存更改。但运行时无法访问这些DbSet或集合,尝试使用dynamic类型的字典后遇到RuntimeBinderException异常。
涉及类型说明
字典的键(DbSet)类型
- configContext!.Clients:实际类型为
Duende.IdentityServer.EntityFramework.Entities.Client - configContext!.IdentityResources:实际类型为
Duende.IdentityServer.EntityFramework.Entities.IdentityResource - configContext!.ApiScopes:实际类型为
Duende.IdentityServer.EntityFramework.Entities.ApiScope - configContext!.ApiResources:实际类型为
Duende.IdentityServer.EntityFramework.Entities.ApiResource
字典的值(模型集合)类型
- Config.Clients:实际类型为
Duende.IdentityServer.Models.Client - Config.IdentityResources:实际类型为
Duende.IdentityServer.Models.IdentityResource - Config.ApiScopes:实际类型为
Duende.IdentityServer.Models.ApiScope - Config.ApiResources:实际类型为
Duende.IdentityServer.Models.ApiResource
注:键(数据库实体)与值(模型)为一对一映射关系,可互相转换。
初始代码
引用部分
using Duende.IdentityServer.EntityFramework.DbContexts; using Duende.IdentityServer.EntityFramework.Mappers; using Duende.IdentityServer.Models; using Microsoft.EntityFrameworkCore; using DbClient = Duende.IdentityServer.EntityFramework.Entities.Client; using DbIdentityResource = Duende.IdentityServer.EntityFramework.Entities.IdentityResource; using DbApiScope = Duende.IdentityServer.EntityFramework.Entities.ApiScope; using DbApiResource = Duende.IdentityServer.EntityFramework.Entities.ApiResource;
测试方法
private static void Test(this ConfigurationDbContext configContext) { Dictionary<dynamic, dynamic> configCollections = new Dictionary<dynamic, dynamic>() { { configContext!.Clients, Config.Clients }, { configContext!.IdentityResources, Config.IdentityResources }, { configContext!.ApiScopes, Config.ApiScopes }, { configContext!.ApiResources, Config.ApiResources }, }; foreach (var configCollection in configCollections) { var collection = configCollection!.Key.Any(); if (collection == false) { foreach (var configValue in configCollection.Value) { configCollection!.Key.Add(configValue.ToEntity()); } configContext.SaveChanges(); } } }
调用方式
configContext.Test();
初始报错
Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: ''Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable<Duende.IdentityServer.EntityFramework.Entities.Client>' does not contain a definition for 'Any''
尝试的解决方法(均未成功)
- 泛型方法改造:将测试方法改为泛型方法,对集合进行Cast转换,调用时指定类型,但问题未解决。
- 强制转换为DbSet
:将 var collection = configCollection!.Key.Any();改为((DbSet<dynamic>)configCollection!.Key).Any();,报错无法转换类型。 - 强制转换后再Cast:改为
((DbSet<dynamic>) configCollection!.Key).Cast<dynamic>().Any();,仍报错无法转换类型。
新报错
Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'Cannot convert type 'Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable<Duende.IdentityServer.EntityFramework.Entities.Client>' to 'Microsoft.EntityFrameworkCore.DbSet''
最终解决方案
注:
- Duende.IdentityServer.EntityFramework.Mappers下的不同类型映射器位于更深层级
- 需在连接字符串中添加
MultipleActiveResultSets=true;
public static void TestExec() { List<(IQueryable dbSet, IEnumerable<object> models)> configCollections = new() { (configContext!.Clients, Config.Clients), (configContext!.IdentityResources, Config.IdentityResources), (configContext!.ApiScopes, Config.ApiScopes), (configContext!.ApiResources, Config.ApiResources), }; foreach (var (dbSet, models) in configCollections) { if (dbSet.AsQueryable().GetEnumerator().MoveNext() == false) { foreach (object configValue in models) { dynamic? test = null; switch (configValue.GetType().Name) { case nameof(Client): test = ClientMappers.ToEntity((dynamic)configValue); break; case nameof(IdentityResource): case nameof(OpenId): case nameof(Profile): case nameof(Email): case nameof(Phone): case nameof(Address): test = IdentityResourceMappers.ToEntity((dynamic)configValue); break; case nameof(ApiScope): test = ScopeMappers.ToEntity((dynamic)configValue); break; case nameof(ApiResource): test = ApiResourceMappers.ToEntity((dynamic)configValue); break; default: break; } ((dynamic)dbSet).Add(test!); } configContext.SaveChanges(); } } }
内容的提问来源于stack exchange,提问作者user20291437

