Select entities with multiple and nested levels without using Include

13,687

Solution 1

Generally speaking, you can't load a recursive structure of unknown unlimited depth in a single SQL query, unless you bulk-load all potentially relevant data irregardless whether they belong to the requested structure.

So if you just want to limit the loaded columns (exclude PropertyB) but its ok to load all rows, the result could look something like the following:

var parentGroups = dbContext.Items.ToLookup(x => x.ParentId, x => new Projection
{
    Id = x.Id,
    PropertyA = x.PropertyA
});

// fix up children
foreach (var item in parentGroups.SelectMany(x => x))
{
    item.Children = parentGroups[item.Id].ToList();
}

If you want to limit the number of loaded rows, you have to accept multiple db queries in order to load child entries. Loading a single child collection could look like this for example

entry.Children = dbContext.Items
    .Where(x => x.ParentId == entry.Id)
    .Select(... /* projection*/)
    .ToList()

Solution 2

I see only a way with first mapping to anonymous type, like this:

var allItems = dbContext.Items
            .Select(x => new {
                Id = x.Id,
                PropertyA = x.PropertyA,
                Children = x.Children.Select(c => new {
                    Id = c.Id,
                    PropertyA = c.PropertyA,
                })
            })
            .AsEnumerable()
            .Select(x => new Projection() {
                Id = x.Id,
                PropertyA = x.PropertyA,
                Children = x.Children.Select(c => new Projection {
                    Id = c.Id,
                    PropertyA = c.PropertyA
                }).ToList()
            }).ToList();

A bit more code but will get the desired result (in one database query).

Share:
13,687
Marc
Author by

Marc

Working at Caterpillar Energy Solutions as Senior Developer ) ( ( ) ) ) ( ( _______)_ .-'---------| ( C|/\/\/\/\/| '-./\/\/\/\/| '_________' '-------' email: [email protected]

Updated on June 12, 2022

Comments

  • Marc
    Marc almost 2 years

    I have the following entity:

    public class Item 
    {
        public int Id { get; set; }
    
        public int? ParentId { get; set; }
        public Item Parent { get; set; }
        public List<Item> Children { get; set; }
    
        public double PropertyA { get; set; }
        public double PropertyB { get; set; }
        ...
    }
    

    Now I want to query the database and retrieve data of all the nested children. I could achieve this by using Eager Loading with Include():

    var allItems = dbContext.Items
                        .Include(x => Children)
                        .ToList();
    

    But instead of Eager Loading, I want to do the following projection:

    public class Projection 
    {
        public int Id { get; set; }
        public List<Projection> Children { get; set; }
        public double PropertyA { get; set; }
    }
    

    Is it possible to retrieve only the desired data with a single select? We are using Entity Framework 6.1.3.

    Edit: This is what I have tried so far. I really don't know how to tell EF to map all child Projection the same way than their parents.

    An unhandled exception of type 'System.NotSupportedException' occurred in EntityFramework.SqlServer.dll

    Additional information: The type 'Projection' appears in two structurally incompatible initializations within a single LINQ to Entities query. A type can be initialized in two places in the same query, but only if the same properties are set in both places and those properties are set in the same order.

    var allItems = dbContext.Items
        .Select(x => new Projection
        {
            Id = x.Id,
            PropertyA = x.PropertyA,
            Children = x.Children.Select(c => new Projection()
            {
                Id = c.Id,
                PropertyA = c.PropertyA,
                Children = ???
            })
        })
        .ToList();