简体   繁体   中英

Strange entity framework error with multiple Include statements that are the same type

I am using the latest version of entity framework (6.1.3) and i have the following class which allows a customers name to be changed:

public class CustomerService
{
    public void ChangeName()
    {
        using (TestModel ctx = new TestModel())
        {
            var customer = GetCustomer(ctx);

            //set new name
            customer.Name = "New Name";

            SaveCustomer(ctx, customer);
        }
    }

    private Customer GetCustomer(TestModel ctx)
    {
        //get customer by email
        var customer = ctx.Customers
                          .AsNoTracking()
                          .Include(n => n.Country) //Load Shipping Country
                          .Include(n => n.Country1) //Load Billing Country
                          .Where(n => n.Email == "test@test.com")
                          .Single();

        return customer;
    }

    private void SaveCustomer(TestModel ctx, Customer customer)
    {
        //save back
        ctx.Customers.Attach(customer); // getting error here
        ctx.Entry(customer).State = EntityState.Modified;
        ctx.SaveChanges();
    }
}

In the sql server database i have 2 tables:

  • Customer - Id , Name , ShippingCountryId (Foreign Key), BillingCountryId (Foreign Key)
  • Country - Id , Name

When i call the ChangeName method i get the following error:

Attaching an entity of type 'TestConsoleApp.customermodel.Country' 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.

I have been doing some debugging and found the following:

  • If i remove the AsNoTracking call then there is no error
  • If i remove one of the Includes (keeping AsNoTracking in) then there is no error

So it looks like the combination of AsNoTracking and 2 Includes that are of the same type causes the issue.

Can anyone suggest why i get this error and how i would resolve it? (At the same time keeping AsNoTracking and the 2 Includes in my code).

I reuse the GetCustomer method in other places thats why i want to keep AsNoTracking and the 2 Includes in.

It has to do with reference navigation properties and AsNotracking .

The line ...

ctx.Entry(customer).State = EntityState.Modified;

... marks the customer as Modified , but also attaches both countries to the context as Unchanged . In this case, both countries are identical. Except, they aren't...

Normally, when an object is materialized by EF using Include , EF creates only one instance of each entity in the object graph. The context cache is an identity map : each entity occurs only once. So when both countries are the same, one Country entity will be found in the cache.

With AsNoTracking however, EF doesn't file anything into its cache. Now two instances of the same country are created. Hence the exception when they are attached.

you don't need this method.

private void SaveCustomer(TestModel ctx, Customer customer)
{
    //save back
    ctx.Customers.Attach(customer); //GET ERROR HERE
    ctx.Entry(customer).State = EntityState.Modified;
    ctx.SaveChanges();
}

just do

    using (TestModel ctx = new TestModel())
    {
        var customer = GetCustomer(ctx);

        //set new name
        customer.Name = "New Name";

        ctx.SaveChanges();
    }

basically, your retrieved customer is not an isolated entity. it comes from ctx and is already tracked by EF. you don't need to attach it once more.

you attach an entity, only if you created it anew in your code, but with a primary key manually.

eg

// where 1023 is the key of an existing entity in the DB
var customer = new Customer { Id = 1023, Name = "test" };
ctx.Customers.Attach(customer)

this is not the case with your code. your entity comes from an EF query via ctx, and is already tracked by EF.

and the problem has nothing to do with multiple includes.

So it looks like the combination of AsNoTracking and 2 Includes that are of the same type causes the issue.

That is the problem. When you attach the customer with:

ctx.Customers.Attach(customer);

It's trying to attach the same country twice. The shipping country and the billing country are the same because they have the same key.

From https://msdn.microsoft.com/library/bb896271(v=vs.100).aspx :

If the object being attached has related objects, those objects are also attached to the object context.

So the customer is attached, then .Country is attached, then .Country1 is attached. Country and Country1 are the same, thus the exception, because, also from https://msdn.microsoft.com/library/bb896271(v=vs.100).aspx , when you are attaching entities:

If more than one entity of a particular type has the same key value, the Entity Framework will throw an exception.

To solve this problem, go into SaveCustomer and change Attach to Add , like this:

private void SaveCustomer(TestModel ctx, Customer customer)
{
    //save back
    ctx.Customers.Add(customer); // no more error!
    ctx.Entry(customer).State = EntityState.Modified;
    // Prevent countries from being added to database (they already exist in db)
    ctx.Entry(customer.Country).State = EntityState.Detatched;
    ctx.Entry(customer.Country1).State = EntityState.Detatched;
    ctx.SaveChanges();
}

Yes, you are calling Add , which is typically used to add new data that doesn't yet exist in the database, which will result in an INSERT to the database. However, since we're going in and manually setting the EntityState to Modified , there will be an UPDATE statement instead of an INSERT statement sent to the database.

I wrote a small test app with some unit tests to demonstrate this more fully, which can be found at https://github.com/codethug/EF.Experiments/blob/master/EF.Test/AttachingMultiple.cs

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