Select objects with multiple and nested levels without using Include

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 get the data of all nested children. I could achieve this using Eager Loading with Include():

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

But instead of Eager Loading, I want to make the following forecast:

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

Is it possible to get only the data you need with one choice? We are using Entity Framework 6.1.3.

Edit: This is what I have tried so far. I really don't know how to say EF to display all the children Projectionjust like their parents.

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

: "" LINQ to Entities. , , .

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();
+4
3

, SQL-, , .

, (exclude PropertyB), , :

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();
}

, db . ,

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

, :

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();

, ( ).

+1

, :

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; }

}

- .

So, first of all, I will create a stored procedure (using code wrapping) to get all the persons in the hierarchy for these 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 the Entity Framework to load identifiers (you can change the stored procedure to return people, not just returned identifiers) of persons under transferred persons (former 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 helps other users load hierarchical data for specific objects using the framework entity.

0
source

Source: https://habr.com/ru/post/1661083/


All Articles