【问题标题】:SqlException: Cannot insert explicit value for identity column in table [Table name] when IDENTITY_INSERT is set to OFFSqlException:当 IDENTITY_INSERT 设置为 OFF 时,无法在表 [表名] 中插入标识列的显式值
【发布时间】:2019-11-11 12:42:06
【问题描述】:

我正在创建一个用于费用跟踪的网站,当我发送一个新的 POST 请求调用 AddNew 方法来添加一个新条目时,我收到此错误:

SqlException:当 IDENTITY_INSERT 设置为 OFF 时,无法在表“Accounts”中插入标识列的显式值。当 IDENTITY_INSERT 设置为 OFF 时,无法为表“类别”中的标识列插入显式值。

public class Entry
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public string Description { get; set; }
    public decimal MoneyAmount { get; set; }
    public virtual Account Account { get; set; }
    public virtual Category Category { get; set; }
    public string CreatedTimestamp { get; set; }
}

public class Account
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual User User { get; set; }
    //public byte[] BankApi { get; set; }
    public decimal MoneyAmount { get; set; }
    public string CreatedTimestamp { get; set; }
    public virtual ICollection<Entry> Entries { get; set; }
}

public class Category
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual EntryType EntryType { get; set; }
    public virtual ICollection<Entry> Entries { get; set; }
}

public interface IEntryService
{
    void AddNew(Entry entry);
}

public class EntryService : IEntryService
{
    private DataContext _context;

    public EntryService(DataContext context)
    {
        _context = context;
    }

    public void AddNew(Entry entry)
    {
        entry.CreatedTimestamp = DateTime.Now.ToString();

        _context.Entries.Add(entry);
        _context.SaveChanges();
    }
}

这是我的邮递员要求:

ID == 1 的帐户和 ID == 1 的类别都已存在于各自的数据库中。

此外,当我向 Account 数据库中插入值时,其方法看起来与 AddNew 的条目完全相同,它可以正常工作。

有人知道这是怎么回事吗?

【问题讨论】:

  • 你试过这个解决方案了吗:stackoverflow.com/questions/1334012/…
  • DatabaseGenerated.Identity 表示您将 PK 生成留给数据库。在添加条目之前尝试设置 entry.Id = 0。
  • @VidmantasBlazevicius 我将 entry.Id 设置为 0,但我得到了同样的错误。
  • 这里有很多缺失的代码。该错误抱怨您为AccountsCategories 实体设置了一个ID。错误在于您实例化并保存这两个对象而不是Entry

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


【解决方案1】:

问题是您将具有关系的实体传递回服务器。每个请求都由不同的数据库上下文提供服务,因此虽然您的条目与其关联的帐户和类别看起来像实体,但它们不是。它们是 DbContext 一无所知的反序列化 POCO 对象。当您传递引用数据库中已存在的其他实体的实体时,接收上下文不知道这些实体,因此它将它们解释为新记录并尝试插入它们。

我的默认建议是永远不要在客户端和服务器之间传递实体。这会导致从服务器到客户端的序列化错误和性能问题,并使您的系统容易被篡改,以及在从客户端到服务器使用时出现插入错误或重复等问题。

在不做太多改动的情况下解决您的问题:

public void AddNew(Entry entry)
{
    entry.CreatedTimestamp = DateTime.Now.ToString();
    var account = _context.Accounts.Single(x => x.Id = entry.Account.Id);
    var category = _context.Categories.Single(x => x.Id = entry.Category.Id);
    entry.Account = account; 
    entry.Category = category;
    _context.Entries.Add(entry);
    _context.SaveChanges();
}

这将条目帐户和类别与上下文已知的实例相关联。

另一种方法是将帐户和类别附加回 DbContext。这也可以,但是它会使您的系统容易受到篡改。传回控制器的数据可以被客户端机器上的调试工具或嗅探器截获,其中消息中的数据可以被更改以以您不希望的方式更改记录及其相关记录中的详细信息。如果出于任何原因,任何代码最终将这些重新附加的实体设置为已修改状态,则这些更改将在 SaveChanges 上持久保存到数据库中。在处理多个分离的引用时,附加实体也可能是一件麻烦事。例如,如果您一次与亲戚一起保存多条记录。当所有亲戚都是唯一的时,附加每个都可以正常工作,但是如果没有额外的逻辑来检查每个以查看是否已经有附加的引用,当尝试对已附加(或加载)的实体附加的第二个引用时,您可能会出错.这意味着需要更多代码来检查本地关联并在找到时替换它。

【讨论】:

  • 您在这里提出了很好的观点,但篡改风险不太现实。如果AdNew 方法只是附加已知的account/category,它们将永远不会被标记为Changed。然后,如果这种拦截真的发生,那么主实体也可能被篡改, 成功(任何未加密的网络流量等)。 FWIW,我的建议是开始使用外键关联而不是这些独立关联
  • 是的,它需要一些东西来将实体状态设置为已修改。对于不太可能的简单引用,但随着系统的发展,对实体的引用永远不应被猜测是来自数据状态并被认为是安全的,还是来自其他地方并被认为是不安全的。类别可能是安全的,但稍后会更新帐户上的字段?可能而且肯定有风险。隐藏在不同方法中的代码甚至可以使用 context.Accounts 来获取并假设它在调用 SaveChanges 之前获取数据状态。我不主张发送任何实体、参考或主要。
  • @StevePy 嘿伙计,在这里发表评论以感谢您。由于这是 stackoverflow 中的一个常见问题,而常见的答案并没有解决我的问题,所以我很沮丧。最后,您的详细回答为我解决了问题。我无法表达我的感激之情。
【解决方案2】:

我刚刚从

更改了 Entry 模型
public class Entry
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public string Description { get; set; }
    public decimal MoneyAmount { get; set; }
    public virtual Account Account { get; set; }
    public virtual Category Category { get; set; }
    public string CreatedTimestamp { get; set; }
}

到这里:

 public class Entry
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; set; }
        public string Description { get; set; }
        public decimal MoneyAmount { get; set; }
        public int AccountId { get; set; }
        public int CategoryId { get; set; }
        public string CreatedTimestamp { get; set; }
    }

现在一切似乎都运行良好。 C# 发挥了魔力,选择 AccountId 和 CategoryId 作为外键。

【讨论】:

    猜你喜欢
    • 2018-06-29
    • 2018-12-30
    • 1970-01-01
    • 2020-03-09
    • 2010-11-22
    • 2016-08-18
    • 2019-03-10
    相关资源
    最近更新 更多