简体   繁体   English

选择具有多个和嵌套级别的实体,而不使用“包含”

[英]Select entities 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 retrieve data of all the nested children. 现在,我想查询数据库并检索所有嵌套子代的数据。 I could achieve this by using Eager Loading with Include() : 我可以通过Include()与Eager Load一起使用来实现:

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. 我们正在使用实体框架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. 我真的不知道该如何告诉EF与他们的父母以相同的方式映射所有儿童Projection

An unhandled exception of type 'System.NotSupportedException' occurred in EntityFramework.SqlServer.dll EntityFramework.SqlServer.dll中发生了'System.NotSupportedException'类型的未处理异常

Additional information: The type 'Projection' appears in two structurally incompatible initializations within a single LINQ to Entities query. 附加信息:“投影”类型出现在单个LINQ to Entities查询中的两个结构不兼容的初始化中。 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();

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. 一般来说,除非您批量加载所有可能相关的数据,无论它们是否属于请求的结构,否则您都无法在单个SQL查询中加载未知深度的递归结构。

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: 因此,如果您只想限制已加载的列(不包括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();
}

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

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). 多一点的代码,但是将获得期望的结果(在一个数据库查询中)。

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) : 之后,您可以使用Entity Framework加载传递的人员(前祖父)下人员的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();

I hope my answer will help others to load hierarchical data for specific entities using entity framework. 我希望我的回答将帮助其他人使用实体框架为特定实体加载层次结构数据。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM