EF Core 7.x+MySQL使用JSON列执行add migration时遇空引用异常
问题
我有一个Student模型类,希望其中一个属性以JSON格式存储在数据库中,使用的技术栈是.NET Core 7.x、EF Core 7.x和MySQL。
Student模型代码:
public class Student : BaseEntity { public string Name { get; set; } public List<StudentData> Data {get; set;} = new (); }
相关的其他对象模型:
public class StudentData { public virtual Day Day { get; set; } public virtual StudentValue Value { get; set; } } public class StudentValue { public string Key { get; set; } public string Value { get; set; } } public class Day { public int DateOfMonth { get; set; } public string DayHE { get; set; } public int DayOfWeek { get; set; } }
OnModelCreating配置:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Student>().OwnsMany( entity => entity.Data, builder => { builder.ToJson(); builder.OwnsOne(x => x.Day); builder.OwnsOne(x => x.Value); }); }
执行迁移命令:
dotnet-ef migrations add AddStudentModel
报错信息:
Object reference not set to an instance of an object
完整错误栈:
Build started... Build succeeded. 2023-07-10 16:40:54.2833|0|INFO|Program|Server is running in "Development" mode, on port 5053 |Program.<Main>$ System.NullReferenceException: Object reference not set to an instance of an object. at Microsoft.EntityFrameworkCore.Metadata.Internal.RelationalModel.CreateTableMapping(IRelationalTypeMappingSource relationalTypeMappingSource, IEntityType entityType, IEntityType mappedType, StoreObjectIdentifier mappedTable, RelationalModel databaseModel, List`1 tableMappings, Boolean includesDerivedTypes, Nullable`1 isSplitEntityTypePrincipal) at Microsoft.EntityFrameworkCore.Metadata.Internal.RelationalModel.AddTables(RelationalModel databaseModel, IEntityType entityType, IRelationalTypeMappingSource relationalTypeMappingSource) at Microsoft.EntityFrameworkCore.Metadata.Internal.RelationalModel.Create(IModel model, IRelationalAnnotationProvider relationalAnnotationProvider, IRelationalTypeMappingSource relationalTypeMappingSource, Boolean designTime) at Microsoft.EntityFrameworkCore.Metadata.Internal.RelationalModel.Add(IModel model, IRelationalAnnotationProvider relationalAnnotationProvider, IRelationalTypeMappingSource relationalTypeMappingSource, Boolean designTime) at Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelRuntimeInitializer.InitializeModel(IModel model, Boolean designTime, Boolean prevalidation) at Microsoft.EntityFrameworkCore.Infrastructure.ModelRuntimeInitializer.Initialize(IModel model, Boolean designTime, IDiagnosticsLogger`1 validationLogger) at Microsoft.EntityFrameworkCore.Infrastructure.ModelSource.GetModel(DbContext context, ModelCreationDependencies modelCreationDependencies, Boolean designTime) at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel(Boolean designTime) at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model() at Microsoft.EntityFrameworkCore.Infrastructure.EntityFrameworkServicesBuilder.<>c.<TryAddCoreServices>b__8_4(IServiceProvider p) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope) at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass2_0.<RealizeService>b__0(ServiceProviderEngineScope scope) at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope) at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType) at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider) at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies() at Microsoft.EntityFrameworkCore.DbContext.get_ContextServices() at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider() at Microsoft.EntityFrameworkCore.DbContext.Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure<System.IServiceProvider>.get_Instance() at Microsoft.EntityFrameworkCore.Infrastructure.Internal.InfrastructureExtensions.GetService[TService](IInfrastructure`1 accessor) at Microsoft.EntityFrameworkCore.Infrastructure.AccessorExtensions.GetService[TService](IInfrastructure`1 accessor) at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContext(Func`1 factory) at Microsoft.EntityFrameworkCore.Design.Internal.DbContextOperations.CreateContext(String contextType) at Microsoft.EntityFrameworkCore.Design.Internal.MigrationsOperations.AddMigration(String name, String outputDir, String contextType, String namespace) at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigrationImpl(String name, String outputDir, String contextType, String namespace) at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigration.<>c__DisplayClass0_0.<.ctor>b__0() at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.<>c__DisplayClass3_0`1.<Execute>b__0() at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action) Object reference not set to an instance of an object.
使用-v参数执行也没有更多有效信息,请问如何解决?
解决方案
这个空引用异常是因为EF Core 7中OwnsMany配合ToJson时嵌套OwnsOne的写法存在兼容性问题,MySQL的EF Core提供程序对这种嵌套JSON映射支持不完善,或是EF Core本身的bug导致。可以通过以下几种方式解决:
方法一:调整映射配置并移除virtual关键字
EF Core的JSON映射不需要使用virtual(该关键字用于延迟加载),先移除StudentData中的virtual关键字,再将OwnsOne配置移到ToJson之前:
- 修改
StudentData模型:
public class StudentData { public Day Day { get; set; } public StudentValue Value { get; set; } }
- 调整
OnModelCreating配置:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Student>().OwnsMany( entity => entity.Data, builder => { builder.OwnsOne(x => x.Day); builder.OwnsOne(x => x.Value); builder.ToJson(); }); }
方法二:使用自定义JSON值转换器
如果上述方法无效,可以放弃OwnsMany+ToJson的组合,改用EF Core的自定义值转换器直接将List<StudentData>序列化为JSON字符串:
- 在
OnModelCreating中配置值转换器:
protected override void OnModelCreating(ModelBuilder modelBuilder) { var jsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = false }; modelBuilder.Entity<Student>() .Property(s => s.Data) .HasConversion( v => JsonSerializer.Serialize(v, jsonOptions), v => JsonSerializer.Deserialize<List<StudentData>>(v, jsonOptions) ) .HasColumnType("json"); // MySQL中使用json类型 }
- 确保
StudentData、Day、StudentValue都是可序列化的简单类(已移除virtual关键字)。
方法三:升级EF Core版本
该问题在EF Core 8中已被修复,如果项目允许,可以升级到EF Core 8.x及对应的MySQL EF Core提供程序,原有的配置写法即可正常工作。
内容的提问来源于stack exchange,提问作者Yaniv Amrami
相关产品推荐
相关产品推荐

