Clean way to deal with circular references in EF?

Vaccano picture Vaccano · Nov 11, 2011 · Viewed 16.7k times · Source

Say I have this table structure:

Client
-----------
ClientId                     int            not null    (identity)
CurrentDemographicId         int            null        (FK to ClientDemographic)
OtherClientFields            varchar(100)   null


ClientDemographic
------------------
ClientDemographicId          int            not null    (identity)
ClientId                     int            not null    (FK to Client)
OtherClientDemographicFields varchar(100)   null

The idea is that Client (in EF) will have a ClientDemographics list and a CurrentDemographic property.

The problem is when I setup the object structure and try to save it, I get this error:

Unable to determine a valid ordering for dependent operations. Dependencies may exist due to foreign key constraints, model requirements, or store-generated values

This error makes sense. I have a circular reference in my table setup. It does not know which entity to insert first (because it needs the Id from both tables at the same time).

So, I hacked together a solution that looks like this:

// Save off the unchanged ClientDemograpic
ClientDemographic originalClientDemographic = client.CurrentClientDemographic;

// Merge the contract into the client object
Mapper.Map(contract, client);

// If this is a new client then add as new to the list.
if (client.ClientId == 0)
{
    dataAccess.Add(client);
}

// Restore the original ClientDemographic so that EF will not choke
// on the circular reference.
ClientDemographic newClientDemographic = null;
if (client.CurrentClientDemographic != originalClientDemographic)
{
    newCurrentClientDemographic = client.CurrentClientDemographic;
    client.CurrentClientDemographic = originalClientDemographic;
}

// save our changes to the db.
dataAccess.SaveChanges();

// Restore updates to ClientDemographics and save (if needed)
if (newClientDemographic != null)
{
    client.CurrentClientDemographic = newCurrentClientDemographic;
    dataAccess.SaveChanges();
}

But changing the reference back to the previous value, saving, then setting it again so I can save again feels like a hack.

Is there a cleaner way to deal with circular references in EF?

Answer

Steve Wilkes picture Steve Wilkes · Nov 12, 2011

I'd say the answer is: "not really". The only clean way to deal with the circular reference is to look again at the design and remove it.

In this case - approaching it from a Domain Driven Design perspective - I'd say that Client is the root of your aggregate and ClientDemographic is a value object; ClientDemographics are defined by the values of their 'Other ClientDemographic fields'. You can therefore remove ClientId from ClientDemographic, and the problem is prevented instead of cured.

That said, if you're settled on this structure then unfortunately I don't think there's a neat way of handling it in EF, no.

Edit: To give Client multiple ClientDemographics as well as a CurrentClientDemographic property, you can go the other way; remove CurrentClientDemographicId from Client, and add an IsCurrent binary field to ClientDemographic. The EF then gives you a ClientDemographics collection property, and you can add the following yourself in a new, partial class:

public partial class Client
{
    public ClientDemographic CurrentDemogaphic
    {
        get { return this.ClientDemographics.First(cd => cd.IsPrimary); }
    }
}