EF on Model creation with one to many relation cause cycle overflow

I hope you will help me.

I have to entities in EF with one-to-many relation

First:

public class DefaultEvent
{

        public int DefaultEventId { get; set; }

        public virtual ICollection<DefaultCommitteeAgenda> DefaultCommitteeAgendas { get; set; }
}

Second:

public class DefaultCommitteeAgenda
    {
        public int Id { get; set; }
  
        public int DefaultEventId { get; set; }

        public DefaultEvent Event { get; set; }
}

I Config the relation between this two entities like that:

public void Configure(EntityTypeBuilder<DefaultEvent> builder)
  {
            builder.HasKey(a => a.DefaultEventId);
            builder.Property(a => a.DefaultEventId).HasColumnName("idDefaultEvent");

            // Связи
            builder.HasMany(x => x.DefaultCommitteeAgendas)
                .WithOne(x => x.Event)
                .HasForeignKey(x => x.DefaultEventId);

            builder.ToTable("ftDefaultEvent", "dbo");
     }
   public void Configure(EntityTypeBuilder<DefaultCommitteeAgenda> builder)
        {
            builder.HasKey(x => x.Id);

            builder.Property(x => x.Id)
                .HasColumnName("idDefaultCommitteeAgenda")
                .ValueGeneratedOnAdd();
  
            builder.Property(x => x.DefaultEventId)
                .HasColumnName("idDefaultEvent")
                .IsRequired();
         
            builder.HasOne(x => x.Event)
                .WithMany(x => x.DefaultCommitteeAgendas)
                .HasForeignKey(x => x.DefaultEventId);

            builder.ToTable("ftDefaultCommitteeAgenda", "dbo");
}

But in the end I received cycle relations:
DefaultEvent which has DefaultCommitteeAgenda which has DefaultEvent which has DefaultCommitteeAgenda and so and so.

But need only DefaultEvent which has DefaultCommitteeAgenda without cycle.
How can I fix this?

  • What is the problem? That’s how your entities are set up. DefaultEvent has many DefaultCommitteeAgendas and DefaultCommitteeAgenda has one DefaultEvent. This will generate a valid schema.

    – 




If Event is the top level entity where you will always query agendas through their event then you can do away with the bi-directional reference, and even the FK property in the agenda:

public class DefaultCommitteeAgenda
{
    public int Id { get; set; }

    // Agenda related fields...
}

public void Configure(EntityTypeBuilder<DefaultEvent> builder)
{
    builder.ToTable("ftDefaultEvent", "dbo");
    builder.HasKey(a => a.DefaultEventId);
    builder.Property(a => a.DefaultEventId).HasColumnName("idDefaultEvent");

    // Связи
    builder.HasMany(x => x.DefaultCommitteeAgendas)
        .WithOne()
        .HasForeignKey("DefaultEventId");

}

If you do want to query agendas and be able to get back to their event, then you have to deal with recursive references for things like serialization /w Lazy loading. The best way to do this is via projection. Entities represent the data domain. They should not be used for data transport or view concerns so instead create a DTO/ViewModel representing the data structure your consumer needs. If you only want to send details about the event and it’s agendas without the cyclical reference:

[Serializable]
public class EventDTO
{
    public int Id { get; set; }
    // Other fields the consumer needs
    
    public ICollection<AgendaDTO> Agendas { get; set; } = new List<AgendaDTO>();
}

[Serializable]
public class AgendaDTO
{
    public int Id { get; set; }
    // Other fields the consumer needs
}

Then to project these from the entities:

var events = _context.Events
    .Where(e => /* criteria */)
    .Select(e => new EventDTO
    {
         Id = e.Id,
         // other fields from event...
         Agendas = e.Agendas.Select(a => new AgendaDTO
         {
             Id = a.Id,
             // other fields from agenda...
         }).ToList()
    }).ToList();

Often developers dismiss creating a DTO as they figure if it’s going to contain the same things as the entity, why not just use the entity? Often it’s not about what you want to include, it’s about what you don’t want to include. (Like a convenient circular reference) DTOs are also a good place to address things like formatting and conversions rather than in the entity, leaving the entity to deal with DB-compatible types. Entities are responsible for data concerns. DTOs are responsible for data transfer and/or consumption concerns like serialization, formatting, etc.

The benefits here is that you don’t need to worry about references including cyclic references in the entities. The DTOs are minimized to represent only and exactly the data structure you want to expose to the consumer. This also results in better querying performance as less data is pulled back, and there is no risk of tripping up on lazy loading so long as you don’t mix DTOs and entities. (I.e. add an entity reference inside a DTO class)

Leave a Comment