我有以下代码段,这些代码段我已经使用了几个月的时间,使用通过ViewModel传回的值来更新Client的数据模型。

我正在使用LINQ来过滤从客户端传递的电子邮件CC值的集合(这是一个字符串列表),并使用指定的电子邮件地址创建EmailCC对象的新实例,然后将其分配给客户端EmailCC这是一对多关系映射。我期望的正确行为是打破先前输入的电子邮件CC之间的关系,然后使用新输入的值。

更新到最新版本的EF Core之后,尝试更新时收到以下异常,我相信我理解这是由于在相同上下文中删除和修改EmailCCs集合引起的。


  InvalidOperationException:无法跟踪实体类型'EmailCC'的实例,因为已经跟踪了具有相同键的该类型的另一个实例。添加新实体时,对于大多数键类型,如果未设置任何键(即,如果为键属性分配了其类型的默认值),则将创建唯一的临时键值。如果您为新实体明确设置键值,请确保它们不与现有实体或为其他新实体生成的临时值冲突。附加现有实体时,请确保仅将一个具有给定键值的实体实例附加到上下文。


我的EmailCC主键列是数据库生成的。

Post方法代码为

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(
    [Bind("Id,FirstName,LastName,Email,EmailCc")] ClientEditViewModel clientEditViewModel)
{
    if (!ModelState.IsValid) return View(clientEditViewModel);

    Client client =
        _dbContext.Client.Include(p => p.EmailCCs).SingleOrDefault(p => p.Id == clientEditViewModel.Id);

    client.FirstName = clientEditViewModel.FirstName;
    client.LastName = clientEditViewModel.LastName;
    client.Email = clientEditViewModel.Email;
    client.EmailCCs =
        clientEditViewModel.EmailCc.Where(p => !string.IsNullOrWhiteSpace(p))
            .Select(p => new EmailCC { Email = p }).ToList();

    await _dbContext.SaveChangesAsync();
    return RedirectToAction(nameof(Edit), new { id = client.Id });
}


EmailCC类为

public class EmailCC
{
    [Column("ID")]
    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public int Id { get; set; }
    public string Email { get; set; }
}


客户类别:

public class Client
{
    [Column("ID")]
    public int Id { get; set; }

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public string Email { get; set; }

    public virtual ICollection<EmailCC> EmailCCs { get; set; }
}


我会以这种错误的方式走吗?如果是这样,替换集合的正确方法是什么,只要它们被新值覆盖,我就不在乎该集合中的值了吗?

最佳答案

从模型创建和应用迁移将生成以下SQL命令:

CREATE TABLE [EmailCCs] (
    [ID] int NOT NULL,
    [ClientId] int,
    [Email] nvarchar(max),
    CONSTRAINT [PK_EmailCCs] PRIMARY KEY ([ID]),
    CONSTRAINT [FK_EmailCCs_Clients_ClientId] FOREIGN KEY ([ClientId]) REFERENCES [Clients] ([ID]) ON DELETE NO ACTION
);


您可以在这里注意到几个问题。 PK不是IDENTITY,因此不是数据库生成的。您应该删除[DatabaseGenerated(DatabaseGeneratedOption.Computed)]批注或使用[DatabaseGenerated(DatabaseGeneratedOption.Identity)]

第二个问题是ClientId列可为空,并且删除层叠已关闭(ON DELETE NO ACTION)。为了使替换代码正常工作,您需要配置删除级联所需的关系。

这是固定模型:

public class EmailCC
{
    [Column("ID")]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public string Email { get; set; }
}


流畅的配置:

modelBuilder.Entity<Client>()
    .HasMany(e => e.EmailCCs)
    .WithOne()
    .IsRequired()
    .OnDelete(DeleteBehavior.Cascade);


现在会创建另一个命令:

CREATE TABLE [EmailCCs] (
    [ID] int NOT NULL IDENTITY,
    [ClientId] int NOT NULL,
    [Email] nvarchar(max),
    CONSTRAINT [PK_EmailCCs] PRIMARY KEY ([ID]),
    CONSTRAINT [FK_EmailCCs_Clients_ClientId] FOREIGN KEY ([ClientId]) REFERENCES [Clients] ([ID]) ON DELETE CASCADE
);


更重要的是,没有任何修改的问题代码可以按预期工作(无例外,旧的EmailCCs集合被新的值替换)。

07-27 23:24