Мне нужно провести рефакторинг сущности и перенести некоторые ее свойства в другую сущность с соотношением 1: 1. Однако, когда я создаю новый класс и перемещаю в него в основном свойства типа Instant, создавая Entity1Id
в качестве его ключа плюс свойство виртуальной навигации между ними, когда я хочу создать миграцию, я получаю следующую ошибку:
System.NotSupportedException: в сопоставлении типов для 'Instant' не реализована генерация литералов кода.
Что происходит? Я не делаю посева (обнаружил эту ошибку: https://github.com/npgsql/Npgsql.EntityFrameworkCore.PostgreSQL/issues/526)
Я создал простой репозиторий, демонстрирующий такое поведение, где вы можете протестировать его с .Net Core 2.2. Я в настоящее время на Mac; Я не уверен, оказывает ли это какое-либо влияние.
GitHub репо: https://github.com/Slaviusz/EFCoreSplittingEntityProblem
Edit:
По запросу, что на самом деле является содержимым репозитория Github, приведен пример кода. Обратите внимание, что я столкнулся с этой проблемой в решении с десятками сущностей, но мне удалось урезать его до простого проекта рефакторинга сущностей.
Начиная с простой сущности:
public class Table1
{
public int Id { get; set; }
public string Name { get; set; }
public Instant Starts { get; set; }
public Instant Ends { get; set; }
}
public class ApplicationDbContext : DbContext
{
... // other code like constructors and configure method overrides
public DbSet<Table1> Table1s { get; set; }
}
Создание миграции завершается успешно, за которым следует dotnet ef database update
. (однако, чтобы вызвать это, это вообще не обязательно)
Более подробную информацию можно увидеть в первом коммите: https://github.com/Slaviusz/EFCoreSplittingEntityProblem/commit/57562f0c978287e15d75ff1bead435501c28befc
Следующий шаг - выполнить рефакторинг, извлекая два свойства Instant во вторичный класс, создавая логическую связь между ними.
Типичный вариант использования заключается в извлечении сведений об объекте в его собственную таблицу, требующую объединения / выборки, только когда необходимы сведения.
В этом случае реквизиты Instant Starts
и Instant Ends
перемещаются в Table2
.
public class Table1
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Table2 Table2 { get; set; }
}
public class Table2
{
[Key] public int Table1Id { get; set; }
public Instant Starts { get; set; }
public Instant Ends { get; set; }
public virtual Table1 Table1 { get; set; }
}
public class ApplicationDbContext : DbContext
{
... // other code like constructors and configure method overrides
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Table2>()
.HasOne(p => p.Table1)
.WithOne(p => p.Table2);
}
public DbSet<Table1> Table1s { get; set; }
public DbSet<Table2> Table2s { get; set; }
}
Все можно увидеть в третьем коммите: https://github.com/Slaviusz/EFCoreSplittingEntityProblem/commit/04fe59563bd510df26a37e5938889557b9741673
Примечание. Во втором коммите я добавил .gitignore, чтобы исключить файлы, ненужные для воспроизведения.
На этом этапе выполнение dotnet ef migrations add Split
приводит к:
$ dotnet ef migrations add Split
An operation was scaffolded that may result in the loss of data. Please review the migration for accuracy.
System.NotSupportedException: The type mapping for 'Instant' has not implemented code literal generation.
at Microsoft.EntityFrameworkCore.Storage.CoreTypeMapping.GenerateCodeLiteral(Object value)
at Microsoft.EntityFrameworkCore.Design.Internal.CSharpHelper.UnknownLiteral(Object value)
at Microsoft.EntityFrameworkCore.Migrations.Design.CSharpMigrationOperationGenerator.Generate(AddColumnOperation operation, IndentedStringBuilder builder)
at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid3[T0,T1,T2](CallSite site, T0 arg0, T1 arg1, T2 arg2)
at Microsoft.EntityFrameworkCore.Migrations.Design.CSharpMigrationOperationGenerator.Generate(String builderName, IReadOnlyList`1 operations, IndentedStringBuilder builder)
at Microsoft.EntityFrameworkCore.Migrations.Design.CSharpMigrationsGenerator.GenerateMigration(String migrationNamespace, String migrationName, IReadOnlyList`1 upOperations, IReadOnlyList`1 downOperations)
at Microsoft.EntityFrameworkCore.Migrations.Design.MigrationsScaffolder.ScaffoldMigration(String migrationName, String rootNamespace, String subNamespace, String language)
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)
The type mapping for 'Instant' has not implemented code literal generation.
Важно отметить, что для возможности тестирования с провайдером EF Core In-Memory я использую 2 конструктора:
// constructor for mocking with InMemory provider
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// return if already configured (by mocking with InMemory provider)
if (optionsBuilder.IsConfigured)
return;
... // the rest of the code to init
}
Однако это приводит к тому, что dotnet
инструменты не могут выполнять операции Cli EF Core (миграции, обновления баз). Таким образом, у меня есть дополнительный класс, который расширяет IDesignTimeDbContextFactory<>
.
public class CliDbContext : IDesignTimeDbContextFactory<ApplicationDbContext>
{
public ApplicationDbContext CreateDbContext(string[] args)
{
... // code to init in cli cases
}
}