EF Core 拥有的实体影子 PK 导致与 SQLite 的空约束冲突

EF Core owned entity shadow PK causes null constraint violation with SQLite

我有一个 Comment 拥有的实体类型:

public class Comment {    // owned entity type
  public Comment(string text) { Text = text; }
  public string Text { get; private set; }
}

public class Post {
  public Post(string content) { Content = content; }
  public long Id { get; private set; }
  public string Content { get; private set; }
  public ICollection<Comment> Comments { get; private set; } = new HashSet<Comment>();
}

Post的配置包括:

builder.OwnsMany(x => x.Comments, x => {
  x.Property(y => y.Text).IsRequired();
});

播种代码包括:

var post = new Post("content");
post.Comments.Add(new Comment("comment1"));
post.Comments.Add(new Comment("comment2"));
await _context.AddAsync(post);
await _context.SaveChangesAsync();

当我使用 postgres 提供程序时,我可以成功地创建、播种和编辑数据库。

当我使用 sqlite 提供程序时,我可以成功创建数据库,但是当我尝试为它做种时出现此错误:

Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. See the inner exception for details.
---> Microsoft.Data.Sqlite.SqliteException (0x80004005): SQLite Error 19: 'NOT NULL constraint failed: Comment.Id'.

拥有的 table 的 docs say 有一个隐式密钥,这解释了对 Comment.Id 的抱怨。

但是为什么这只发生在 sqlite 上,我该如何解决

这是由 (1) 不正确的(恕我直言)EF Core 默认和 (2) 不受支持的 SQLite 功能共同造成的。

  1. Collections of owned types EF Core 文档中所述

Owned types need a primary key. If there are no good candidates properties on the .NET type, EF Core can try to create one. However, when owned types are defined through a collection, it isn't enough to just create a shadow property to act as both the foreign key into the owner and the primary key of the owned instance, as we do for OwnsOne: there can be multiple owned type instances for each owner, and hence the key of the owner isn't enough to provide a unique identity for each owned instance.

问题是,如果您没有定义明确的 PK,那么 EF Core 会生成名为 Id 的影子 属性(列),类型 int,自动增量(他们认为,但是请参阅 (2)) 并在 (OwnerId, Id)

上定义 composite PK
  1. 但是,SQLite 支持自增列 如果它是单个主键列。因此,它会生成常规 INTId,然后需要 INSERT 上的显式值,但 EF Core 不会发送它,因为它仍然认为 属性 是自动生成的在服务器上。

话虽如此,您最好始终定义拥有的集合实体的 PK。由于自动增量本身是唯一的,绝对最小值是将自动生成的影子 Id 属性 标记为 PK,例如

builder.OwnsMany(e => e.Comments, cb => {
    cb.HasKey("Id"); // <-- add this
    // The rest...
    cb.Property(e => e.Text).IsRequired();
});

生成的迁移应具有 Id 列的“Sqlite:Autoincrement”注释:

Id = table.Column<long>(type: "INTEGER", nullable: false)
    .Annotation("Sqlite:Autoincrement", true),

丢失并导致 OP 设计出现问题。

我个人更愿意 EF Core 抛出常规的无键定义错误,而不是定义所有数据库都不支持的 PK 构造。此外,SQLite 提供程序抛出异常而不是默默地忽略自动增量模型请求,从而引入模型元数据之间的差异(EF Core 基础结构使用它来控制所有运行时行为)。因此,从技术上讲,两者都可以被视为错误。但他们就是他们。通常更喜欢约定而不是配置,但对于具有任意默认值的事物要明确。