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 功能共同造成的。
- 如 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
- 但是,SQLite 支持自增列仅 如果它是单个主键列。因此,它会生成常规
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 基础结构使用它来控制所有运行时行为)。因此,从技术上讲,两者都可以被视为错误。但他们就是他们。通常更喜欢约定而不是配置,但对于具有任意默认值的事物要明确。
我有一个 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 功能共同造成的。
- 如 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)
- 但是,SQLite 支持自增列仅 如果它是单个主键列。因此,它会生成常规
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 基础结构使用它来控制所有运行时行为)。因此,从技术上讲,两者都可以被视为错误。但他们就是他们。通常更喜欢约定而不是配置,但对于具有任意默认值的事物要明确。