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

时间:2016-11-17 16:28:15

标签: c# entity-framework linq

我有以下实体:

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

是否可以通过一次选择仅检索所需的数据? 我们正在使用Entity Framework 6.1.3。

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

  

EntityFramework.SqlServer.dll中出现未处理的“System.NotSupportedException”类型异常

     

附加信息:“Projection”类型出现在单个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();

3 个答案:

答案 0 :(得分:5)

一般来说,您无法在单个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();
}

如果要限制已加载的行数,则必须接受多个数据库查询才能加载子条目。加载单个子集合可能看起来像这样,例如

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

答案 1 :(得分:1)

我只看到第一种映射到匿名类型的方式,如下所示:

VM

更多代码,但会获得所需的结果(在一个数据库查询中)。

答案 2 :(得分:0)

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

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

之后,您可以使用实体框架加载ids(您可以修改存储过程以返回人员而不仅仅是返回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();

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