title: Entity Framework Core 2.1. Proper way to update an object with a lot of sub objects
body: <p>I'm developing an ASP.NET Core + EF Core application.</p>
  
  <p>I'm struggling a bit (a lot actually) with the update method of the controller which manage the most complex object of my project.</p>
  
  <p>The object looks something like this :</p>
  
  <pre><code>public class CIApplication : ConfigurationItem
  {
      public String InventoryCollectionQuery { get; set; }
      public EnumCCMCIType CCMCIType { get; set; }
  
      //Collection of DeploymentScenario
      public virtual ICollection&lt;DeploymentScenario&gt; DeploymentScenarios { get; set; }
  
      //Collection of SoftwareMeteringRules
      public virtual ICollection&lt;SoftwareMeteringRule&gt; SoftwareMeteringRules { get; set; }
  
      //Collection of AppLocalPresenceCondition
      public virtual ICollection&lt;AppLocalPresenceCondition&gt; AppLocalPresenceConditions { get; set; }
  }
  </code></pre>
  
  <p>And some of the objects in collections can also contains other collections and so, on and so on...</p>
  
  <p>Actually my update methods works (which is below) but is a real pain to maintain when I do some evolutions on my model. As I developed it quite a while ago I'm wondering if there's a better approach to do this. And by better a mean a lot simpler for me :) sometimes I even wonder if it wouldn't be better to delete the object and recreate it.</p>
  
  <pre><code> var cIApplicationInDB = _context.CIApplications
              .Include(c =&gt; c.Translations)
              .Include(c =&gt; c.DeploymentScenarios).ThenInclude(d =&gt; d.InstallSteps)
              .Include(c =&gt; c.DeploymentScenarios).ThenInclude(d =&gt; d.InstallSteps).ThenInclude(s =&gt; s.Translations)
              .Include(c =&gt; c.DeploymentScenarios).ThenInclude(d =&gt; d.InstallSteps).ThenInclude(s =&gt; s.InputVariables).ThenInclude(x =&gt; x.Translations)
              .Include(c =&gt; c.DeploymentScenarios).ThenInclude(d =&gt; d.InstallSteps).ThenInclude(s =&gt; s.OutPutVariables).ThenInclude(x =&gt; x.Translations)
              .Include(c =&gt; c.DeploymentScenarios).ThenInclude(d =&gt; d.UninstallSteps)
              .Include(c =&gt; c.DeploymentScenarios).ThenInclude(d =&gt; d.UninstallSteps).ThenInclude(s =&gt; s.Translations)
              .Include(c =&gt; c.DeploymentScenarios).ThenInclude(d =&gt; d.UninstallSteps).ThenInclude(s =&gt; s.InputVariables).ThenInclude(x =&gt; x.Translations)
              .Include(c =&gt; c.DeploymentScenarios).ThenInclude(d =&gt; d.UninstallSteps).ThenInclude(s =&gt; s.OutPutVariables).ThenInclude(x =&gt; x.Translations)
              .Include(c =&gt; c.DeploymentScenarios).ThenInclude(d =&gt; d.Translations)
              .Include(c =&gt; c.DeploymentScenarios).ThenInclude(d =&gt; d.DetectionMethods)
              .Include(c =&gt; c.DeploymentScenarios).ThenInclude(d =&gt; d.InstallationBehaviorRules)
              .Include(c =&gt; c.SoftwareMeteringRules)
              .Include(c =&gt; c.Catalogs)
              .Include(c =&gt; c.Categories)
              .Include(c =&gt; c.OwnerCompany)
              .Include(c =&gt; c.AppLocalPresenceConditions)
              .SingleOrDefault(c =&gt; c.ID == id);
  
  _context.Entry(cIApplicationInDB).CurrentValues.SetValues(cIApplication);
  
  // Updating Translations
  foreach (var upTodateTranslation in cIApplication.Translations)
  {
      var existingTranslation = cIApplicationInDB.Translations.FirstOrDefault(t =&gt; t.Id == upTodateTranslation.Id);
  
      if (existingTranslation == null) 
      {
           cIApplicationInDB.Translations.Add(upTodateTranslation); 
      }
      else 
      {
           _context.Entry(existingTranslation).CurrentValues.SetValues(upTodateTranslation);    
      }
  }
  
  foreach (var ExistingTranslation in cIApplicationInDB.Translations)
  {
      if (!cIApplication.Translations.Any(t =&gt; t.Id == ExistingTranslation.Id)) 
      {
           _context.Remove(ExistingTranslation); 
      }
  }
  
  // Updating SoftwareMeteringRules
  foreach (var upTodateSoftwareMeteringRule in cIApplication.SoftwareMeteringRules)
  {
      var existingSoftwareMeteringRule = cIApplicationInDB.SoftwareMeteringRules.FirstOrDefault(t =&gt; t.ID == upTodateSoftwareMeteringRule.ID);
  
      if (existingSoftwareMeteringRule == null) 
      {
            cIApplicationInDB.SoftwareMeteringRules.Add(upTodateSoftwareMeteringRule);
      }
      else 
      { 
           _context.Entry(existingSoftwareMeteringRule).CurrentValues.SetValues(upTodateSoftwareMeteringRule); 
      }
  }
  
  foreach (var existingSoftwareMeteringRule in cIApplicationInDB.SoftwareMeteringRules)
  {
      if (!cIApplication.SoftwareMeteringRules.Any(t =&gt; t.ID == existingSoftwareMeteringRule.ID)) 
      {
           _context.Remove(existingSoftwareMeteringRule); 
      }
  }
  
  // Updating DeploymentScenarios Graph
  foreach (var upToDateDS in cIApplication.DeploymentScenarios)
  {
      // Look for incoming DS into the actual db CI
      var existingDeploymentScenario = cIApplicationInDB.DeploymentScenarios.FirstOrDefault(d =&gt; d.ID == upToDateDS.ID);
  
      // can't find DS in existing CI, it is new
      if (existingDeploymentScenario == null)
      {
          // cIApplicationInDB.DeploymentScenarios.
          cIApplicationInDB.DeploymentScenarios.Add(upToDateDS);
      }
      // Look if there are things to update in existing DS
      else
      {
           _context.Entry(existingDeploymentScenario).CurrentValues.SetValues(upToDateDS);
  
           // update DeploymentScenario translations
          ....
          ...doing the same way for all objects
  
          // and then after going through all sub collections :
          try
          {
              await _context.SaveChangesAsync();
          }
          catch (DbUpdateConcurrencyException e)
          {
              if (!CIApplicationExists(id))
              {
                  return NotFound();
              }
              else
              {
              }
          }
          catch (Exception e)
          {
              throw new Exception(e.Message);
          }
      }
  }
  </code></pre>