Я пытаюсь написать соглашение для массовой настройки модели. Он должен добавить первичный ключ к базовому классу типа сущности вместе с некоторыми другими менее важными вещами. Нам это нужно, поскольку мы обнаружили, что ключи, добавленные EF Core, не используют правильный тип CLR (
вместо uint), а также потому, что у нас есть общие типы сущностей и мы не хотим настраивать каждый конкретный тип как сущность отдельно.
Но мы не получаем далеко. EF Core сообщает нам следующее:
Невозможно создать «DbContext» типа «Контекст». Исключение «Тип объекта «DirectoryReference» требует определения первичного ключа. Если вы намеревались использовать тип объекта без ключа, вызовите HasNoKey в OnModelCreating. Дополнительные сведения о типах объектов без ключа см. на странице https://go.microsoft.com/fwlink/?linkid=2141943.' был выброшен при попытке создать экземпляр. Информацию о различных шаблонах, поддерживаемых во время разработки, см. на странице https://go.microsoft.com/fwlink/?linkid=851728
[ProcessEntityTypeAdded] Entity `DirectoryReference`, True, False, ``.
Set a new base type (transitive): `DirectoryReference` => `DirectoryReference`.
Base type: DirectoryReference, `False`, `False`.
PKs: ``, ``.
[ProcessEntityTypeAdded] Entity `DirectoryReference`, True, False, ``.
Base type: DirectoryReference, `False`, `False`.
Added an ID property: `Microsoft.EntityFrameworkCore.Metadata.Internal.InternalPropertyBuilder`.
Added a primary key: `PK_directory_reference_directory_entry_foo`.
Configured as a base type.
PKs: `Id`, `Id`.
[ProcessEntityTypeBaseTypeChanged] Entity `DirectoryReference`, True, False, BT: `DirectoryReference`, OT: ``, NT: `DirectoryReference`, OBT: ``, NBT: ``.
Base type: DirectoryReference, `False`, `False`.
Added an ID property: `Microsoft.EntityFrameworkCore.Metadata.Internal.InternalPropertyBuilder`.
Added a primary key: `PK_directory_reference_directory_entry_foo`.
[ProcessEntityTypeBaseTypeChanged] Entity `DirectoryReference`, True, False, BT: ``, OT: `DirectoryReference`, NT: ``, OBT: ``, NBT: ``.
Очевидно, что-то в EF Core переопределило нашу конфигурацию и сбросило базовый тип объекта на нет. Мы попытались переместить код изменения базового типа в соглашение о завершении модели, но к тому времени FK уже установлены и больше не позволяют изменять базовый тип, и не похоже, что их будет легко перенацелить на базовый тип. .
Как мы можем предотвратить такое переопределение нашего соглашения EF Core?
Я пытаюсь написать соглашение для массовой настройки модели. Он должен добавить первичный ключ к базовому классу типа сущности вместе с некоторыми другими менее важными вещами. Нам это нужно, поскольку мы обнаружили, что ключи, добавленные EF Core, не используют правильный тип CLR ([code]long[/code] вместо uint), а также потому, что у нас есть общие типы сущностей и мы не хотим настраивать каждый конкретный тип как сущность отдельно. Но мы не получаем далеко. EF Core сообщает нам следующее:
Невозможно создать «DbContext» типа «Контекст». Исключение «Тип объекта «DirectoryReference» требует определения первичного ключа. Если вы намеревались использовать тип объекта без ключа, вызовите HasNoKey в OnModelCreating. Дополнительные сведения о типах объектов без ключа см. на странице https://go.microsoft.com/fwlink/?linkid=2141943.' был выброшен при попытке создать экземпляр. Информацию о различных шаблонах, поддерживаемых во время разработки, см. на странице https://go.microsoft.com/fwlink/?linkid=851728
Но мы их настроили. в соглашении: [code]public class IdShadowPropertyConvention: IEntityTypeBaseTypeChangedConvention, IEntityTypeAddedConvention { static IdShadowPropertyConvention() { File.Delete("efcore.log"); } public void ProcessEntityTypeAdded(IConventionEntityTypeBuilder builder, IConventionContext context) { File.AppendAllText("efcore.log", $"[ProcessEntityTypeAdded] Entity `{builder.Metadata.Name}`, {builder.Metadata.IsInModel}, {builder.Metadata.HasSharedClrType}, `{builder.Metadata.BaseType?.Name}`.\n"); using var t = context.DelayConventions(); if(builder.Metadata is { IsInModel: true, HasSharedClrType: false, BaseType: null } && !builder.Metadata.IsOwned()) { var type = builder.Metadata.ClrType; if(type.BaseType != typeof(object)) { var entity = builder; do { type = type.BaseType!; var baseEntity = builder.ModelBuilder.Metadata.FindEntityType(type)?.Builder ?? builder.ModelBuilder.Entity(type)!; if(!entity.Metadata.HasSharedClrType && !entity.Metadata.IsOwned()) { File.AppendAllText("efcore.log", $"\tSet a new base type (transitive): `{entity.Metadata.Name}` => `{baseEntity.Metadata.Name}`.\n"); _ = entity.HasBaseType(baseEntity.Metadata); } entity = baseEntity; } while(type.BaseType != typeof(object)); } IConventionKeyBuilder? pk1 = null; { var baseEntity = builder.ModelBuilder.Entity(type); File.AppendAllText("efcore.log", $"\tBase type: {type.ShortDisplayName()}, `{baseEntity?.Metadata.HasSharedClrType}`, `{baseEntity?.Metadata.IsKeyless}`.\n"); if(baseEntity == builder) { if(baseEntity.Metadata is { HasSharedClrType: false } meta) { if(!baseEntity.Metadata.IsKeyless) { var prop = baseEntity.Property(typeof(uint), "Id"); File.AppendAllText("efcore.log", $"\tAdded an ID property: `{prop}`.\n"); var pk = prop is null ? baseEntity.PrimaryKey(["Id"]) : baseEntity.PrimaryKey([prop.Metadata]); pk1 = pk; File.AppendAllText("efcore.log", $"\tAdded a primary key: `{pk?.Metadata.GetName()}`.\n"); } _ = baseEntity.UseMappingStrategy(RelationalAnnotationNames.TphMappingStrategy)?.HasDiscriminator(); File.AppendAllText("efcore.log", $"\tConfigured as a base type.\n"); } } } _ = builder.Property(typeof(uint), "RowVersion")?.ValueGenerated(ValueGenerated.OnAddOrUpdate)?.IsConcurrencyToken(true); var pk2 = builder.Metadata.FindDeclaredPrimaryKey(); File.AppendAllText("efcore.log", $"\tPKs: `{pk1?.Metadata.Properties.Select((p) => p.Name).Join(", ")}`, `{pk2?.Properties.Select((p) => p.Name).Join(", ")}`.\n"); } }
if(newBaseType?.IsInModel is true) { var type = newBaseType; while(type.BaseType is not null) { type = type.BaseType; } File.AppendAllText("efcore.log", $"\tBase type: {type.Name}, `{type.HasSharedClrType}`, `{type.IsKeyless}`.\n"); if(type.BaseType is null) { if(!type.IsKeyless) { var prop = type.Builder.Property(typeof(uint), "Id"); File.AppendAllText("efcore.log", $"\tAdded an ID property: `{prop}`.\n"); var pk = prop is null ? type.Builder.PrimaryKey(["Id"]) : type.Builder.PrimaryKey([prop.Metadata]); File.AppendAllText("efcore.log", $"\tAdded a primary key: `{pk?.Metadata.GetName()}`.\n"); } _ = type.Builder.Property(typeof(uint), "RowVersion")?.ValueGenerated(ValueGenerated.OnAddOrUpdate)?.IsConcurrencyToken(true); _ = type.Builder.UseMappingStrategy(RelationalAnnotationNames.TphMappingStrategy)?.HasDiscriminator(); } } } } [/code] Изучая полученный журнал, мы видим следующие важные части: [code][ProcessEntityTypeAdded] Entity `DirectoryReference`, True, False, ``. Set a new base type (transitive): `DirectoryReference` => `DirectoryReference`. Base type: DirectoryReference, `False`, `False`. PKs: ``, ``. [ProcessEntityTypeAdded] Entity `DirectoryReference`, True, False, ``. Base type: DirectoryReference, `False`, `False`. Added an ID property: `Microsoft.EntityFrameworkCore.Metadata.Internal.InternalPropertyBuilder`. Added a primary key: `PK_directory_reference_directory_entry_foo`. Configured as a base type. PKs: `Id`, `Id`. [ProcessEntityTypeBaseTypeChanged] Entity `DirectoryReference`, True, False, BT: `DirectoryReference`, OT: ``, NT: `DirectoryReference`, OBT: ``, NBT: ``. Base type: DirectoryReference, `False`, `False`. Added an ID property: `Microsoft.EntityFrameworkCore.Metadata.Internal.InternalPropertyBuilder`. Added a primary key: `PK_directory_reference_directory_entry_foo`. [ProcessEntityTypeBaseTypeChanged] Entity `DirectoryReference`, True, False, BT: ``, OT: `DirectoryReference`, NT: ``, OBT: ``, NBT: ``. [/code] Очевидно, что-то в EF Core переопределило нашу конфигурацию и сбросило базовый тип объекта на нет. Мы попытались переместить код изменения базового типа в соглашение о завершении модели, но к тому времени FK уже установлены и больше не позволяют изменять базовый тип, и не похоже, что их будет легко перенацелить на базовый тип. . Как мы можем предотвратить такое переопределение нашего соглашения EF Core?