【问题标题】:EntityFramework Core 1.1.1 - Replacing old collection with new and overwriting valuesEntityFramework Core 1.1.1 - 用新值替换旧集合并覆盖值
【发布时间】:2017-10-05 15:19:17
【问题描述】:

我有以下 sn-p 代码,几个月来我一直在使用它来更新 Client 的数据模型,其值通过 ViewModel 传回。

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

更新到最新版本的 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; }
}

我是否以错误的方式解决这个问题,如果是这样,如果我不关心该集合中的值,只要它们被新值覆盖,替换集合的正确方法是什么?

【问题讨论】:

  • 你能发EmailCC类吗
  • @IvanStoev 更新为包括 EmailCC 和 Client 类
  • 您的EmailCC PK 列不是数据库生成的。可能应该删除[DatabaseGenerated(DatabaseGeneratedOption.Computed)] 或使用[DatabaseGenerated(DatabaseGeneratedOption.Identity)]

标签: c# asp.net-core entity-framework-core


【解决方案1】:

从您的模型创建和应用迁移会生成以下 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 集合被新值替换)。

【讨论】:

  • DatabaseGenerated 属性是问题所在,现已解决。这个答案的信息很好,谢谢伊万
  • 不客气。但我建议检查数据库表,因为没有流畅的配置和级联删除,我发现旧的EmailCC 记录没有被删除,而是留在数据库中,ClientId 列设置为NULL。
  • 使用分配方式,只会破坏EmailCC与客户端的关系链接。我将手动更新 DbSet 并为 EmailCc 集合中的每条记录使用 RemoveRange - 应将实体状态标记为已删除。
猜你喜欢
  • 1970-01-01
  • 2010-12-10
  • 1970-01-01
  • 2016-09-02
  • 2015-08-01
  • 1970-01-01
  • 2013-04-09
  • 2020-08-03
  • 2015-01-15
相关资源
最近更新 更多