简体   繁体   中英

Entity Framework - Saving child entities

I'm using EF and WebAPI and trying to save a new object that has many child properties (eg city, country, etc). The data for these properties is pulled from an external source but I want to save them to my database along with my object for reporting purposes. For that reason I need to first check if the new child being passed along with the new parent already exists (remember, it's not being pulled from my database).

If I've selected, for example, a city I've already saved, it saves a duplicate row because the object passed in doesn't have MY city ID so EF thinks it's a new item.

I've tried attaching if it is already in the DB but it won't let me attach. It says the item is already being tracked by the context.

Here is my code for trying to check and save one of the child collections before saving the new parent.

foreach (HBCountry country in hbcampaign.HBTargetingSpec.HBCountries)
{
     if (db.HBCountries.Any(c => c.country_code == country.country_code))
     {
         country.CountryID = db.HBCountries.Where(c => c.country_code == country.country_code)
                                           .FirstOrDefault()
                                           .CountryID;
         db.HBCountries.Attach(country);
     }
     else
     {
         db.HBCountries.Add(country);
     }                 
}

I need to grab the ID of the existing entity from my context but after that I can't attach and let EF know that it doesn't need to create a new entry for this item.

I'm pretty new to EF and I'm constantly struggling with saving / updating any entities that have child collections. I can't seem to wrap my mind around when things are attached to the context and when not etc.

Don't try to set the Id, just create a new list with the existing country entities (and make as few queries as possible):

var newList = new List<HBCountry>();
foreach (HBCountry country in hbcampaign.HBTargetingSpec.HBCountries)
{
     var countryMatch = db.HBCountries.FirstOrDefault(c => c.country_code == country.country_code)
     if (countryMatch != null)
     {
         newList.Add(countryMatch);
     }
     else
     {
         newList.Add(country);
     }                 
}

hbcampaign.HBTargetingSpec.HBCountries = newList;

This is probably not the very best way to do this, but it's the first one I could think of.

Assuming a unique constraint on the country_code column doesn't fix the problem attaching the entity. I'd try this...

var countryCodes = hbcampaign.HBTargetingSpec.HBCountries.Select(c => country.country_code);
// Gives you the list of existing countries in one SQL query
var existingCountries = db.HBCountries.Where(c => 
    countryCodes.Contains(c.country_code);  

foreach (HBCountry country in existingCountries)
{
    var existingCountry = hbcampaign.HBTargetingSpec.HBCountries.FirstOrDefault(c => c.country_code == country.country_code);
    existingCountry.CountryID = country.CountryID;
    db.HBCountries.Detach(country);
    db.HBCountries.Attach(existingCountry);
}

var newCountries = hbcampaign.HBTargetingSpec.HBCountries.Where(c => !existingCountries.Any(c2 => c2.country_code == c.country_code);
foreach (HBCountry country in newCountries)
    db.HBCountries.Add(country);

Or even simpler, just assign the values on existingCountry from Country. In my little universe I'd code generate a solution for this using T4 templates...

http://msdn.microsoft.com/en-us/data/gg558520.aspx

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