Add-Migration с семенами, выбрасывающими исключение «значение не указано» - PullRequest
0 голосов
/ 20 декабря 2018

Я пытаюсь создать пакет, который поможет заполнить базы данных данными из файлов JSON, которые могут быть созданы вручную или извлечены из устаревшей базы данных.В настоящее время я создал универсальный класс SeedCreator<T>, который извлекает JSON из заданного файла (имя сущности плюс .json) и десериализует его в объект данного типа.Эта часть работает нормально.

Чтобы сделать этот процесс максимально динамичным, я использую рефлексию сущностей в проекте, идентифицируя все классы с пространством имен Entities.После этого я перебираю полученный List и проверяю, существует ли файл JSON.Если это так, я передаю путь и универсальный тип классу 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)

1 Ответ

0 голосов
/ 20 декабря 2018

Вы попадаете в типичную params object[] ловушку.

Метод HasData имеет 2 перегрузки - одну с params object[] data и одну с IEnumerable<object> data.Из-за вызова отражения тип вашей переменной data равен object.Следовательно, вы вызываете первую перегрузку с single item object[], содержащим ваш data.

Если вам интересно, почему в сообщении об исключении говорится, что для свойства не задано значениеОбъяснение простое.HasData не требует, чтобы переданные объекты были того же типа, что и просеиваемый объект.Это позволяет указать теневые свойства, которых нет в классе сущностей, но которые необходимы для заполнения данных.Таким образом, он позволяет передавать любой анонимный или конкретный тип, который содержит все свойства объекта.

Следовательно, он пытается отразить фактический тип переданных объектов и найти свойство Id.Поскольку фактический тип переданного одиночного объекта в вашем случае - List<TEntity>, он, конечно, не имеет свойства Id, следовательно, это сообщение об исключении.

При всем сказанном, исправление, конечно,вызовите правильную HasData перегрузку (с IEnumerable<object> data):

modelBuilder.Entity(type).HasData((IEnumerable<object>)data);
...