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 ?
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 Track
s are created in one transaction, EF will try to use the generated What.Id
to insert the new Track
s 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 Track
s 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With