我有几段代码,我用了几个月来更新Client
的数据模型,其值通过ViewModel传回。
我正在使用LINQ来过滤从客户端传递的电子邮件CC值的集合,这是一个字符串列表,并使用指定的电子邮件地址创建EmailCC
对象的新实例,这是分配给客户EmailCCs,这是一对多关系映射。我期望的正确行为是打破先前输入的电子邮件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; }
}
我是否以错误的方式解决这个问题,如果是这样,只要用新值覆盖,我不会关心该集合中的值,那么更换集合的正确方法是什么?
答案 0 :(得分:3)
从模型创建和应用迁移会生成以下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
);
更重要的是,有问题的代码w.o任何修改都按预期工作(没有例外,旧的EmailCCs
集合被新值替换。)