1
votes

I'm trying to fetch (in disconnected way) an entity with its all related entities and then trying to update the entity. But I'm getting the following error:

Attaching an entity of type 'Feature' failed because another entity of the same type already has the same primary key value. public class Person { public int PersonId { get; set; } public string Personname { get; set } public ICollection Addresses { get; set; } }

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
}

public Person GetCandidate(int id)
{
    using (MyDbContext dbContext = new MyDbContext())
    {
         var person = dbContext.People.AsNoTracking().Where(x => x.PersonId == id);
         person = person.Include(prop => prop.Addresses.Select(x => x.Country)).Include(prop => prop.Addresses.Select(x => x.Features));
         return person.FirstOrDefault();
     }
}

public void UpdateCandidate(Person newPerson)
{
    Person existingPerson = GetPerson(person.Id); // Loading the existing candidate from database with ASNOTRACKING
dbContext.People.Attach(existingPerson); // This line is giving error
    .....
    .....
    .....
}

Error: Additional information: Attaching an entity of type 'Feature' failed because another entity of the same type already has the same primary key value.

It seems like (I may be wrong) GetCandidate is assigning every Feature within Person.Addresses a new instance. So, how could I modify the GetCandidate to make sure that the same instance (for same values) is bing assisgned to Person.Addresses --> Features.

Kindly suggest.

1
It's a side effect from AsNoTracking. You'd better set dbContext.Configuration.ProxyCreationEnabled = false; when retrieving the data and remove AsNoTracking. - Ivan Stoev
ProxyCreationEnabled is already false but I do want to use AsNoTracking as I want EF not to track the entities. Is it possible that either one of the settings needs to be implemented? Is is possible to use AsNoTracking and avoid the error I'm getting? - Johny
Tracking means to allow using the db context cache. If you don't allow using the cache, EF will generate separate instances. It's simple as that. I don't see what's the problem of using the context tracking services in a short lived db context like in your GetCandidate method. As soon as EF does not create proxies, the returned objects will have no reference to the db context used to obtain them. - Ivan Stoev
cool. Thanks Ivan. It's great help. How could I accept your answer? - Johny
Ivan, could you please post as an answer so that I can accept your answer. - Johny

1 Answers

2
votes

It seems like (I may be wrong) GetCandidate is assigning every Feature within Person.Addresses a new instance. So, how could I modify the GetCandidate to make sure that the same instance (for same values) is bing assisgned to Person.Addresses --> Features.

Since you are using a short lived DbContext for retrieving the data, all you need is to remove AsNoTracking(), thus allowing EF to use the context cache and consolidate the Feature entities. EF tracking serves different purposes. One is to allow consolidating the entity instances with the same PK which you are interested in this case, and the second is to detect the modifications in case you modify the entities and call SaveChanges(), which apparently you are not interested when using the context simply to retrieve the data. When you disable the tracking for a query, EF cannot use the cache, thus generates separate object instances.

What you really not want is to let EF create proxies which hold reference to the context used to obtain them and will cause issues when trying to attach to another context. I don't see virtual navigation properties in your models, so most likely EF will not create proxies, but in order to be absolutely sure, I would turn ProxyCreationEnabled off:

public Person GetCandidate(int id)
{
    using (MyDbContext dbContext = new MyDbContext())
    {
         dbContext.Configuration.ProxyCreationEnabled = false;
         var person = dbContext.People.Where(x => x.PersonId == id);
         person = person.Include(prop => prop.Addresses.Select(x => x.Country)).Include(prop => prop.Addresses.Select(x => x.Features));
         return person.FirstOrDefault();
     }
}