简体   繁体   中英

EF-Core trying to insert relational data that already exists

I am trying to add an object that has references to other objects in it to my database using EF-Core. These are my two models:

    public class Contact : Entity
    {
        #nullable enable
        public string? Name1 { get; set; }
        public string? Name2 { get; set; }
        public Company? Company { get; set; }
        public string? Salutation { get; set; }
        public string? Title { get; set; }
        public DateTime? Birthday { get; set; }
        public string? Address { get; set; }
        public string? Postcode { get; set; }
        public string? City { get; set; }
        public string? Country { get; set; }
        public string? Mail { get; set; }
        public string? MailSecond { get; set; }
        public string? PhoneFixed { get; set; }
        public string? PhoneFixedSecond { get; set; }
        public string? PhoneMobile { get; set; }
        public string? Fax { get; set; }
        public string? Url { get; set; }
        public string? SkypeName { get; set; }
        public bool? IsLead { get; set; }
        public string? Remarks { get; set; }
    }

    public class Company : Entity
    {
        #nullable enable
        public string? Name { get; set; }
        public List<Contact>? Contacts { get; set; }
        public string? Address { get; set; }
        public string? Postcode { get; set; }
        public string? City { get; set; }
        public string? Country { get; set; }
        public string? Url { get; set; }
        public string? Remarks { get; set; }
        public string? UpdatedAt { get; set; }
    }

    public class Entity
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; set; }
        public DateTime? CreatedAt { get; set; }
        public DateTime? LastEdited { get; set; }
        public string? BexioId { get; set; }
    }

This is my DbContext:

    public class DatabaseContext : DbContext
    {
        public DatabaseContext(DbContextOptions<DatabaseContext> options)
            : base(options)
        {
        }
        public DbSet<Company> Companies { get; set; }
        public DbSet<Contact> Contacts { get; set; }
        
        protected override void OnModelCreating(ModelBuilder modelbuilder)
        {
            modelbuilder.Entity<Contact>().ToTable("Contacts"):
            modelbuilder.Entity<Company>().ToTable("Companies");
        }
    }

When trying to insert data using the following code, I get the following error:

        public object Add(string values)
        {
            var newContact = new Models.Contact();
            JsonConvert.PopulateObject(values, newContact);

            if (!TryValidateModel(newContact))
                return BadRequest(ModelState);

            _context.Contacts.Add(newContact);
            _context.SaveChanges();

            return Ok();
        }
Microsoft.EntityFrameworkCore.DbUpdateException
  HResult=0x80131500
  Message=An error occurred while updating the entries. See the inner exception for details.
  Source=Microsoft.EntityFrameworkCore.Relational
  StackTrace:
   at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.Execute(IRelationalConnection connection)
   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.Execute(IEnumerable`1 commandBatches, IRelationalConnection connection)
   at Microsoft.EntityFrameworkCore.Storage.RelationalDatabase.SaveChanges(IList`1 entries)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(IList`1 entriesToSave)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(DbContext _, Boolean acceptAllChangesOnSuccess)
   at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChanges()
   at microtom.portal.Controllers.ContactController.Add(String values) in C:\Users\tpeduzzi\Documents\microtom\microtom.portal\Controllers\ContactController.cs:line 38
   at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<<InvokeActionMethodAsync>g__Logged|12_1>d.MoveNext()

  This exception was originally thrown at this call stack:
    [External Code]

Inner Exception 1:
SqlException: Cannot insert explicit value for identity column in table 'Companies' when IDENTITY_INSERT is set to OFF.

I don't exactly know what is going on. I believe that the problem is that EF-Core is trying to insert the object in the Company field of the contact object, even though the data is already in the database and the Id field of that company has already been filled out by the database. Can anybody tell me what I am doing wrong? I've been trying to fix this problem for a few days and I am wuite desperate... I am doing exactly what is being told to me online and in person. Thanks in advance!

Call _context.Attach(newContact.Company) before _context.Add .

EF uses concept of tracking to see the changes and propagate them to database. In you current code newContact.Company is not null and it is added to current context as part of newContact object graph. Since context does not know anything about this Company it will consider it as a new one and try to save it into the database. DbContext.Attach begins tracking the given entity and entries reachable from the given entity using the Unchanged state by default so it will consider it as existing (and unchanged) one and will not try to save it database.

You are deserializing an entity graph from JSON. Contact will likely have a Company reference in your JSON which already exists in your database. EF doesn't know whether this company exists or not so it will treat it as a brand new entity.

Based on the exception your Company table has an Identity set for it's PK however your EF Entity's PK is not set as DatabaseGenerated.Identity. You'd still have a problem if it was, but the issue then would be it would insert essentially a duplicate record with a new PK.

Guru's recommendation above is correct, though to be safe in all cases you should check the DbContext local cache for all references and re-associate them if found, attaching them if not found.

JsonConvert.PopulateObject(values, newContact);

if (!TryValidateModel(newContact))
    return BadRequest(ModelState);

var existingCompany = _context.Companies.Local.SingleOrDefault(x => x.CompanyId == newContact.CompanyId);
if (existingCompany != null)
    newContact.Company = existingCompany;
else
    _context.Attach(newContact.Comany);

_context.Contacts.Add(newContact);

This needs to be applied to all references included with an entity being deserialized. (and any references of those references) 99% of the time the local check doesn't result in anything, but there will be situations that it does, such as when you can encounter updating a collection of entities within a call where some of those entities refer to the same reference. Deserializing two objects with the same ID will create two references to the same data. Attaching the first one will succeed, but attaching the second will fail due to the context now tracking the first.

Working with detached and/or deserialized entities can easily lead to seemingly intermittent or situational exceptions. Walk carefully. :)

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