0

I want to set a foreign key on an entity. I have the foreign entity exposed in my user control, and want to set it via WinForms data binding.

Here's the catch - the foreign entity was originally loaded from another repository/DbContext, as the user control populates itself independently using its own repository.

Unfortunately this doesn't work "out of the box", as this example demonstrates:

var repository1 = GetRepository();
var categoryFromRepository1 = repository1.GetAll<Category>().First();

var repository2 = GetRepository();
var appointmentFromRepository2 = repository2.GetNewAppointment();

appointmentFromRepository2 .Category = categoryFromRepository1;

repository2.Add(appointmentFromRepository2);

repository2.SaveChanges();

This fails on at Add() with the following error:

An entity object cannot be referenced by multiple instances of IEntityChangeTracker.

OK, so repository2 can't auto-attach the Category because it's attached to repository1. Great, so let's detach first:

repository1.Detach(categoryFromRepository1);

Which fails on SaveChanges() due to a validation error - whoops, turns out repository2 thinks it's an Added entry and trying to insert. Great, so let's attach as well to avoid this:

repository2.Attach(categoryFromRepository1);

And this works! Problem solved. I've now set the repository2-entity property to the repository1-entity, voila.

Except that this solution sucks swamp water... We have many data-bound self-populating user controls throughout the program, and manually detaching/reattaching all the foreign entity references prior to SaveChanges() is a horrible solution. Furthermore, supposing the repository we're saving via happens to have the object attached already then we get this error when we Attach():

An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.

None of the solutions I can think of are that great:

1) In our generic repository class on SaveChanges(), scan all foreign references on all modified entities for out-of-DbContext entity references, dynamically change them to the in-DbContext entity reference (load from DB if necessary)

2) Don't set the navigation property at all, just set the foreign key ID field (sucks0rz yo' b0x0rz)

3) Manually do these checks before save (violates DRY & persistence-ignorance principles)

4) Abandon data-binding to these properties, manually set properties & load entities from the main repository (terrible - means extra queries to the database for data we already have)

5) Fudge user controls so that they can load their data from a given repository, if required (poor solution, violates some basic design principle... but workable)

Any other ideas, plz?

Regards,

-Brendan

Brendan Hill
  • 3,406
  • 4
  • 32
  • 61

1 Answers1

0

Given the presence of multiple DbContext instances, it seems you have multiple bounded contexts at play. Specifically, there are multiple aggregates at play, namely Category and Appointment. Due to issues such as the one you're having, it is desirable to implement references between aggregates using only the identity value - no direct object references. If Appointment references Category by ID alone, you wouldn't have this problem. It is likely though that you need the entire Category aggregate for display purposes. This requirement can be addressed with the use of the read-model pattern.

Take a look at Effective Aggregate Design for more on this.

eulerfx
  • 36,769
  • 7
  • 61
  • 83
  • Thanks for the feedback. I agree that this would solve the immediate problem (I tested it), but by adopting option (2) we lose the benefits of accessing the Category properties at this point (eg. Name, Color, etc). It would be a shame to reload the Category it from the database just to access these we it was loaded earlier, merely via another DbContext. – Brendan Hill Mar 14 '13 at 21:59
  • In the end we've adopted (5) FYI – Brendan Hill Mar 14 '13 at 22:00
  • There is a difference between accessing for reading purposes alone vs accessing for behavior or modification. You can still get the benefits of accessing category properties by creating a separate read-model which would join in Category. This would be separate from the model used for behavior and persistence. – eulerfx Mar 14 '13 at 22:24