1

I'm new to the .NET Core framework. When I trying with Entity Framework Core, I couldn't get the relational object of entity.

This is my sample code:

public class TodoItemT
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public long Id { get; set; }
    public string Name { get; set; }
    public bool IsComplete { get; set; }
    
    public virtual List<SubItemT> SubItems { get; set; }
}

public class SubItemT
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public long Id { get; set; }
    [ForeignKey("TodoItemT")]
    public long TodoId { get; set; }
    public string Name { get; set; }
    public bool IsComplete { get; set; }
    
    public virtual TodoItemT TodoItemT { get; set; }
}

public class TodoContext : Microsoft.EntityFrameworkCore.DbContext
{
    public TodoContext(DbContextOptions<TodoContext> options) : base(options)
    {
    }

    public DbSet<TodoItemT> TodoItemTs { get; set; }
    public DbSet<SubItemT> SubItemTs { get; set; }
}

And I have this in Startup.cs:

services.AddDbContext<TodoContext>(opt => opt.UseInMemoryDatabase("TodoList"));

After I insert data to the database.

I try to get data from database with controller

// GET: api/Todo
[HttpGet]
public async Task<ActionResult<IEnumerable<TodoItemT>>> GetTodoItemTs()
{
    return await _context.TodoItemTs.ToListAsync();
}

But what I got is

[
  {
    "id": 1,
    "name": "string",
    "isComplete": true,
    "subItems": null
  }
]

I expected to get this instead:

[
  {
    "id": 1,
    "name": "task 1",
    "isComplete": true,
    "subItems": [
    {
        "id": 1,
        "name": "sub 1",
        "isComplete": true,
    }
    ]
  }
]

Did I do something wrong or having wrong configuration?

Thank you for the help.

3
  • Are you trying to use in-memory or sql database? Commented Sep 10, 2021 at 4:12
  • None of the answer given so far hold water. It's not about lazy loading or Include. You can only get what you want by projecting to DTOs, otherwise subItems will always contain the back reference TodoItemT, which is not in the JSON you want. Using the entity classes themselves would cause reference loops in the JSON serializer. Also, Include doesn't work with in-memory database, which you shouldn't use for testing anyway. EF themselves advise against it. Commented Sep 10, 2021 at 7:12
  • 1
    Use builder.Services.AddControllers().AddJsonOptions(x => x.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles); And then Include. This will take care of the reference loops and you will get what you are looking for. Commented Sep 10, 2021 at 20:33

5 Answers 5

4

Lazy loading is one of breaking changes you need to keep in mind when moving from EF6 to EF.Core.
In EF6 it worked automatically by default with the value LazyLoadingEnabled set to true by default.
See this post Lazy and Eager Loading in Entity Framework for example.

But in EF.Core there is no such option anymore and you always need to add .Include explicitly, as it was already mentioned. Sure, this is a better and more reliable approach.

Sign up to request clarification or add additional context in comments.

Comments

1

Entity Framework doesn't load related lists (otherwise, how does it know where to stop). You need to specify explicitly:

return await _context.TodoItemTs.Include("SubItems").ToListAsync();

There are various ways to accomplish that. Please refer to Loading Related Entities documentation

3 Comments

Is it different when I use database first to create the entity models? Cause when I use that I don't have to add .Include("SubItems")
no, if you just have _context.TodoItemTs, then it is no different. But there are so many permutations - in many cases you would have shape it with some explicit fields from SubItems. In this case is is retrieved without Include()
@jamesaq12wsx If your Db-First example was originally done in EF6 as opposed to EF Core, EF6 would happily lazy-load related entities automatically. Even in that case you are better off disabling lazy loading and explicitly eager loading if you intend to serialize entities. Lazy loading to load dependencies of a single entity will generally be a little slower than eager loading... Lazy loading dependencies of a set of initial entities will be a LOT slower than eager loading. In both cases, projection is a better recommendation to learn about to optimize performance and avoid surprises.
1

If EF would automatically load related items, you would literally load the whole database because it would not know where to stop.

Hence, you must manually decide which related items to load. Using the ".include" function to declare which items to include.

In this case, i.e.

_context.TodoItemTs.Include(x => x.SubItems).ToList();

You find this in the documentation properly explained at https://learn.microsoft.com/en-us/ef/core/querying/related-data/

4 Comments

this is not completely true, because old EF6 worked automatically with lazy loading without .Include. And there were no such issues as infinite loading usually
Surely there were issues with "infinite loading", i.e. reference loops, and the same will happen if OP enables lazy loading here or uses Include.
Ah, it is not reference loops - if you automatically follow every reference, you load the whole database EVEN WITHOUT LOOPS as every item will somehow normally reference every other item.
No, it's a loop and a JSON serializer will choke in it unless it's configured to handle reference loops.
0

Try to add foreign key attribute also on your navigation property matching your foreign key property name. Anyway I would go for fluent definition of relationships between the tables, this way I think you are missing a relationship.

Comments

0

Use lazy loading with proxies. See this: https://learn.microsoft.com/en-us/ef/core/querying/related-data/lazy .

You need to add the Microsoft.EntityFrameworkCore.Proxies package to your project and then you need to call:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseLazyLoadingProxies()
        .UseSqlServer(myConnectionString);

Once you do that, the related objects will be retrieved as expected via proxies under the hood.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.