Reputation: 3071
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 == "[email protected]")
.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:
AsNoTracking
call then there is no errorAsNoTracking
in) then there is no errorSo 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.
Upvotes: 2
Views: 1427
Reputation: 3192
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
Upvotes: 0
Reputation: 109080
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.
Upvotes: 3
Reputation: 9489
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.
e.g.
// 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.
Upvotes: 0