选择具有多个嵌套级别的实体,而不使用Include

本文关键字:实体 Include 嵌套 选择 | 更新日期: 2023-09-27 18:18:31

我有以下实体:

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

现在我想查询数据库并检索所有嵌套子节点的数据。我可以通过使用Include():

的动态加载来实现这一点
var allItems = dbContext.Items
                    .Include(x => Children)
                    .ToList();

但我想做以下投影,而不是急于加载:

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

是否有可能通过一次选择只检索所需的数据?我们使用的是实体框架6.1.3。

编辑:

这是我目前所尝试的。我真的不知道如何告诉EF映射所有孩子Projection的方式与他们的父母相同。

类型为"System"的未处理异常。在EntityFramework.SqlServer.dll中发生了NotSupportedException

附加信息:在单个LINQ to Entities查询中,类型'Projection'出现在两个结构上不兼容的初始化中。可以在同一个查询的两个位置初始化类型,但前提是在两个位置设置相同的属性,并且这些属性的设置顺序相同。

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

选择具有多个嵌套级别的实体,而不使用Include

一般来说,您不能在单个SQL查询中加载深度未知的递归结构,除非您批量加载所有潜在的相关数据,而不管它们是否属于所请求的结构。

所以,如果你只是想限制加载的列(不包括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()

我只看到第一个映射到匿名类型的方法,像这样:

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

更多的代码,但将得到所需的结果(在一个数据库查询)

假设我们有如下自引用表:

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

在某个时间点,你需要得到特定人的所有孙子。

因此,首先我将创建存储过程(使用代码优先迁移)来获取这些特定人员的层次结构中的所有人员:

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

之后,您可以使用实体框架加载id(您可以修改存储过程以返回人员而不是只返回id)的人员在传递的人员(前祖父):

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

我希望我的答案将帮助其他人使用实体框架加载特定实体的分层数据。