How to avoid circular navigation properties in Entity Framework Core?

asp.net-core c# entity-framework-core tsql

Question

I have a such table schema:

CREATE TABLE Categories
(
    Id INT IDENTITY(1,1),
    Name varchar(100),
    CONSTRAINT PK_Category_Id PRIMARY KEY (Id)
)

CREATE TABLE Products
(
    Id INT IDENTITY(1,1),
    IdCategory INT NOT NULL
        CONSTRAINT FK_Products_IdCategory__Categories_Id FOREIGN KEY(IdCategory) REFERENCES Categories(Id),
    Name varchar(100), 
    Price decimal(18,2),
    ImageUrl varchar(256),
    CONSTRAINT PK_Product_Id PRIMARY KEY (Id)
)

EF Core generated the following code:

public partial class Categories
{
    public Categories()
    {
        Products = new HashSet<Products>();
    }

    public int Id { get; set; }
    public string Name { get; set; }

    public virtual ICollection<Products> Products { get; set; }
}

public partial class Products
{
    public Products()
    {
        OrderItems = new HashSet<OrderItems>();
        ShoppingCartItems = new HashSet<ShoppingCartItems>();
    }

    public int Id { get; set; }
    public int IdCategory { get; set; }
    public string Name { get; set; }
    public decimal? Price { get; set; }
    public string ImageUrl { get; set; }

    public virtual Categories IdCategoryNavigation { get; set; }
    public virtual ICollection<Items> Items { get; set; }        
}

And OnModelCreating(ModelBuilder modelBuilder) method:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.HasAnnotation("ProductVersion", "2.2.6-servicing-10079");

    modelBuilder.Entity<Categories>(entity =>
    {
        entity.Property(e => e.Name)
            .HasMaxLength(100)
            .IsUnicode(false);
    });

    modelBuilder.Entity<Products>(entity =>
    {
        entity.Property(e => e.ImageUrl)
            .HasMaxLength(256)
            .IsUnicode(false);

        entity.Property(e => e.Name)
            .HasMaxLength(100)
            .IsUnicode(false);

        entity.Property(e => e.Price).HasColumnType("decimal(18, 2)");

        entity.HasOne(d => d.IdCategoryNavigation)
            .WithMany(p => p.Products)
            .HasForeignKey(d => d.IdCategory)
            .OnDelete(DeleteBehavior.ClientSetNull)
            .HasConstraintName("FK_Products_IdCategory__Categories_Id");
    });
}

I've written the following code:

public override async Task<Products> GetById(int id)
{   
    return await DbContext.Products
        .Include(p => p.IdCategoryNavigation)
        .FirstOrDefaultAsync(p => p.Id == id);
}

And I've got the following circular navigation properties and these properties will be created until I click on Products: enter image description here

How can I avoid circular navigation properties? Please, tell me what I've done wrong? Is it expected and correct behaviour?

1
1
9/20/2019 8:15:29 PM

Accepted Answer

This is an expected part of any programming language because it's always possible to create objects that refer to each other. When visualising this the debugger In visual studio will just show the links as it sees it in response to your navigation of the object tree

Moving away from Entity Framework for a moment, consider the generic collection class LinkedList, which is a chained collection of LinkedListNode objects. Each node has a Next and a Previous, while the list itself has a First and a Last (the linking is hence bidirectional)

If you put two items in your list, "hello" and "world" then examined it in the debugger you'd see:

list.First          LinkedListNode "hello"
  .Next             LinkedListNode "world"
    .Previous       LinkedListNode "hello"
      .Next         LinkedListNode "world"

You could step back and forth all day, making the tree in the debugger visualiser deeper and deeper. It's not an error or a problem, it's just the conceptual visual equivalent of writing in code:

list.First.Next.Previous.Next; //"world

You could alternate typing Next and Previous all day too - it's valid C# though probably unnecessary unless you're walking a tree that branches out and you want to go up closer to the root before travelling down a different branch

This "cyclical" structure pops up anywhere an object refers to another object that refers to the original - A DataTable's DataRows each have a .Table property that refers back to the table that owns the row and so on. Not a problem and won't cause your code to break down in some endless loop - EF won't try to save the Customers, then the Customer's Orders, then the Customer's Orders' Customers, then the Customer's Orders' Customers' Orders ... etc. It knows when to stop saving a Customer Order pair

2
9/21/2019 5:04:53 AM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow