Johny
Johny

Reputation: 71

EF6: Adding an entity with many-to-many and the main entity's children with many-to-many

Basically I'm trying to add a new entity (Person) with its children (Addresses); and both of these entities do have many to many relationships. I'm using Entity Framework 6.1.3.

Attaching an entity of type 'Feature' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.

But I'm getting the following error even though I think that I'm using the proper Attach method to indicate that both Hobbies and Features already exist in the database.

It seems like if I try to attach only one featureSet instead of 2, then it works fine. But the problem seems to be when the same Feature is selected for 2 addresses. I've even tried to attach only the distinct feature, but then a different error comes. Then the code tries to ADD a NEW feature which I don't want.

Here is my code that I'm working with:

public class Person
{
    public int PersonId { get; set; }
    public string Personname { get; set }
    public ICollection<Hobby> Hobbies { get; set; } // Many to Many with Hobbies
    public ICollection<Address> Addresses { get; set; } // One to Many (many side)
}


// Many to Many: Represented in database as PersonHobby (eg. Reading, Writing; User could select multiple hobbies of a person)
public class Hobby
{
    public int HobbyId { get; set; }
    public string Hobbyname { get; set; }
    public ICollection<Person> People { get; set; } // Many-To-Many with Person
}


public class Address
{
    public int AddressId { get; set; }
    public int PersonId { get; set; }
    public string Line1 { get; set; }
    public string City { get; set; }
    public string State { get; set; }

    public Person Person { get; set; }
    public ICollection<Feature> Features { get; set; } 
}


// Many to Many: Represented in database as AddressFeature (e.g Air Conditioning, Central Heating; User could select multiple features of a single address)
public class Feature
{
    public int FeatureId { get; set; }
    public string Featurename { get; set; }
    public ICollection<Address> Addresses { get; set; } // Many-To-Many with Addresses
}

Now, I want to add a NEW person with multiple addresses in the same shot.

public static Person GetFakePerson()
{
        Person person = new Person();

    person.PersonName = "Peter";
        person.PersonEmail = "[email protected]";

    person.Hobbies = new List<Hobby>();
        person.Hobbies.Add(new Hobby { HobbyId = 1 });
        person.Hobbies.Add(new Hobby { HobbyId = 2 });

    person.Addresses = new List<Address>();

    // In real world, the user in GUI will be selecting the following features as the checkboxes within the Address object.
    List<Feature> featureSet_1 = new List<Feature>();
        featureSet_1.Add(new Feature { FeatureId = 1 });
        featureSet_1.Add(new Feature { FeatureId = 2 });
        person.Addresses.Add(new Address { Line1 = "123 St", City = "Fishers", State = "IN", Features = featureSet_1 });

    List<Feature> featureSet_2 = new List<Feature>();
        featureSet_2.Add(new Feature { FeatureId = 1 });
        featureSet_2.Add(new Feature { FeatureId = 2 });
        person.Addresses.Add(new Address { Line1 = "987 Avenue", City = "Carmel", State = "IN", Features = featureSet_2 });

    return person;
}

// PersonService has this method
public int AddPerson(Person person)
{
    try
    {
        using (MyDbContext dbContext = new MyDbContext())
        {
            person.Addresses.ToList().ForEach(y => y.Features.ToList().ForEach(x => dbContext.Features.Attach(x)));

            person.Hobbies.ToList().ForEach(x => dbContext.Hobbies.Attach(x)); // This seems to be working

            dbContext.People.Add(person);

            dbContext.SaveChanges();

            return person.PersonId;
        }
    }
    catch(Exception exp)
    {
        // Catch exception here
    }
}

This is how I'm calling the above mentioned function:

static void Main(string[] args)
{
    PersonService _personService = new PersonService(); // This class holds the AddPerson()
        Person samplePerson = GetFakePerson();

    int peronId = _personService.AddPerson(samplePerson);
}

Upvotes: 1

Views: 93

Answers (1)

Gert Arnold
Gert Arnold

Reputation: 109079

You're doing nearly everything right. The only mistake you make is in

List<Feature> featureSet_1 = new List<Feature>();
    featureSet_1.Add(new Feature { FeatureId = 1 });
    featureSet_1.Add(new Feature { FeatureId = 2 });
...

List<Feature> featureSet_2 = new List<Feature>();
    featureSet_2.Add(new Feature { FeatureId = 1 });
    featureSet_2.Add(new Feature { FeatureId = 2 });
...

featureSet_1 and featureSet_2 are two lists, together containing four Feature instances. All EF knows, is that these four instances are attached to the context. At the third instance, the first in featureSet_2, it complains that this primary key value is already "taken".

You have to assign the same two Feature instances to both Addresses:

var f1 = new Feature { FeatureId = 1 };
var f2 = new Feature { FeatureId = 2 };
List<Feature> featureSet_1 = new List<Feature> { f1, f2 };
...

List<Feature> featureSet_2 = new List<Feature>  { f1, f2 };
...

(Maybe EF is even happy if you assign featureSet_1 twice, to both addresses).

Upvotes: 1

Related Questions