EF Core 拥有的实体影子 PK 导致 SQLite 违反空约束

lon*_*nix 4 c# sqlite entity-framework-core ef-core-5.0

我有一个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>();
}
Run Code Online (Sandbox Code Playgroud)

AndPost的配置包括:

builder.OwnsMany(x => x.Comments, x => {
  x.Property(y => y.Text).IsRequired();
});
Run Code Online (Sandbox Code Playgroud)

种子代码包括:

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

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

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

Microsoft.EntityFrameworkCore.DbUpdateException:更新条目时发生错误。有关详细信息,请参阅内部异常。
---> Microsoft.Data.Sqlite.SqliteException (0x80004005): SQLite 错误 19: 'NOT NULL 约束失败: Comment.Id'。

文档拥有的表有一个隐式键,这解释了有关Comment.Id.

但为什么这种情况只发生在 sqlite 上,我该如何修复它

Iva*_*oev 10

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

  1. 如EF Core 文档拥有类型的集合中所述

自有类型需要主键。如果 .NET 类型没有好的候选属性,EF Core 可以尝试创建一个。但是,当通过集合定义自有类型时,仅仅创建一个影子属性来充当所有者的外键和自有实例的主键是不够的,就像我们所做的那样OwnsOne:可以有多个每个所有者拥有的类型实例,因此所有者的密钥不足以为每个拥有的实例提供唯一的身份。

问题是,如果您没有定义显式 PK,则 EF Core 会生成名为Id、 type int、 autoincrement 的影子属性(列)(他们认为,但请参阅 (2))并在 (OwnerId, Id) 上定义复合PK

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

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

builder.Entity<Post>.OwnsMany(e => e.Comments, cb => {
    cb.HasKey("Id"); // <-- add this
    // The rest...
    cb.Property(e => e.Text).IsRequired();
});
Run Code Online (Sandbox Code Playgroud)

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

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

它的缺失导致了 OP 设计中的问题。

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

  • 令人惊奇的答案,我永远无法发现这些。对我帮助很大,谢谢!我还在 [repo](https://github.com/dotnet/efcore/issues/26521#issuecomment-959797156) 问题中引用了您的评论,我希望他们能够考虑您的想法 - 一个更清晰的例外会有帮助我解决了问题。 (4认同)