EFCore: SqliteException (Error 19) when trying out TPH

0

I'm trying out a multilingual database with TPH. What I'm after is for there to only be one table that contains all strings (names, descriptions, etc.) in different languages.

EFCore can generate me an Sqlite database, but when I add this LanguageName, it throws an SqliteException.

await context.LanguageNames.AddAsync(
  new LanguageName {
    Id = LanguageName.EnglishLanguageNameId,
    Value = LanguageName.EnglishLanguageNameValue,
    ForeignEntityId = Language.EnglishLanguageId,
    LanguageId = Language.EnglishLanguageId
  });
await context.SaveChangesAsync();

The exception says: Unhandled exception. Microsoft.Data.Sqlite.SqliteException (0x80004005): SQLite Error 19: 'FOREIGN KEY constraint failed'. Googling wasn't any help in tackling this specific issue.

I've tried uploading the Sqlite file to an online IDE (this one, specifically) and executing the snippet below. It worked as expected.

INSERT INTO MultilingualString(id, Value, foreignentityid, languageid, discriminator)
VALUES (1, 'English', 1, 1, 'LanguageName')

So I tried running the same code in the DbContext.

await context.Database.ExecuteSqlRawAsync(
  "INSERT INTO MultilingualString(id, value, foreignentityid, languageid, discriminator) " +
  "VALUES (1, 'English', 1, 1, 'LanguageName')"
);

I still get the same SqliteException. What am I doing wrong?

In case more details are needed, here are the models I'm working with.

public record MultilingualString {
  public UInt64 Id { get; init; }
  public String Value { get; init; }

  public UInt64 ForeignEntityId { get; init; }

  public Language Language { get; init; }
  public UInt64 LanguageId { get; init; }

  protected internal static UInt64 GenerateId() { ... }
}
public record MultilingualString<TEntity> : MultilingualString
  where TEntity : class {
    public TEntity ForeignEntity { get; init; }
  }

public record Language {
  public UInt64 Id { get; init; }
  public IEnumerable<LanguageName> Names { get; init; }

  public IEnumerable<MultilingualString> StringValues { get; init; }

  public static UInt64 EnglishLanguageId { get; } = 1;
}
public record LanguageName : MultilingualString<Language> {
  public static String EnglishLanguageNameValue { get; } = "English";
  public static UInt64 EnglishLanguageNameId { get; } = GenerateId();
}

Here's the model building:

protected override void OnModelCreating(ModelBuilder modelBuilder) {
  base.OnModelCreating(modelBuilder);
  BuildMultilingualStringModel(modelBuilder)
    .BuildLanguageModel(modelBuilder)
    .BuildItemModel(modelBuilder);
}
protected virtual DbContext BuildMultilingualStringModel(
  ModelBuilder modelBuilder) {
    modelBuilder.Entity<MultilingualString>(builder => {
      builder.HasKey(@string => @string.Id);
      builder.Property(@string => @string.Value).IsRequired();
    });
    modelBuilder.Entity<MultilingualString>()
      .HasDiscriminator<String>("Discriminator")
      .HasValue<LanguageName>(nameof(LanguageName))
      .HasValue<ItemName>(nameof(ItemName))
      .HasValue<ItemDescription>(nameof(ItemDescription));
    return this;
  }
protected virtual DbContext BuildLanguageModel(
  ModelBuilder modelBuilder) {
    modelBuilder.Entity<Language>(builder => {
      builder.HasKey(language => language.Id);
      builder.HasMany(language => language.Names)
        .WithOne(name => name.ForeignEntity)
        .HasForeignKey(name => name.ForeignEntityId);
      builder.HasMany(language => language.StringValues)
        .WithOne(@string => @string.Language)
        .HasForeignKey(@string => @string.LanguageId);
      builder.HasData(new Language {
        Id = Language.EnglishLanguageId
      });
    });
    return this;
  }
protected virtual DbContext BuildItemModel(
  ModelBuilder modelBuilder) {
    modelBuilder.Entity<Item>(builder => {
      builder.HasKey(item => item.Id);
      builder.HasMany(item => item.Names)
        .WithOne(name => name.ForeignEntity)
        .HasForeignKey(name => (UInt32)name.ForeignEntityId);
      builder.HasMany(item => item.Descriptions)
        .WithOne(description => description.ForeignEntity)
        .HasForeignKey(description => description.ForeignEntityId);
    });
    return this;
  }

These are the other models in the database, just in case any of these have something to do with the error.

public record Item {
  public UInt64 Id { get; init; }
  public IEnumerable<ItemName> Names  { get; init; }
  public IEnumerable<ItemCategory> Categories { get; init; }
  public IEnumerable<ItemDescription> Descriptions { get; init; }
}
public record ItemName : MultilingualString<Item> { }
public record ItemDescription : MultilingualString<Item> { }

EDIT: These are the create scripts:

CREATE TABLE "Items" (
    "Id" INTEGER NOT NULL CONSTRAINT "PK_Items" PRIMARY KEY AUTOINCREMENT
)

CREATE TABLE sqlite_sequence(name,seq)

CREATE TABLE "Languages" (
    "Id" INTEGER NOT NULL CONSTRAINT "PK_Languages" PRIMARY KEY AUTOINCREMENT
)

CREATE TABLE "MultilingualStrings" (
    "Id" INTEGER NOT NULL CONSTRAINT "PK_MultilingualStrings" PRIMARY KEY AUTOINCREMENT,
    "Value" TEXT NOT NULL,
    "ForeignEntityId" INTEGER NOT NULL,
    "LanguageId" INTEGER NOT NULL,
    "Discriminator" TEXT NOT NULL,
    CONSTRAINT "FK_MultilingualStrings_Items_ForeignEntityId" FOREIGN KEY ("ForeignEntityId") REFERENCES "Items" ("Id") ON DELETE CASCADE,
    CONSTRAINT "FK_MultilingualStrings_Languages_ForeignEntityId" FOREIGN KEY ("ForeignEntityId") REFERENCES "Languages" ("Id") ON DELETE CASCADE,
    CONSTRAINT "FK_MultilingualStrings_Languages_LanguageId" FOREIGN KEY ("LanguageId") REFERENCES "Languages" ("Id") ON DELETE CASCADE
)

CREATE INDEX "IX_MultilingualStrings_ForeignEntityId" ON "MultilingualStrings" ("ForeignEntityId")

CREATE INDEX "IX_MultilingualStrings_LanguageId" ON "MultilingualStrings" ("LanguageId")
c#
entity-framework-core
asked on Stack Overflow Dec 9, 2020 by b12629 • edited Dec 9, 2020 by b12629

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0