Select entities with multiple and nested levels without using Include

前端 未结 3 1486
没有蜡笔的小新
没有蜡笔的小新 2020-12-11 06:05

I have the following entity:

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

    public int? ParentId { get; set; }
    public Item Parent { get; set; }         


        
相关标签:
3条回答
  • 2020-12-11 06:23

    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).

    0 讨论(0)
  • 2020-12-11 06:30

    Let's say we have the following self-referencing table:

    public class Person
    {
        public Person()
        {
            Childern= new HashSet<Person>();
        }
    
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; set; }
    
        public int? ParentId { get; set; }
    
    
        [StringLength(50)]
        public string Name{ get; set; }
    
        public virtual Person Parent { get; set; }
    
        public virtual ICollection<Person> Children { get; set; }
    
    }
    

    And for some point of time you need to get all grandsons for specific persons.

    So, first of all I will create stored procedure(using code-first migration) to get all persons in the hierarchy for those specific persons:

    public override void Up()
    {
        Sql(@"CREATE TYPE IdsList AS TABLE   
                    ( 
                    Id Int
                    )
                    GO
    
                    Create Procedure getChildIds(
                    @IdsList dbo.IdsList ReadOnly
                    )
                    As
                    Begin
                    WITH RecursiveCTE AS
                    (
                        SELECT Id
                        FROM dbo.Persons
                        WHERE ParentId in (Select * from @IdsList)
                        UNION ALL
    
                        SELECT t.Id
                        FROM dbo.Persons t
                        INNER JOIN RecursiveCTE cte ON t.ParentId = cte.Id
                    )
                    SELECT Id From RecursiveCTE
                    End");
    }
    
    public override void Down()
    {
        Sql(@" Drop Procedure getChildIds
               Go
               Drop Type IdsList
               ");
    }
    

    After that you can use Entity Framework to load the ids(you could modify stored procedure to return persons instead of only returning ids) of persons under the passed persons(ex grandfather) :

     var dataTable = new DataTable();
     dataTable.TableName = "idsList";
     dataTable.Columns.Add("Id", typeof(int));
     //here you add the ids of root persons you would like to get all persons under them
     dataTable.Rows.Add(1);
     dataTable.Rows.Add(2);
    //here we are creating the input parameter(which is array of ids)
     SqlParameter idsList = new SqlParameter("idsList", SqlDbType.Structured);
     idsList.TypeName = dataTable.TableName;
     idsList.Value = dataTable;
     //executing stored procedure
     var ids= dbContext.Database.SqlQuery<int>("exec getChildIds @idsList", idsList).ToList();
    

    I hope my answer will help others to load hierarchical data for specific entities using entity framework.

    0 讨论(0)
  • 2020-12-11 06:39

    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()
    
    0 讨论(0)
提交回复
热议问题