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

uplii1fm  于 2023-05-18  发布在  SQLite
关注(0)|答案(1)|浏览(126)

我有一个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:更新条目时出错。有关详细信息,请参见内部异常。
---> Microsoft.Data.Sqlite.SqliteException(0x80004005):错误19:'NOT NULL约束失败:评论,同上。
文档说拥有的表有一个隐式键,这解释了关于Comment.Id的抱怨。
但是为什么这种情况只发生在sqlite上,以及 * 我如何修复它 *?

pw136qt2

pw136qt21#

这是由(1)不正确的(恕我直言)EF核心默认值和(2)不受支持的SQLite功能的组合引起的。
1.如自有类型集合EF Core文档中所述
拥有的类型需要主键。如果.NET类型上没有好的候选属性,EF Core可以尝试创建一个。然而,当通过集合定义拥有类型时,仅仅创建一个影子属性来充当所有者的外键和拥有示例的主键是不够的,就像我们对OwnsOne所做的那样:每个所有者可以有多个所拥有的类型示例,因此所有者的密钥不足以为每个所拥有的示例提供唯一标识。
问题是,如果您没有定义显式PK,那么EF Core会生成名为Id的shadow属性(列),类型为int,自动递增(他们认为,但请参见(2))并在(OwnerId,Id)上定义 composite 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();
});

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

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

这是缺失的,并导致OP设计中的问题。
我个人更喜欢EF Core抛出常规的无键定义错误,而不是定义所有数据库都不支持的PK结构。另外,SQLite提供程序抛出异常,而不是默默地忽略自动增量模型请求,从而引入模型元数据之间的差异(EF Core基础设施使用它来控制所有运行时行为)。所以两者在技术上都可以被认为是bug。但他们就是他们。一般来说,更喜欢约定而不是配置,但是对于具有任意默认值的东西要显式。

相关问题