4

(Using Entity Framework 6.2)

I have the following two models/entities:

public class City
    {
        public int CityId { get; set; }
        public string Name { get; set; }
    }

public class Country
    {
        public Country()
        {
            Cities new HashSet<City>();
        }

        public int CountryId { get; set; }
        public string Name { get; set; }
        public virtual ICollection<City> Cities { get; set; }
    }   

And the following DbContext

public DbSet<Country> Countries { get; set; }

My question is: If the children of the Country object change (i.e. the Cities), how do I update this?

Can I do this:

List<City> cities = new List<City>();
// Add a couple of cities to the list...
Country country = dbContext.Countries.FirstOrDefault(c => c.CountryId == 123);
if (country != null)
{
    country.Cities.Clear();
    country.Cities = cities;
    dbContext.SaveChanges();
}

Would that work? Or should I specifically add each city? i.e.:

List<City> cities = new List<City>();
// Add a couple of cities to the list...
Country country = dbContext.Countries.FirstOrDefault(c => c.CountryId == 123);
if (country != null)
{
    country.Cities.Clear();
    foreach (City city in cities)
        country.Cities.Add(city);
    dbContext.SaveChanges();
}  
Fabricio Rodriguez
  • 3,241
  • 7
  • 40
  • 85
  • `dbContext.Countries.Cities.Clear();` will definitely not work since `Countries` is a `DbSet` which does not have a `Cities` property ;) – Balázs Feb 23 '17 at 09:37
  • Hehe, thanks for the reply Balazs. Not sure if one of us is confused, but doesn't the DbSet indeed have a Cities property: **public virtual ICollection Cities { get; set; }** – Fabricio Rodriguez Feb 23 '17 at 09:39
  • No, not the `DbSet` but an actual entity, that is, a `Country` **instance** is what contains it. – Balázs Feb 23 '17 at 09:41
  • Possible duplicate: http://stackoverflow.com/q/27176014/861716 – Gert Arnold Feb 23 '17 at 09:45
  • @user1900799 Are you getting any error with your code or is it simply not adding cities to country ? – Venky Feb 23 '17 at 10:11
  • @user1900799 i don't think it's a good practice to create `cities` collection in the `udpate` method. I would suggest, passing `citites` as part of `country` object which is being updated. In that case `dbcontext` keeps track of all the child entities . My solution below might work. – Venky Feb 23 '17 at 10:21
  • @user1900799 did either `country.Cities = cities;` or `foreach (City city in cities) country.Cities.Add(city);` work? I think you're looking for `AddRange()` as in `countries.Cities.AddRange(cities)`, but I'm unsure if that is what you're asking. – kimbaudi Feb 23 '17 at 10:50
  • AddRange() would be great, but unfortunately there is only an Add() method available... – Fabricio Rodriguez Feb 24 '17 at 06:55

1 Answers1

6

You need to add Cities to that particular Country object which is being updated.

public Country Update(Country country)
{
    using (var dbContext =new DbContext())
    {
        var countryToUpdate = dbContext.Countries.SingleOrDefault(c => c.Id == country.Id);
        countryToUpdate.Cities.Clear();
        foreach (var city in country.Cities)
        {
            var existingCity =
                dbContext.Cities.SingleOrDefault(
                    t => t.Id.Equals(city.cityId)) ??
                dbContext.Cities.Add(new City
                {
                    Id = city.Id,
                    Name=city.Name
                });

            countryToUpdate.Cities.Add(existingCity);
        }
        dbContext.SaveChanges();
        return countryToUpdate;
    }
}

Update :

  public class City
    {
        public int CityId { get; set; }
        public string Name { get; set; }

        [ForeignKey("Country")]
        public int CountryId {get;set;}
        public virtual Country Country {get; set;}
    } 

Hope it helps.

Venky
  • 4,348
  • 4
  • 42
  • 83
  • Thanks for the reply Venky. I actually had made a mistake in my original post, as pointed out by Balázs. I've updated the post - if you look at my second proposed solution (the one with the loop), would that also not work? – Fabricio Rodriguez Feb 23 '17 at 09:56
  • I think First you need to add those cities to `dbcontext` like `dbcontext.cities.add(city)` and then you should be able to add them to `countries` just like you did. – Venky Feb 23 '17 at 10:00
  • @Venky no, not really, since EF is able to discover changes in associated entities, you do not need to both load it via context and update in the parent collection. In fact, you don't even need to clear the parent's collection. Clearing it only makes sense if you indeed want to actually delete all the entities in the collection and add all new ones. – Balázs Feb 23 '17 at 10:03
  • @Balázs thanks for clarification, But cities are newly added . So i don't think dbcontext will now about these changes. I think atleast we should mark the country entity as modified by `context.Entry(country).State = EntityState.Modified` before savechanges. – Venky Feb 23 '17 at 10:05
  • @Venky I know it seems like magic for the first time but in fact, you don't even need to do that. This is because of EF internal workings, but to put it simple, think of it like this: whenever you load an entity with EF, EF will add it to a change tracker. Any change made to the entity will cause EF to register it as dirty and so it will properly (*magically*) update it when you call `SaveChanges`. Because the child entites are associated with the same context, they will be tracked too. – Balázs Feb 23 '17 at 10:09
  • A brief intro to how this works: EF does not actually return the object types you define (`Country`, for example), but something they call proxy objects. This is actually what makes it possible to lazily load related entities. What EF does is it generates a derived class during runtime and overrides the property getters and setters so that any modification to the entity will cause EF to register it as dirty. You can see it in action during debugging: if you hover over an entity object its type will be something like `YourEntityType_crypticStuff`. – Balázs Feb 23 '17 at 10:12
  • @Balázs . I totally agree with your point above. My only concern was with `Cities`. These are newly created collection, not attached to `dbcontext` so while updating `country` , `dbcontext` will not have any reference to those `cities`. So it wont't add them to `countries.` So i think `cities` needs to be attached to `dbcontext`. – Venky Feb 23 '17 at 10:16
  • Thanks for your input guys,,,I was going to ask the same question as Venky - I don't have a DbSet - I only have a DbSet. So either I'd have to create a DbSet, or rather add the cities to the Country object, and not directly to DbSet – Fabricio Rodriguez Feb 24 '17 at 06:58
  • @user1900799 i think you have made a mistake in designing your data model. Anyways i updated my answer. Not sure it helps. give it a try. – Venky Feb 24 '17 at 10:07
  • Got it! Thanks everyone – Fabricio Rodriguez Feb 24 '17 at 10:23