Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Entity Framework circular dependency for last entity

Please consider the following entities

public class What {
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<Track> Tracks { get; set; }
    public int? LastTrackId { get; set; }]
    public Track LastTrack { get; set; }
}

public class Track {
    public Track(string what, DateTime dt, TrackThatGeoposition pos) {
        What = new What { Name = what, LastTrack = this };
    }

    public int Id { get; set; }

    public int WhatId { get; set; }
    public What What { get; set; }
}

I use the following to configure the entities:

builder.HasKey(x => x.Id);
builder.HasMany(x => x.Tracks).
    WithOne(y => y.What).HasForeignKey(y => y.WhatId);
builder.Property(x => x.Name).HasMaxLength(100);
builder.HasOne(x => x.LastTrack).
    WithMany().HasForeignKey(x => x.LastTrackId);

Has you can see there is a wanted circular reference:

What.LastTrack <-> Track.What

when I try to add a Track to the context (on SaveChanges in fact):

Track t = new Track("truc", Datetime.Now, pos);
ctx.Tracks.Add(t);
ctx.SaveChanges();

I get the following error:

Unable to save changes because a circular dependency was detected in the data to be saved: ''What' {'LastTrackId'} -> 'Track' {'Id'}, 'Track' {'WhatId'} -> 'What' {'Id'}'.

I would like to say... yes, I know but...

Is such a configuration doable with EF Core ?

like image 602
tschmit007 Avatar asked Oct 16 '16 17:10

tschmit007


1 Answers

This is what I like to call the favored child problem: a parent has multiple children, but one of them is extra special. This causes problems in real life... and in data processing.

In your class model, What (is that a sensible name, by the way?) has Tracks as children, but one of these, LastTrack is the special child to which What keeps a reference.

When both What and Tracks are created in one transaction, EF will try to use the generated What.Id to insert the new Tracks with WhatId. But before it can save What it needs the generated Id of the last Track. Since SQL databases can't insert records simultaneously, this circular reference can't be established in one isolated transaction.

You need one transaction to save What and its Tracks and a subsequent transaction to set What.LastTrackId.

To do this in one database transaction you can wrap the code in a TransactionScope:

using(var ts = new TransactionScope())
{
    // do the stuff
    ts.Complete();
}

If an exception occurs, ts.Complete(); won't happen and a rollback will occur when the TransactionScope is disposed.

like image 191
Gert Arnold Avatar answered Nov 12 '22 10:11

Gert Arnold