简体   繁体   中英

SqlException: Cannot insert explicit value for identity column in table [Table name] when IDENTITY_INSERT is set to OFF

I am creating a website for expense tracking and when I send a new POST request calling AddNew method to add a new Entry I get this error:

SqlException: Cannot insert explicit value for identity column in table 'Accounts' when IDENTITY_INSERT is set to OFF. Cannot insert explicit value for identity column in table 'Categories' when IDENTITY_INSERT is set to 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();
    }
}

Here is my postman request:

邮递员请求

Account with ID == 1, and Category with ID == 1 both exist already in the respective databases.

Also, when I am inserting values to Account database, whose method looks exactly the same as AddNew for entries, it works without a problem.

Does anyone know what is this about?

The issue is that you are passing entities with relations back to the server. Each request is served by a different DB Context so while your Entry with it's associated Account and Category look like entities, they are not. They are deserialized POCO objects that the DbContext knows nothing about. When you pass an entity referencing other entities that already exist in the database, the receiving context does not know about these entities, so it interprets them as new records and tries to insert them.

My default recommendation is to never pass entities between client and server. This leads to serialization errors & performance issues from server to client, and exposes your system to tampering, plus issues like this with insert errors or duplication when used from client to server.

To solve your issue without too much of a change:

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();
}

This associates the entry account and category with instances known by the context.

An alternative would be to attach the account and category passed back to the DbContext. This would also work, however it can make your system vulnerable to tampering. The data passed back to the controller can be intercepted by debugging tools or sniffers on a client machine where the data in the message can be altered to change details in records and their related records in ways you don't intend. If for any reason any code ends up setting those re-attached entities to a Modified state, those alterations would be persisted to the database on SaveChanges. Attaching entities can also be a nuisance when dealing with multiple detached references. For instance if you are saving multiple records at a time with relatives. Attaching each will work fine when all relatives are unique, but without the extra logic to check each one to see if there is already an attached reference, you can get errors when a second reference to an already attached (or loaded) entity attach is attempted. This means more code to check the local association and replace it if found.

I just changed Entry model from

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; }
}

To this:

 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; }
    }

And everything seems to work fine now. C# does its magic and picks AccountId and CategoryId as foreign keys.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM