Ошибка ограничения FOREIGN KEY в модульных тестах SQLite

У меня есть база данных с двумя разными путями к определенному объекту, один путь с DeleteBehavior.Cascade, а другой с DeleteBehavior.Restrict, чтобы избежать ошибки нескольких каскадных путей при создании базы данных. Это работает, как я и ожидал, при локальном запуске с использованием IIS Express и SQL Server Visual Studio в памяти, но выдает ошибку в модульных тестах, проверяющих ту же функциональность.

Пример:

public class Account
{
    public string Id { get; set; }
}

Каждая учетная запись может иметь подписки:

public class Subscription
{
    public string Id { get; set; }

    // relationships

    public string AccountId { get; set; }

    public Account Account { get; set; }
}

Контакты могут быть как у аккаунтов, так и у подписок (это может не иметь особого смысла, но это просто пример, на самом деле моя модель сложнее, но по сути она сводится к следующему):

public class Contact
{
    public string Id { get; set; }

    // relationships

    public string AccountId { get; set; }

    public Account Account { get; set; }

    public string SubscriptionId { get; set; }

    public Subscription Subscription { get; set; }
}

Я хочу, чтобы любые контакты удалялись, если либо аккаунт, либо подписка будут удалены. Это моя конфигурация построителя моделей для контактов:

public void Configure(EntityTypeBuilder<Contact> entity)
{
    entity.HasOne(e => e.Account)
        .WithMany()
        .HasForeignKey(e => e.AccountId)
        .OnDelete(DeleteBehavior.Restrict);

    entity.HasOne(e => e.Subscription)
        .WithMany()
        .HasForeignKey(e => e.SubscriptionId)
        .OnDelete(DeleteBehavior.Cascade);
}

конфиг для подписок:

public void Configure(EntityTypeBuilder<Subscription> entity)
{
    entity.HasOne(e => e.Account)
        .WithMany()
        .HasForeignKey(e => e.AccountId)
        .OnDelete(DeleteBehavior.Cascade);
}

Идея заключалась в том, что при удалении учетной записи подписки будут удалены с помощью поведения удаления Cascade, и это удалит контакты, а поведение Restrict между учетными записями и контактами решит ошибку «множественных каскадных путей».

Это работает, когда я запускаю локально, я могу удалить учетную запись, и все подписки и контакты удаляются, ошибки нет. Проблема заключается в модульных тестах (с использованием xUnit), которые используют SQLite в памяти. Я хочу проверить, что удаление учетной записи приведет к удалению всех контактов:

[Fact]
public async Task DeleteAccount_ContactIsDeleted()
{
    using (var factory = new ContextFactory()) // same connection will be used within using block
    {
        using (var context = factory.CreateContext())
        {
            await context.SeedDatabaseOneContactAsync(); // inserts account, subscription and contact into database
        }

        using (var context = factory.CreateContext())
        {
            Assert.Single(context.Contacts);
            Assert.Single(context.Subscriptions);
            Assert.Single(context.Accounts);

            var account = await context.Accounts.SingleAsync();
            context.Remove(account);
            await context.SaveChangesAsync();

            Assert.Empty(context.Contacts);
            Assert.Empty(context.Accounts);
            Assert.Empty(context.Subscriptions);
        }
    }
}

Однако это вызывает следующую ошибку

Сообщение: Microsoft.EntityFrameworkCore.DbUpdateException: произошла ошибка при обновлении записей. Подробности смотрите во внутреннем исключении. ---- Microsoft.Data.Sqlite.SqliteException: ошибка SQLite 19: «Ошибка ограничения FOREIGN KEY».

на линии

await context.SaveChangesAsync();

Я хотел бы знать, является ли это проблемой с SQLite, или я что-то делаю неправильно с моей моделью или конфигурацией?

Примечание. Я использую Entity Framework Core 2.1.1, и эти отношения обязательны (не могут быть нулевыми).


person amiller    schedule 20.08.2018    source источник
comment
Пожалуйста, отметьте ответ как принятый, если он вам помог.   -  person Gert Arnold    schedule 09.06.2021


Ответы (3)


По умолчанию ограничения FK отключены в SQLite. http://www.sqlite.org/foreignkeys.html

Ограничения внешнего ключа по умолчанию отключены (для обратной совместимости), поэтому их необходимо включать отдельно для каждого подключения к базе данных. (Обратите внимание, однако, что будущие выпуски SQLite могут быть изменены таким образом, что ограничения внешнего ключа будут включены по умолчанию. Осторожные разработчики не будут делать никаких предположений о том, включены ли внешние ключи по умолчанию, а вместо этого будут включать или отключать их по мере необходимости.)

Вы можете включить их, добавив foreign keys=true в строку подключения. (data source=test.db;foreign keys=true для полного примера)

person Selmir    schedule 21.08.2018

Не уверен, что это ваша проблема, но у меня была такая же ошибка, когда конфигурация отношений свободного API не соответствовала фактической структуре таблицы.

В моем случае свободный API установил отношение один к одному, но фактическая базовая структура таблицы была отношением один ко многим.

person Staten    schedule 14.11.2018

Старый вопрос, но у меня такая же проблема.

До сих пор я думаю, что это связано с тем, что столбец представляет собой строку, которую вы не можете установить как тип значения, допускающий значение NULL. например. нить? если вы измените его на Guid? или инт? тогда у вас не такая проблема.

Я думаю, что это может быть ошибка в структуре сущностей, где она думает, что столбец не может быть обнуляемым, хотя на самом деле это так.

person Cam Langsford    schedule 09.06.2021