msawczyn / EFDesigner

Entity Framework visual design surface and code-first code generation for EF6, Core and beyond
MIT License
363 stars 60 forks source link

efcore v5 :: many-to-many, and HasIndex requirement? #301

Closed ensemblebd closed 3 years ago

ensemblebd commented 3 years ago

Checking if anyone else has come across this issue, as per:

System.InvalidOperationException: 
The instance of entity type 'X' cannot be tracked because another instance of this type with the same key is already being tracked.

For me the fix was to change EFCore5ModelGenerator.ttinclude: Line # 322:

var col1 = association.SourcePropertyName + "Id"; // this may be unreliable? Does it always have an "Id" affix? Is the source prop always used to identify?
var col2 = association.TargetPropertyName + "Id";
segments.Add($"UsingEntity(x => x.ToTable(\"{tableMap.Trim('"')}\").HasIndex(new[]{{\"{col1}\",\"{col2}\"}}).IsUnique())");

and then Line # 571:

var col1 = association.Source.Name + "Id";
var col2 = association.Target.Name + "Id";
segments.Add($"UsingEntity(x => x.ToTable(\"{tableMap}\").HasIndex(new[]{{\"{col1}\",\"{col2}\"}}).IsUnique())");

Wanted to get your expert opinion on this, since I'm fairly new to the many-to-many use case and really just trying it out for first time. After the above change, I was able to run ctx.SaveChanges() successfully. Note that the first time you call it, it works fine. But the second time you call it it fails.

Such as (pseudo code sample):

var customer = ctx.Customers.First(x=> x.Id == 1);
var other_customer = ctx.Customers.First(x=> x.Id == 2);

var address = new Address(){ zip = 12345 };

// this one works fine.
customer.Addresses.Add(address); 
ctx.SaveChanges();

// this one throws the exception. 
other_customer.Addresses.Add(address); 
ctx.SaveChanges();

Not sure if this is necessary, since it's only affecting me. Which makes me wonder what I'm doing wrong

msawczyn commented 3 years ago

The code you show above should work fine without any modification to the EF templates.

From what I see, Customer has an Id type of int. What is the identifier type for Address?

ensemblebd commented 3 years ago

Same for Address, both are type int for this sample.

After deeper consideration, I believe the true problem was caused by using many-to-many and writing a query loop with an "improper exchange" of sorts. ie.

Regardless of if the table has an index, as improperly presumed as a fix in my original post, the problem occurs when manipulating entities twice in the same context, using new instances of the class, via different methods of querying.

100% a user error, and 100% a DbContext change tracker usage issue. Can definitely close this or delete this thread, and thank you as always for all you do. You have an incredible product here that we all benefit from greatly.