Странное поведение ядра EF при установке идентификатора навигации на null после обновления до .NET 6

#c# #entity-framework #asp.net-core #.net-core #.net-6.0

#c# #entity-framework #asp.net-core #.net-core #.net-6.0

Вопрос:

Я пытаюсь выполнить обновление с .NET 3.1 до .NET 6. Мне удалось собрать все и обновить различные пакеты NuGet в моем решении.

После попытки запуска моих модульных тестов я столкнулся с некоторыми странными проблемами с некоторыми операциями EF. Я использую поставщика базы данных в памяти для этих модульных тестов.

У меня есть настройка, подобная следующей:

Сущности:

 public class EntityA
{
    public Guid Id { get; set; }
    public Guid? EntityBId { get; set; }
    public EntityB EntityB { get; set; }
}

public class EntityB
{
    public Guid Id { get; set; }
    public string Name { get; set; }
}
 

Конфигурации:

 public class EntityAConfiguration: IEntityTypeConfiguration<EntityA>
{
    public void Configure(EntityTypeBuilder<EntityA> builder)
    {
        builder.ToTable("EntitiyAs");

        builder.Property(e => e.Id)
            .HasColumnName("EntitiyAId")
            .IsRequired();
        
        builder.HasKey(e => e.Id)
            .HasName("PK_EntityAs");

        builder.HasIndex(e => e.EntityBId)
            .HasName("IX_EntityAs_EntityBId");

        builder.HasOne(e => e.EntityB)
            .WithMany()
            .HasForeignKey(e => e.EntityBId)
            .HasConstraintName("FK_EntityA_EntityBs_EntityBId");
    }
}

public class EntityBConfiguration: IEntityTypeConfiguration<EntityB>
{
    public void Configure(EntityTypeBuilder<EntityB> builder)
    {
        builder.ToTable("EntityBs");

        builder.Property(e => e.Id)
            .HasColumnName("EntityBId")
            .IsRequired();

        builder.HasKey(e => e.Id)
            .HasName("PK_EntityBs");
    }
}
 

Создание моего тестового DbContext следующим образом:

 private TestDbContext GenerateDbContext()
{
    var options = new DbContextOptionsBuilder<TestDbContext>()
        .UseInMemoryDatabase(Guid.NewGuid().ToString())
        .EnableSensitiveDataLogging()
        .Options;

    return new TestDbContext(options);
}
 

В моих тестах я предварительно заполняю несколько из этих объектов в DbContext перед выполнением метода, который я хочу протестировать. Тестируемый метод выглядит следующим образом:

 public async Task RemoveEntityBFromAssociatedEntityA(Guid entityBId)
{
    var entityB = await _dbContext.EntityBs.SingleOrDefaultAsync(g => g.Id == entityBId);
    if (entityB == null)
    {
        // throw exception...
    }

    var entityA = await _dbContext.EntityAs.SingleOrDefaultAsync(s => s.EntityBId == entityB);
    if (entityA == null)
    {
        // throw exception...
    }
    
    entityA.EntityBId = null;
    _dbContext.entityAs.Update(entityA);
    await _dbContext.SaveChangesAsync();
}
 

Что я здесь делаю, так это удаляю ассоциацию с EntityB on EntityA , устанавливая идентификатор навигации на null. Как ни странно, эффект, который это имеет, заключается в том, что EntityA он помечается для удаления, когда для него EntityBId установлено значение null, и когда SaveChanges вызывается, он фактически удаляет EntityA .

Для меня это не имеет большого смысла. Почему EntityA в этом сценарии он будет удален? Я просмотрел критические изменения как в EF Core 5, так и в EF Core 6, но я не вижу ничего, что могло бы вызвать это.

Комментарии:

1. Происходит ли это также в реальном приложении? Вы не должны использовать базу данных в памяти для тестирования .

2. Я также изначально думал, что это может быть проблема с использованием поставщика в памяти, но я попытался отключить его для Sqlite, и возникла та же проблема.

3. Тогда это выглядит как еще одна ошибка в EF core 6. Пожалуйста, проверьте проблемы с github, если сообщалось о подобных ошибках.

4. Я думаю, что это может быть как-то связано с обнуляемыми ссылочными типами и тем, как ядро EF обрабатывает это. В моем проекте включен параметр nullable. Обратите внимание, что свойство навигации EntityBId в EntityA является обнуляемым идентификатором Guid, но свойство объекта не обнуляемо. Я только что попытался сделать это также обнуляемым ( public EntityB? EntityB { get; set; } ), и, похоже , это решило проблему. Так ли мы должны определять необязательные отношения, когда NRT теперь включен?

5. @AndyFurniss вы проверили, настроено ли отношение как обязательное и каскадное? Я думаю, что это может быть проблемой, потому EntityB что свойство не обнуляется, что приводит к требуемому отношению (которое каскадируется по умолчанию) и, следовательно EntityA , будет каскадироваться, как только его связь с EntityB будет разорвана. Возможно, попробуйте создать миграцию и проверить, изменился ли снимок модели.