使用 EF Core 插入具有手动分配 ID 的新实体

Mar*_*iak 1 c# mysql entity-framework-core asp.net-core-2.0

我需要将一个实体插入数据库,该实体对其 id 有约束,因此我想手动设置它。

注意:下面的模型是了解我正在做的事情的最小示例。整个过程在 ASP.NET Core 2 上运行,并带有 EF Core 和 Pomelo MySql 提供程序。

我正在尝试在现有数据库(MySQL)中创建一个实体,其定义如下:

CREATE TABLE Users (
    id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(128) NOT NULL);

CREATE TABLE UserData (
    id INTEGER PRIMARY KEY,
    name VARCHAR(32) NOT NULL,
    FOREIGN KEY(id) REFERENCES Users(id));
Run Code Online (Sandbox Code Playgroud)

所以Users表中的每条记录在表中都有一条记录UserData。现在在我的代码中我想使用类似的东西

Context.Users.Add(user);
Context.SaveChanges();
data.Id = user.Id;
Context.UserData.Add(data);
Context.SaveChanges();
Run Code Online (Sandbox Code Playgroud)

它们User应该Id由数据库生成,然后UserData用它创建Id。然而,EF执行的语句会导致错误:

Failed executing DbCommand (6ms) [Parameters=[@p0='?', @p1='?' (Size = 32)], CommandType='Text', CommandTimeout='30']
  INSERT INTO `UserData` (`id`, `name`)
  VALUES (@p0, @p1);

MySql.Data.MySqlClient.MySqlException: Cannot add or update a child row:
  a foreign key constraint fails (`Main`.`UserData`, CONSTRAINT `UserData_ibfk_1`
  FOREIGN KEY (`id`) REFERENCES `Users` (`id`))
Run Code Online (Sandbox Code Playgroud)

这意味着无论Id传递到 MySQL 的是什么,它都不是User.

我什至尝试按照使用 Entity Framework 手动输入键中的建议添加[DatabaseGenerated(DatabaseGeneratedOption.None)]a ,但我认为这更多是关于 CodeFirst 方法,而不是拥有一个已经运行的数据库。UserData.Id

从这个问题:EF Core Add function returns negative id我了解到Context.Add()它修改了Id,但在之后分配我的值Add不会改变任何东西。

如果它很重要,这是我的 DbContext 配置

modelBuilder.Entity<UserData>(entity =>
{
    entity.Property(e => e.Id)
        .HasColumnName("id")
        .HasColumnType("int(11)")
        .ValueGeneratedNever();

    entity.Property(e => e.Name)
        .IsRequired()
        .HasColumnName("name")
        .HasMaxLength(32);

    entity.HasOne(d => d.User)
        .WithOne(p => p.UserData)
        .HasForeignKey<UserData>(d => d.Id)
        .OnDelete(DeleteBehavior.ClientSetNull)
        .HasConstraintName("UserData_ibfk_1");
}
modelBuilder.Entity<User>(entity =>
{
    entity.Property(e => e.Id)
        .HasColumnName("id")
        .HasColumnType("int(11)")
        .ValueGeneratedNever();

    entity.Property(e => e.Email)
        .IsRequired()
        .HasColumnName("email")
        .HasMaxLength(128);
}
Run Code Online (Sandbox Code Playgroud)

Iva*_*oev 6

应该由数据库User生成Id

不根据您的流畅配置:

modelBuilder.Entity<User>(entity =>
{
    entity.Property(e => e.Id)
        .HasColumnName("id")
        .HasColumnType("int(11)")
        .ValueGeneratedNever(); // <-- the problem
}
Run Code Online (Sandbox Code Playgroud)

替换ValueGeneratedNever()为ValueGeneratedOnAdd(),问题就解决了。

作为旁注,由于您似乎在 和 中都有导航属性User,UserData因此您可以使用它们而不是 FK 属性,从而消除了中间的需要,SaveChanges只是获取生成的Id。例如,而不是:

Context.Users.Add(user);
Context.SaveChanges();
data.Id = user.Id;
Context.UserData.Add(data);
Context.SaveChanges();
Run Code Online (Sandbox Code Playgroud)

你可以简单地使用:

user.UserData = data;
Context.Users.Add(user); // <-- will also add `UserData`
Context.SaveChanges();
Run Code Online (Sandbox Code Playgroud)

EF 将以正确的顺序插入两条记录(首先插入User,然后插入上一次插入生成的UserData记录Id)。

  • 是否从 EF 生成数据库并不重要。重要的是要将数据库表正确地**映射**到您的类。由于现有数据库表中有自动增量列,因此您需要让 EF 知道这一点,以便在插入数据库记录后它会检索“Id”并自动更新对象。而不是像您自己的回答那样**手动**执行此操作。 (3认同)