![](/img/trans.png)
[英]EntityCommandExecutionException when using multiple Include() with nested Select()
[英]Select entities with multiple and nested levels without using Include
我有以下實體:
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()
與Eager Load一起使用來實現:
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
。
EntityFramework.SqlServer.dll中發生了'System.NotSupportedException'類型的未處理異常
附加信息:“投影”類型出現在單個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();
一般來說,除非您批量加載所有可能相關的數據,無論它們是否屬於請求的結構,否則您都無法在單個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()
我只看到一種首先映射到匿名類型的方法,如下所示:
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
");
}
之后,您可以使用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();
我希望我的回答將幫助其他人使用實體框架為特定實體加載層次結構數據。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.