Reputation: 63
I am stuck with Problem, that I am trying to solve the right way.
I have multiple one-to-many 'owning' relationships. But I have a duplicate relationship between two children A should have List of Es and chained child D should point to one D from this List. I cannot set one class to be owned by 2 entities and with just 'having' relationship I am not able to save the whole structure at once. Example of how my code structure looks.
public class A //Main structure class
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id {get; set;}
[ForeignKey("FK_A_ID")]
public List<B> Bs {get; set;} //Set as OwnsMany
[ForeignKey("FK_A_ID")]
public List<D> Ds {get; set;} //Set as OwnsMany
}
public class B
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id {get; set;}
[ForeignKey("FK_B_ID")]
public List<C> Cs {get; set;} //Set as OwnsMany
public int FK_A_ID { get; set; }
}
public class C
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id {get; set;}
[ForeignKey("FK_C_ID")]
public List<D> Ds {get; set;} //Set as OwnsMany
public int FK_B_ID { get; set; }
}
public class D //referencing to one
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id {get; set;}
[ForeignKey("FK_D_ID")]
public List<E> Fs {get; set;} //Set as OwnsMany
public int FK_C_ID { get; set; }
}
// Child that I need to have in both one A (A OwnsMany E) and multiple Ds (D has/owns one E respectively E has/owns multiple Ds)
public class E
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id {get; set;}
[ForeignKey("FK_E_ID")]
public List<F> Fs {get; set;} // Trying to set as OwnsMany/HasMany
public int FK_A_ID { get; set; }
}
Problem is, that I do not know how to best specify this relationship for EF Core right.
I was trying Owning the relationship A-E and having the relationship D-E or E-D which throws error on migration. With having relationship A-E, saving throws foreign key violation, as FK is 0, because A was not saved yet and has no ID. I would like to save it all as one (if possible) and load it all as one, as I have Angular front end expecting it and sending it back edited.
I hope someone has already solved a scenario like this, but I can not find anything for a case like this in the documentation.
Upvotes: 0
Views: 1498
Reputation: 13458
Basically, OwnsMany
is a special kind of relationship that's not suitable as soon as you have multiple entities with navigation properties to the same target. In such cases, you should use HasMany
relationship.
When you have multiple references to the same entity (like TransitionOptions
that belongs to some CurrentStep
and points to some NextStep
), use FluentApi or annotate with InversePropertyAnnotation
, not with ForeignKeyAnnotation
.
Generally, I'd advise you to use more fluent api configuration and less annotation based configuration - it's easier to maintain.
Here is some sample code based on your question.
Model classes
public class Story
{
public int Id { get; set; }
public List<Step>? Steps { get; set; }
public List<Variable>? Variables { get; set; }
}
public class Variable
{
public int Id { get; set; }
public int StoryId { get; set; }
public string? Value { get; set; }
public Story? Story { get; set; }
public List<Change>? Changes { get; set; }
}
public class Step
{
public int Id { get; set; }
public int StoryId { get; set; }
public Story? Story { get; set; }
public List<TransitionOption>? TransitionOptions { get; set; }
}
public class TransitionOption
{
public int Id { get; set; }
public int StepId { get; set; }
public int? NextStepId { get; set; }
public Step? CurrentStep { get; set; }
public Step? NextStep { get; set; }
public List<Change>? Changes { get; set; }
}
public class Change
{
// If combination of TransitionOptionId and VariableId is unique,
// you can use them as composite key instead
public int Id { get; set; }
public int TransitionOptionId { get; set; }
public int VariableId { get; set; }
public Variable? Variable { get; set; }
public TransitionOption? TransitionOption { get; set; }
}
Entity Configuration classes
internal class StoryConfiguration : IEntityTypeConfiguration<Story>
{
public void Configure(EntityTypeBuilder<Story> builder)
{
builder.HasMany(x => x.Variables)
.WithOne(x => x.Story)
.HasForeignKey(x => x.StoryId)
.OnDelete(DeleteBehavior.Cascade);
builder.HasMany(x => x.Steps)
.WithOne(x => x.Story)
.HasForeignKey(x => x.StoryId)
.OnDelete(DeleteBehavior.Cascade);
}
}
internal class VariableConfiguration : IEntityTypeConfiguration<Variable>
{
public void Configure(EntityTypeBuilder<Variable> builder)
{
builder.HasMany(x => x.Changes)
.WithOne(x => x.Variable)
.HasForeignKey(x => x.VariableId)
.OnDelete(DeleteBehavior.Cascade);
}
}
internal class StepConfiguration : IEntityTypeConfiguration<Step>
{
public void Configure(EntityTypeBuilder<Step> builder)
{
builder.HasMany(x => x.TransitionOptions)
.WithOne(x => x.CurrentStep)
.HasForeignKey(x => x.StepId)
.OnDelete(DeleteBehavior.Cascade);
// No navigation property in Step for TransitionOptions pointing
// to this as NextStep.
// It would also be possible to define such a property and mention it
// in HasMany
builder.HasMany<TransitionOption>()
.WithOne(x => x.NextStep)
.HasForeignKey(x => x.NextStepId)
// If next step is deleted, only disconnect referencing TransitionOptions
.OnDelete(DeleteBehavior.SetNull);
}
}
internal class TransitionOptionConfiguration : IEntityTypeConfiguration<TransitionOption>
{
public void Configure(EntityTypeBuilder<TransitionOption> builder)
{
builder.HasMany(x => x.Changes)
.WithOne(x => x.TransitionOption)
.HasForeignKey(x => x.TransitionOptionId)
.OnDelete(DeleteBehavior.Cascade);
}
}
Apply the configurations within the OnModelCreating
method.
Sample method to create a whole story structure with 1 SaveChanges
public async Task InitDb(MyDatabase db)
{
var variable1 = new Variable
{
Value = "1st Value"
};
db.Stories?.Add(new Story
{
Variables = new List<Variable>
{
variable1
},
Steps = new List<Step>
{
new Step
{
TransitionOptions = new List<TransitionOption>
{
new TransitionOption
{
Changes = new List<Change>
{
new Change
{
Variable=variable1
}
}
}
}
}
}
});
await db.SaveChangesAsync();
}
Hope this example shows everything you need for your described structure.
Upvotes: 1