title: Entity Framework Core 2.1 failling to update entities with relations
body: <p>I’m currently having issues with EF core 2.1 and a web api used by a native client to update an object which contains several levels of embedded objects.
  I’ve already read theses two topics:</p>
  
  <p><a href="https://stackoverflow.com/questions/50486669/entity-framework-core-fail-to-update-entity-with-nested-value-objects">Entity Framework Core: Fail to update Entity with nested value objects</a></p>
  
  <p><a href="https://docs.microsoft.com/en-us/ef/core/saving/disconnected-entities" rel="nofollow noreferrer">https://docs.microsoft.com/en-us/ef/core/saving/disconnected-entities</a></p>
  
  <p>I’ve learned through this that it is indeed not that obvious for now to update objects in EF Core 2. But I’ve not yet managed to find a solution that works. 
  On each attempt I’m having an exception telling me that a “step” is already tracked by EF.</p>
  
  <p>My model looks like this:</p>
  
  <pre><code>//CIApplication the root class I’m trying to update
  public class CIApplication : ConfigurationItem // -&gt; derive of BaseEntity which holds the ID and some other properties  
  {
  
      //Collection of DeploymentScenario
      public virtual ICollection&lt;DeploymentScenario&gt; DeploymentScenarios { get; set; }
  
      //Collection of SoftwareMeteringRules
      public virtual ICollection&lt;SoftwareMeteringRule&gt; SoftwareMeteringRules { get; set; }
  }
  </code></pre>
  
  <p>//Deployment Scenario which have a one to many relationship with Application. A deployment scenario contain two lists of steps</p>
  
  <pre><code>public class DeploymentScenario : BaseEntity
  {
  
      //Collection of substeps
      public virtual ICollection&lt;Step&gt; InstallSteps { get; set; }
      public virtual ICollection&lt;Step&gt; UninstallSteps { get; set; }
  
      //Navigation properties Parent CI
      public Guid? ParentCIID { get; set; }
      public virtual CIApplication ParentCI { get; set; }
  }
  </code></pre>
  
  <p>//Step, which is also quite complex and is also self-referencing</p>
  
  <pre><code>public class Step : BaseEntity
  {
  
      public string ScriptBlock { get; set; }
  
  
      //Parent Step Navigation property
      public Guid? ParentStepID { get; set; }
      public virtual Step ParentStep { get; set; }
  
      //Parent InstallDeploymentScenario Navigation property
      public Guid? ParentInstallDeploymentScenarioID { get; set; }
      public virtual DeploymentScenario ParentInstallDeploymentScenario { get; set; }
  
      //Parent InstallDeploymentScenario Navigation property
      public Guid? ParentUninstallDeploymentScenarioID { get; set; }
      public virtual DeploymentScenario ParentUninstallDeploymentScenario { get; set; }
  
      //Collection of sub steps
      public virtual ICollection&lt;Step&gt; SubSteps { get; set; }
  
      //Collection of input variables
      public virtual List&lt;ScriptVariable&gt; InputVariables { get; set; }
      //Collection of output variables
      public virtual List&lt;ScriptVariable&gt; OutPutVariables { get; set; }
  
  }
  </code></pre>
  
  <p>Here’s my update method, I know it’s ugly and it shouldn’t be in the controller but I’m changing it every two hours as I try to implement solutions if find on the web. 
  So this is the last iteration coming from 
  <a href="https://docs.microsoft.com/en-us/ef/core/saving/disconnected-entities" rel="nofollow noreferrer">https://docs.microsoft.com/en-us/ef/core/saving/disconnected-entities</a></p>
  
  <pre><code>public async Task&lt;IActionResult&gt; PutCIApplication([FromRoute] Guid id, [FromBody] CIApplication cIApplication)
      {
          _logger.LogWarning("Updating CIApplication " + cIApplication.Name);
  
          if (!ModelState.IsValid)
          {
              return BadRequest(ModelState);
          }
  
          if (id != cIApplication.ID)
          {
              return BadRequest();
          }
  
          var cIApplicationInDB = _context.CIApplications
              .Include(c =&gt; c.Translations)
              .Include(c =&gt; c.DeploymentScenarios).ThenInclude(d =&gt; d.InstallSteps).ThenInclude(s =&gt; s.SubSteps)
              .Include(c =&gt; c.DeploymentScenarios).ThenInclude(d =&gt; d.UninstallSteps).ThenInclude(s =&gt; s.SubSteps)
              .Include(c =&gt; c.SoftwareMeteringRules)
              .Include(c =&gt; c.Catalogs)
              .Include(c =&gt; c.Categories)
              .Include(c =&gt; c.OwnerCompany)
              .SingleOrDefault(c =&gt; c.ID == id);
  
          _context.Entry(cIApplicationInDB).CurrentValues.SetValues(cIApplication);
  
          foreach(var ds in cIApplication.DeploymentScenarios)
          {
              var existingDeploymentScenario = cIApplicationInDB.DeploymentScenarios.FirstOrDefault(d =&gt; d.ID == ds.ID);
  
              if (existingDeploymentScenario == null)
              {
                  cIApplicationInDB.DeploymentScenarios.Add(ds);
              }
              else
              {
                  _context.Entry(existingDeploymentScenario).CurrentValues.SetValues(ds);
  
                  foreach(var step in existingDeploymentScenario.InstallSteps)
                  {
                      var existingStep = existingDeploymentScenario.InstallSteps.FirstOrDefault(s =&gt; s.ID == step.ID);
  
                      if (existingStep == null)
                      {
                          existingDeploymentScenario.InstallSteps.Add(step);
                      }
                      else
                      {
                          _context.Entry(existingStep).CurrentValues.SetValues(step);
                      }
                  }
              }
          }
          foreach(var ds in cIApplicationInDB.DeploymentScenarios)
          {
              if(!cIApplication.DeploymentScenarios.Any(d =&gt; d.ID == ds.ID))
              {
                  _context.Remove(ds);
              }
          }
  
          //_context.Update(cIApplication);
          try
          {
              await _context.SaveChangesAsync();
          }
          catch (DbUpdateConcurrencyException e)
          {
              if (!CIApplicationExists(id))
              {
                  return NotFound();
              }
              else
              {
                  throw;
              }
          }
          catch(Exception e)
          {
          }
  
          return Ok(cIApplication);
      }
  </code></pre>
  
  <p>So far I’m getting this exception : 
  The instance of entity type 'Step' cannot be tracked because another instance with the key value '{ID: e29b3c1c-2e06-4c7b-b0cd-f8f1c5ccb7b6}' is already being tracked. </p>
  
  <p>I paid attention that no “get” operation was made previously by the client and even if it was the case I’ve put AsNoTracking on my get methods. 
  The only operation made before the update by the client is “ _context.CIApplications.Any(e => e.ID == id);” to ckeck if I should Add a new record or update an existing one.</p>
  
  <p>I’ve been fighting with this issue since few days so I would really appreciate if someone could help me getting in the right direction.
  Many thanks</p>
  
  <p><strong>UPDATE :</strong></p>
  
  <p>I added the following code in my controller :</p>
  
  <pre><code>var existingStep = existingDeploymentScenario.InstallSteps.FirstOrDefault(s =&gt; s.ID == step.ID);
                      entries = _context.ChangeTracker.Entries();
                      if (existingStep == null)
                      {
                          existingDeploymentScenario.InstallSteps.Add(step);
                          entries = _context.ChangeTracker.Entries();
                      }
  </code></pre>
  
  <p>The entries = _context.ChangeTracker.Entries(); line raise the "step is already tracked" exception right after adding the new deploymentScenario which contains the also new step.</p>
  
  <p>Just before it the new deploymentScenario and step are not in the tracker and I've check in DB their IDs are not duplicated.</p>
  
  <p>I also check my Post method and now it's failing too... I reverted it to the default methods with no fancy stuff Inside :</p>
  
  <pre><code>[HttpPost]
      public async Task&lt;IActionResult&gt; PostCIApplication([FromBody] CIApplication cIApplication)
      {
          if (!ModelState.IsValid)
          {
              return BadRequest(ModelState);
          }
          var entries = _context.ChangeTracker.Entries();
          _context.CIApplications.Add(cIApplication);
          entries = _context.ChangeTracker.Entries();
          await _context.SaveChangesAsync();
          entries = _context.ChangeTracker.Entries();
          return CreatedAtAction("GetCIApplication", new { id = cIApplication.ID }, cIApplication);
      }
  </code></pre>
  
  <p>Entries are empty at the beginning and the _context.CIApplications.Add(cIApplication); line is still raising the exception still about the only one step included in the deploymentscenario...</p>
  
  <p>So there obviously somthing wrong when I try to add stuff in my context, but right now I'm feeling totally lost. It may can help here how I declare my context in startup :</p>
  
  <pre><code>services.AddDbContext&lt;MyAppContext&gt;(options =&gt;
              options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
              b =&gt; b.MigrationsAssembly("DeployFactoryDataModel")),
              ServiceLifetime.Transient
              );
  </code></pre>
  
  <p>Add my context class :</p>
  
  <pre><code>public class MyAppContext : DbContext
  {
      private readonly IHttpContextAccessor _contextAccessor;
      public MyAppContext(DbContextOptions&lt;MyAppContext&gt; options, IHttpContextAccessor contextAccessor) : base(options)
      {
          _contextAccessor = contextAccessor;
      }
  
  
      protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
      {
  
          optionsBuilder.EnableSensitiveDataLogging();
      }
  
      public DbSet&lt;Step&gt; Steps { get; set; }
      //public DbSet&lt;Sequence&gt; Sequences { get; set; }
      public DbSet&lt;DeploymentScenario&gt; DeploymentScenarios { get; set; }
      public DbSet&lt;ConfigurationItem&gt; ConfigurationItems { get; set; }
      public DbSet&lt;CIApplication&gt; CIApplications { get; set; }
      public DbSet&lt;SoftwareMeteringRule&gt; SoftwareMeteringRules { get; set; }
      public DbSet&lt;Category&gt; Categories { get; set; }
      public DbSet&lt;ConfigurationItemCategory&gt; ConfigurationItemsCategories { get; set; }
      public DbSet&lt;Company&gt; Companies { get; set; }
      public DbSet&lt;User&gt; Users { get; set; }
      public DbSet&lt;Group&gt; Groups { get; set; }
      public DbSet&lt;Catalog&gt; Catalogs { get; set; }
      public DbSet&lt;CIDriver&gt; CIDrivers { get; set; }
      public DbSet&lt;DriverCompatiblityEntry&gt; DriverCompatiblityEntries { get; set; }
      public DbSet&lt;ScriptVariable&gt; ScriptVariables { get; set; }
  
      protected override void OnModelCreating(ModelBuilder modelBuilder)
      {
          //Step one to many with step for sub steps
          modelBuilder.Entity&lt;Step&gt;().HasMany(s =&gt; s.SubSteps).WithOne(s =&gt; s.ParentStep).HasForeignKey(s =&gt; s.ParentStepID);
  
          //Step one to many with step for variables
          modelBuilder.Entity&lt;Step&gt;().HasMany(s =&gt; s.InputVariables).WithOne(s =&gt; s.ParentInputStep).HasForeignKey(s =&gt; s.ParentInputStepID);
          modelBuilder.Entity&lt;Step&gt;().HasMany(s =&gt; s.OutPutVariables).WithOne(s =&gt; s.ParentOutputStep).HasForeignKey(s =&gt; s.ParentOutputStepID);
  
          //Step one to many with sequence
          //modelBuilder.Entity&lt;Step&gt;().HasOne(step =&gt; step.ParentSequence).WithMany(seq =&gt; seq.Steps).HasForeignKey(step =&gt; step.ParentSequenceID).OnDelete(DeleteBehavior.Cascade);
  
          //DeploymentScenario One to many with install steps
          modelBuilder.Entity&lt;DeploymentScenario&gt;().HasMany(d =&gt; d.InstallSteps).WithOne(s =&gt; s.ParentInstallDeploymentScenario).HasForeignKey(s =&gt; s.ParentInstallDeploymentScenarioID);
  
          //DeploymentScenario One to many with uninstall steps
          modelBuilder.Entity&lt;DeploymentScenario&gt;().HasMany(d =&gt; d.UninstallSteps).WithOne(s =&gt; s.ParentUninstallDeploymentScenario).HasForeignKey(s =&gt; s.ParentUninstallDeploymentScenarioID);
  
          //DeploymentScenario one to one with sequences
          //modelBuilder.Entity&lt;DeploymentScenario&gt;().HasOne(ds =&gt; ds.InstallSequence).WithOne(seq =&gt; seq.IDeploymentScenario).HasForeignKey&lt;DeploymentScenario&gt;(ds =&gt; ds.InstallSequenceID).OnDelete(DeleteBehavior.Cascade);
          //modelBuilder.Entity&lt;DeploymentScenario&gt;().HasOne(ds =&gt; ds.UninstallSequence).WithOne(seq =&gt; seq.UDeploymentScenario).HasForeignKey&lt;DeploymentScenario&gt;(ds =&gt; ds.UninstallSequenceID);
  
          //Step MUI config
          modelBuilder.Entity&lt;Step&gt;().Ignore(s =&gt; s.Description);
          modelBuilder.Entity&lt;Step&gt;().HasMany(s =&gt; s.Translations).WithOne().HasForeignKey(x =&gt; x.StepTranslationId);
  
          //Sequence MUI config
          //modelBuilder.Entity&lt;Sequence&gt;().Ignore(s =&gt; s.Description);
          //modelBuilder.Entity&lt;Sequence&gt;().HasMany(s =&gt; s.Translations).WithOne().HasForeignKey(x =&gt; x.SequenceTranslationId);
  
          //DeploymentScenario MUI config
          modelBuilder.Entity&lt;DeploymentScenario&gt;().Ignore(s =&gt; s.Name);
          modelBuilder.Entity&lt;DeploymentScenario&gt;().Ignore(s =&gt; s.Description);
          modelBuilder.Entity&lt;DeploymentScenario&gt;().HasMany(s =&gt; s.Translations).WithOne().HasForeignKey(x =&gt; x.DeploymentScenarioTranslationId);
  
          //CIApplication  relations
          //CIApplication one to many relation with Deployment Scenario
          modelBuilder.Entity&lt;CIApplication&gt;().HasMany(ci =&gt; ci.DeploymentScenarios).WithOne(d =&gt; d.ParentCI).HasForeignKey(d =&gt; d.ParentCIID).OnDelete(DeleteBehavior.Cascade);
          modelBuilder.Entity&lt;CIApplication&gt;().HasMany(ci =&gt; ci.SoftwareMeteringRules).WithOne(d =&gt; d.ParentCI).HasForeignKey(d =&gt; d.ParentCIID).OnDelete(DeleteBehavior.Cascade);
  
          // CIDriver relations
          // CIAPpplication one to many relation with DriverCompatibilityEntry
          modelBuilder.Entity&lt;CIDriver&gt;().HasMany(ci =&gt; ci.CompatibilityList).WithOne(c =&gt; c.ParentCI).HasForeignKey(c =&gt; c.ParentCIID).OnDelete(DeleteBehavior.Restrict);
  
          //ConfigurationItem MUI config
          modelBuilder.Entity&lt;ConfigurationItem&gt;().Ignore(s =&gt; s.Name);
          modelBuilder.Entity&lt;ConfigurationItem&gt;().Ignore(s =&gt; s.Description);
          modelBuilder.Entity&lt;ConfigurationItem&gt;().HasMany(s =&gt; s.Translations).WithOne().HasForeignKey(x =&gt; x.ConfigurationItemTranslationId);
  
          //category MUI config
          modelBuilder.Entity&lt;Category&gt;().Ignore(s =&gt; s.Name);
          modelBuilder.Entity&lt;Category&gt;().Ignore(s =&gt; s.Description);
          modelBuilder.Entity&lt;Category&gt;().HasMany(s =&gt; s.Translations).WithOne().HasForeignKey(x =&gt; x.CategoryTranslationId);
  
          //CI Categories Many to Many
          modelBuilder.Entity&lt;ConfigurationItemCategory&gt;().HasKey(cc =&gt; new { cc.CategoryId, cc.CIId });
          modelBuilder.Entity&lt;ConfigurationItemCategory&gt;().HasOne(cc =&gt; cc.Category).WithMany(cat =&gt; cat.ConfigurationItems).HasForeignKey(cc =&gt; cc.CategoryId);
          modelBuilder.Entity&lt;ConfigurationItemCategory&gt;().HasOne(cc =&gt; cc.ConfigurationItem).WithMany(ci =&gt; ci.Categories).HasForeignKey(cc =&gt; cc.CIId);
  
          //CI Catalog Many to Many
          modelBuilder.Entity&lt;CICatalog&gt;().HasKey(cc =&gt; new { cc.CatalogId, cc.ConfigurationItemId });
          modelBuilder.Entity&lt;CICatalog&gt;().HasOne(cc =&gt; cc.Catalog).WithMany(cat =&gt; cat.CIs).HasForeignKey(cc =&gt; cc.CatalogId);
          modelBuilder.Entity&lt;CICatalog&gt;().HasOne(cc =&gt; cc.ConfigurationItem).WithMany(ci =&gt; ci.Catalogs).HasForeignKey(cc =&gt; cc.ConfigurationItemId);
  
          //Company Customers Many to Many
          modelBuilder.Entity&lt;CompanyCustomers&gt;().HasKey(cc =&gt; new { cc.CustomerId, cc.ProviderId });
          modelBuilder.Entity&lt;CompanyCustomers&gt;().HasOne(cc =&gt; cc.Provider).WithMany(p =&gt; p.Customers).HasForeignKey(cc =&gt; cc.ProviderId).OnDelete(DeleteBehavior.Restrict);
          modelBuilder.Entity&lt;CompanyCustomers&gt;().HasOne(cc =&gt; cc.Customer).WithMany(c =&gt; c.Providers).HasForeignKey(cc =&gt; cc.CustomerId);
  
          //Company Catalog Many to Many
          modelBuilder.Entity&lt;CompanyCatalog&gt;().HasKey(cc =&gt; new { cc.CatalogId, cc.CompanyId });
          modelBuilder.Entity&lt;CompanyCatalog&gt;().HasOne(cc =&gt; cc.Catalog).WithMany(c =&gt; c.Companies).HasForeignKey(cc =&gt; cc.CatalogId);
          modelBuilder.Entity&lt;CompanyCatalog&gt;().HasOne(cc =&gt; cc.Company).WithMany(c =&gt; c.Catalogs).HasForeignKey(cc =&gt; cc.CompanyId);
  
          //Author Catalog Many to Many
          modelBuilder.Entity&lt;CatalogAuthors&gt;().HasKey(ca =&gt; new { ca.AuthorId, ca.CatalogId });
          modelBuilder.Entity&lt;CatalogAuthors&gt;().HasOne(ca =&gt; ca.Catalog).WithMany(c =&gt; c.Authors).HasForeignKey(ca =&gt; ca.CatalogId);
          modelBuilder.Entity&lt;CatalogAuthors&gt;().HasOne(ca =&gt; ca.Author).WithMany(a =&gt; a.AuthoringCatalogs).HasForeignKey(ca =&gt; ca.AuthorId);
  
          //Company one to many with owned Catalog
          modelBuilder.Entity&lt;Company&gt;().HasMany(c =&gt; c.OwnedCatalogs).WithOne(c =&gt; c.OwnerCompany).HasForeignKey(c =&gt; c.OwnerCompanyID).OnDelete(DeleteBehavior.Restrict);
          //Company one to many with owned Categories
          modelBuilder.Entity&lt;Company&gt;().HasMany(c =&gt; c.OwnedCategories).WithOne(c =&gt; c.OwnerCompany).HasForeignKey(c =&gt; c.OwnerCompanyID).OnDelete(DeleteBehavior.Restrict);
          //Company one to many with owned CIs
          modelBuilder.Entity&lt;Company&gt;().HasMany(c =&gt; c.OwnedCIs).WithOne(c =&gt; c.OwnerCompany).HasForeignKey(c =&gt; c.OwnerCompanyID).OnDelete(DeleteBehavior.Restrict);
  
          //CIDriver one to many with DriverCompatibilityEntry
          modelBuilder.Entity&lt;CIDriver&gt;().HasMany(c =&gt; c.CompatibilityList).WithOne(c =&gt; c.ParentCI).HasForeignKey(c =&gt; c.ParentCIID).OnDelete(DeleteBehavior.Restrict);
  
          //User Group Many to Many
          modelBuilder.Entity&lt;UserGroup&gt;().HasKey(ug =&gt; new { ug.UserId, ug.GroupId });
          modelBuilder.Entity&lt;UserGroup&gt;().HasOne(cg =&gt; cg.User).WithMany(ci =&gt; ci.Groups).HasForeignKey(cg =&gt; cg.UserId);
          modelBuilder.Entity&lt;UserGroup&gt;().HasOne(cg =&gt; cg.Group).WithMany(ci =&gt; ci.Users).HasForeignKey(cg =&gt; cg.GroupId);
  
          //User one to many with Company
          modelBuilder.Entity&lt;Company&gt;().HasMany(c =&gt; c.Employees).WithOne(u =&gt; u.Employer).HasForeignKey(u =&gt; u.EmployerID).OnDelete(DeleteBehavior.Restrict);
      }
  </code></pre>
  
  <p><strong>UPDATE 2</strong></p>
  
  <p>Here's a one drive link to a minima repro example. I haven't implemented PUT in the client as the post method already reproduce the issue.</p>
  
  <p><a href="https://1drv.ms/u/s!AsO87EeN0Fnsk7dDRY3CJeeLT-4Vag" rel="nofollow noreferrer">https://1drv.ms/u/s!AsO87EeN0Fnsk7dDRY3CJeeLT-4Vag</a></p>