我有一个 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'.
docs say拥有的表有一个隐含的键,这解释了关于 Comment.Id
的提示。
但是为什么这只发生在 sqlite 上,我该如何解决它?
最佳答案
这是由 (1) 不正确的 (恕我直言) EF Core 默认值和 (2) 不受支持的 SQLite 功能共同造成的。
- 如 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) 上定义 复合 PK
- 然而,SQLite 支持自动增量列仅如果它是单个 PK 列。因此,它会生成常规的
INT
列Id
,然后需要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 基础设施使用它来控制所有运行时行为)。因此,从技术上讲,两者都可以被视为错误。但他们就是他们。一般来说,约定优于配置,但对于具有任意默认值的事物要明确。
关于c# - EF Core 拥有的实体影子 PK 导致 SQLite 违反空约束,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69819523/