Question

I have a complex type called account, which contains a list of licenses. Licenses in turn contains a list of domains (a domain is a simple id + url string).

In my repository I have this code

    public void SaveLicense(int accountId, License item)
    {

        Account account = GetById(accountId);
        if (account == null)
        {
            return;
        }


        if (item.Id == 0)
        {
            account.Licenses.Add(item);
        }
        else
        {
            ActiveContext.Entry(item).State = EntityState.Modified;
        }


        ActiveContext.SaveChanges();
    }

When I try to save an updated License (with modified domains) what happens is that strings belonging straight to the license get updated just fine.

However no domains get updated.


I should mention that what I have done is allow the user to add and remove domains in the user interface. Any new domains get id=0 and any deleted domains are simply not in the list.

so what I want is

  1. Any domains that are in the list and database and NOT changed - nothing happens
  2. Any domains that are in the list and database, but changed in the list - database gets updated
  3. Any domains with id=0 should be inserted (added) into database
  4. Any domains NOT in the list but that are in the database should be removed

I have played a bit with it with no success but I have a sneaky suspicion that I am doing something wrong in the bigger picture so I would love tips on if I am misunderstanding something design-wise or simply just missed something.

Was it helpful?

Solution

Unfortunately updating object graphs - entities with other related entities - is a rather difficult task and there is no very sophisticated support from Entity Framework to make it easy.

The problem is that setting the state of an entity to Modified (or generally to any other state) only influences the entity that you pass into DbContext.Entry and only its scalar properties. It has no effect on its navigation properties and related entities.

You must handle this object graph update manually by loading the entity that is currently stored in the database including the related entities and by merging all changes you have done in the UI into that original graph. Your else case could then look like this:

//...
else
{
    var licenseInDb = ActiveContext.Licenses.Include(l => l.Domains)
        .SingleOrDefault(l => l.Id == item.Id)

    if (licenseInDb != null)
    {
        // Update the license (only its scalar properties)
        ActiveContext.Entry(licenseInDb).CurrentValus.SetValues(item);

        // Delete domains from DB that have been deleted in UI
        foreach (var domainInDb in licenseInDb.Domains.ToList())
            if (!item.Domains.Any(d => d.Id == domainInDb.Id))
                ActiveContext.Domains.Remove(domainInDb);

        foreach (var domain in item.Domains)
        {
            var domainInDb = licenseInDb.Domains
                .SingleOrDefault(d => d.Id == domain.Id);
            if (domainInDb != null)
                // Update existing domains
                ActiveContext.Entry(domainInDb).CurrentValus.SetValues(domain);
            else
                // Insert new domains
                licenseInDb.Domains.Add(domain);
        }
    }
}
ActiveContext.SaveChanges();
//...

You can also try out this project called "GraphDiff" which intends to do this work in a generic way for arbitrary detached object graphs.

The alternative is to track all changes in some custom fields in the UI layer and then evaluate the tracked state changes when the data get posted back to set the appropriate entity states. Because you are in a web application it basically means that you have to track changes in the browser (most likely requiring some Javascript) while the user changes values, adds new items or deletes items. In my opinion this solution is even more difficult to implement.

OTHER TIPS

This should be enough to do what you are looking to do. Let me know if you have more questions about the code.

public void SaveLicense(License item)
{        
    if (account == null)
    {
        context.Licenses.Add(item);
    }

    else if (item.Id > 0)
           {

               var currentItem = context.Licenses                        
                   .Single(t => t.Id == item.Id);

               context.Entry(currentItem ).CurrentValues.SetValues(item);                    
            }

    ActiveContext.SaveChanges();
 }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top