带有种子的添加迁移抛出 "no value provided" 异常
Add-Migration with seeds throwing "no value provided" exception
我正在尝试创建一个包,该包将帮助使用 JSON 文件中的数据为数据库提供种子,这些文件可以手动创建或从遗留数据库中提取。目前我已经创建了一个通用的 class SeedCreator<T>
,它从给定文件(实体名称加上 .json
)中检索 JSON 并将其反序列化为给定类型的对象.这部分工作正常。
为了使这个过程尽可能动态化,我通过识别所有 class 名称空间 Entities
对项目中的实体使用反射。有了这个,我循环遍历检索到的 List
并检查 JSON 文件是否存在。如果是,我将路径和通用类型传递给 class SeedsCreator
。在调试 运行 Add-Migration
时,数据如我所期望的那样从 JSON 文件返回,但是在变量 modelBuilder
返回后我得到错误 The seed entity for entity type 'Table1' cannot be added because there was no value provided for the required property 'Id'.
如果我手动输入以下内容,它就可以正常工作。
modelBuilder.Entity(typeof(Table1)).HasData(data);
如有任何帮助,我们将不胜感激。特别是如果我是盲人并且做了一些非常简单和愚蠢的事情。
public class Seeds
{
public ModelBuilder CreateSeeds(ModelBuilder modelBuilder)
{
var entities = (from t in Assembly.GetExecutingAssembly().GetTypes()
where t.Namespace != null && (t.IsClass && t.Namespace.Contains("Entities"))
select t).ToList();
foreach (var type in entities)
{
if (File.Exists("./Seeds/" + type.Name + ".json"))
{
Type[] typeArr = { type };
var seeds = typeof(SeedCreator<>).MakeGenericType(typeArr);
var activatedSeeds = Activator.CreateInstance(seeds);
var data = seeds.GetMethod("GetSeeds")?.Invoke(activatedSeeds, new object[] { "./Seeds/" + type.Name + ".json" });
modelBuilder.Entity(type).HasData(data);
}
}
return modelBuilder;
}
}
public class SeedCreator<T>
{
public List<T> GetSeeds(string jsonPath)
{
using (var sr = new StreamReader(jsonPath))
return JsonConvert.DeserializeObject<List<T>>(sr.ReadToEnd());
}
}
public class Table1
{
public int Id { get; set; }
}
在 DbContext 内部
using (var dataSeed = new Seeds()) modelBuilder = dataSeed.CreateSeeds(modelBuilder);
示例 JSON 文件 (Table1.json
)
[
{
"id": 1
},
{
"id": 2
}
]
堆栈跟踪
System.InvalidOperationException: The seed entity for entity type 'Table1' cannot be added because there was no value provided for the required property 'Id'.
at Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.ValidateData(IModel model)
at Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.Validate(IModel model)
at Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidator.Validate(IModel model)
at Microsoft.EntityFrameworkCore.Internal.SqlServerModelValidator.Validate(IModel model)
at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ValidatingConvention.Apply(InternalModelBuilder modelBuilder)
at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.ImmediateConventionScope.OnModelBuilt(InternalModelBuilder modelBuilder)
at Microsoft.EntityFrameworkCore.ModelBuilder.FinalizeModel()
at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
at System.Lazy`1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor)
at System.Lazy`1.CreateValue()
at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel()
at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model()
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProviderEngineScope scope)
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_InternalServiceProvider()
at Microsoft.EntityFrameworkCore.Internal.InternalAccessorExtensions.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)
at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigrationImpl(String name, String outputDir, String contextType)
at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.<>c__DisplayClass3_0`1.<Execute>b__0()
at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action)
您正在陷入典型的 params object[]
陷阱。
HasData
方法有 2 个重载 - 一个带有 params object[] data
,一个带有 IEnumerable<object> data
。由于反射调用,data
变量的类型是 object
。因此,您使用 单个 项目 object[]
调用包含您的 data
.
的第一个重载
如果您想知道为什么异常消息告诉您没有为 属性 提供任何值,解释很简单。 HasData
不要求传递的对象与被播种的实体具有相同的类型。这是为了允许指定实体 class 中不存在但数据播种所需的影子属性。因此它允许您传递包含所有实体属性的任何匿名或具体类型。
因此它试图反映传递对象的实际类型并找到 属性 Id
。由于在您的情况下传递的单个对象的实际类型是 List<TEntity>
,当然它没有 Id
属性,因此出现异常消息。
综上所述,解决方法当然是调用正确的 HasData
重载(使用 IEnumerable<object> data
):
modelBuilder.Entity(type).HasData((IEnumerable<object>)data);
我正在尝试创建一个包,该包将帮助使用 JSON 文件中的数据为数据库提供种子,这些文件可以手动创建或从遗留数据库中提取。目前我已经创建了一个通用的 class SeedCreator<T>
,它从给定文件(实体名称加上 .json
)中检索 JSON 并将其反序列化为给定类型的对象.这部分工作正常。
为了使这个过程尽可能动态化,我通过识别所有 class 名称空间 Entities
对项目中的实体使用反射。有了这个,我循环遍历检索到的 List
并检查 JSON 文件是否存在。如果是,我将路径和通用类型传递给 class SeedsCreator
。在调试 运行 Add-Migration
时,数据如我所期望的那样从 JSON 文件返回,但是在变量 modelBuilder
返回后我得到错误 The seed entity for entity type 'Table1' cannot be added because there was no value provided for the required property 'Id'.
如果我手动输入以下内容,它就可以正常工作。
modelBuilder.Entity(typeof(Table1)).HasData(data);
如有任何帮助,我们将不胜感激。特别是如果我是盲人并且做了一些非常简单和愚蠢的事情。
public class Seeds
{
public ModelBuilder CreateSeeds(ModelBuilder modelBuilder)
{
var entities = (from t in Assembly.GetExecutingAssembly().GetTypes()
where t.Namespace != null && (t.IsClass && t.Namespace.Contains("Entities"))
select t).ToList();
foreach (var type in entities)
{
if (File.Exists("./Seeds/" + type.Name + ".json"))
{
Type[] typeArr = { type };
var seeds = typeof(SeedCreator<>).MakeGenericType(typeArr);
var activatedSeeds = Activator.CreateInstance(seeds);
var data = seeds.GetMethod("GetSeeds")?.Invoke(activatedSeeds, new object[] { "./Seeds/" + type.Name + ".json" });
modelBuilder.Entity(type).HasData(data);
}
}
return modelBuilder;
}
}
public class SeedCreator<T>
{
public List<T> GetSeeds(string jsonPath)
{
using (var sr = new StreamReader(jsonPath))
return JsonConvert.DeserializeObject<List<T>>(sr.ReadToEnd());
}
}
public class Table1
{
public int Id { get; set; }
}
在 DbContext 内部
using (var dataSeed = new Seeds()) modelBuilder = dataSeed.CreateSeeds(modelBuilder);
示例 JSON 文件 (Table1.json
)
[
{
"id": 1
},
{
"id": 2
}
]
堆栈跟踪
System.InvalidOperationException: The seed entity for entity type 'Table1' cannot be added because there was no value provided for the required property 'Id'.
at Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.ValidateData(IModel model)
at Microsoft.EntityFrameworkCore.Infrastructure.ModelValidator.Validate(IModel model)
at Microsoft.EntityFrameworkCore.Infrastructure.RelationalModelValidator.Validate(IModel model)
at Microsoft.EntityFrameworkCore.Internal.SqlServerModelValidator.Validate(IModel model)
at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ValidatingConvention.Apply(InternalModelBuilder modelBuilder)
at Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal.ConventionDispatcher.ImmediateConventionScope.OnModelBuilt(InternalModelBuilder modelBuilder)
at Microsoft.EntityFrameworkCore.ModelBuilder.FinalizeModel()
at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
at System.Lazy`1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor)
at System.Lazy`1.CreateValue()
at Microsoft.EntityFrameworkCore.Internal.DbContextServices.CreateModel()
at Microsoft.EntityFrameworkCore.Internal.DbContextServices.get_Model()
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScoped(ScopedCallSite scopedCallSite, ServiceProviderEngineScope scope)
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_InternalServiceProvider()
at Microsoft.EntityFrameworkCore.Internal.InternalAccessorExtensions.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)
at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigrationImpl(String name, String outputDir, String contextType)
at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.<>c__DisplayClass3_0`1.<Execute>b__0()
at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action)
您正在陷入典型的 params object[]
陷阱。
HasData
方法有 2 个重载 - 一个带有 params object[] data
,一个带有 IEnumerable<object> data
。由于反射调用,data
变量的类型是 object
。因此,您使用 单个 项目 object[]
调用包含您的 data
.
如果您想知道为什么异常消息告诉您没有为 属性 提供任何值,解释很简单。 HasData
不要求传递的对象与被播种的实体具有相同的类型。这是为了允许指定实体 class 中不存在但数据播种所需的影子属性。因此它允许您传递包含所有实体属性的任何匿名或具体类型。
因此它试图反映传递对象的实际类型并找到 属性 Id
。由于在您的情况下传递的单个对象的实际类型是 List<TEntity>
,当然它没有 Id
属性,因此出现异常消息。
综上所述,解决方法当然是调用正确的 HasData
重载(使用 IEnumerable<object> data
):
modelBuilder.Entity(type).HasData((IEnumerable<object>)data);