我在 ASP.NET Core Web API 项目中有一个使用 Entity Framework Core 的启动项目。
我有一个
Category
对象,它与 Category
具有父子关系(即:类别及其子类别,子类别可以有子类别等)。
当我尝试仅返回顶级类别(希望它们的子类别会自动添加到图表中)时,我发现它确实返回顶级类别及其子类别,但不返回其子类别的子类别,即:
top level -> child -> [should return child's child but its not]
我正在尝试在我的
CategoryRepository
类上使用此方法:
public async Task<IEnumerable<Category>> getAllTopLevelCategories()
{
return await _context.Categories
.Include(p => p.Children)
.Where(category => category.ParentId == null)
.ToListAsync();
}
但是我返回的 JSON 缺少“Steer Compost”,它是“Compost”类别的子项
[
{
"name": "Garden",
"parentId": null,
"parent": null,
"children": [
{
"name": "Compost",
"parentId": "e9a9e63b-e71f-4aab-b1bb-97db5687048b",
"parent": null,
"children": [],
"products": [],
"id": "464b75f0-f5db-4fac-8023-ec39ae21ad79",
"dateAdded": "2024-10-14T20:51:57.167129Z"
}
],
"products": [],
"id": "e9a9e63b-e71f-4aab-b1bb-97db5687048b",
"dateAdded": "2024-08-07T13:59:27.182185Z"
},
{
"name": "Pets",
"parentId": null,
"parent": null,
"children": [
{
"name": "Pet Food",
"parentId": "f1dfe34c-adb2-4e19-81ad-d69d4c3364b9",
"parent": null,
"children": [],
"products": [],
"id": "b1000daf-d101-4ee3-b5b6-fb49e4aa5598",
"dateAdded": "2024-10-14T17:36:29.502663Z"
}
],
"products": [],
"id": "f1dfe34c-adb2-4e19-81ad-d69d4c3364b9",
"dateAdded": "2024-08-07T14:17:44.973453Z"
}
]
此方法返回所有
Categories
,但每个的对象图都已完全填写!我基本上想要这个,但只返回顶层Categories
(以及它们所属的对象图)。我可以使用这个,但我必须对返回的 Category
数组进行过滤(使用 Linq),因此我只会使用顶级类别,但这似乎是 Entity Core 应该能够做到的事情,我只是还不知道该怎么做。
public async Task<IEnumerable<Category>> getAllCategories()
{
return await _context.Set<Category>()
.ToListAsync();
}
结果:
[
{
"name": "Garden",
"parentId": null,
"parent": null,
"children": [
{
"name": "Compost",
"parentId": "e9a9e63b-e71f-4aab-b1bb-97db5687048b",
"parent": null,
"children": [
{
"name": "Steer Compost",
"parentId": "464b75f0-f5db-4fac-8023-ec39ae21ad79",
"parent": null,
"children": [],
"products": [],
"id": "5d567504-62be-4af7-9cab-dcdd66b670ec",
"dateAdded": "2024-10-18T15:09:45.58668Z"
}
],
"products": [],
"id": "464b75f0-f5db-4fac-8023-ec39ae21ad79",
"dateAdded": "2024-10-14T20:51:57.167129Z"
}
],
"products": [],
"id": "e9a9e63b-e71f-4aab-b1bb-97db5687048b",
"dateAdded": "2024-08-07T13:59:27.182185Z"
},
{
"name": "Pets",
"parentId": null,
"parent": null,
"children": [
{
"name": "Pet Food",
"parentId": "f1dfe34c-adb2-4e19-81ad-d69d4c3364b9",
"parent": null,
"children": [],
"products": [],
"id": "b1000daf-d101-4ee3-b5b6-fb49e4aa5598",
"dateAdded": "2024-10-14T17:36:29.502663Z"
}
],
"products": [],
"id": "f1dfe34c-adb2-4e19-81ad-d69d4c3364b9",
"dateAdded": "2024-08-07T14:17:44.973453Z"
},
{
"name": "Pet Food",
"parentId": "f1dfe34c-adb2-4e19-81ad-d69d4c3364b9",
"parent": {
"name": "Pets",
"parentId": null,
"parent": null,
"children": [
null
],
"products": [],
"id": "f1dfe34c-adb2-4e19-81ad-d69d4c3364b9",
"dateAdded": "2024-08-07T14:17:44.973453Z"
},
"children": [],
"products": [],
"id": "b1000daf-d101-4ee3-b5b6-fb49e4aa5598",
"dateAdded": "2024-10-14T17:36:29.502663Z"
},
{
"name": "Compost",
"parentId": "e9a9e63b-e71f-4aab-b1bb-97db5687048b",
"parent": {
"name": "Garden",
"parentId": null,
"parent": null,
"children": [
null
],
"products": [],
"id": "e9a9e63b-e71f-4aab-b1bb-97db5687048b",
"dateAdded": "2024-08-07T13:59:27.182185Z"
},
"children": [
{
"name": "Steer Compost",
"parentId": "464b75f0-f5db-4fac-8023-ec39ae21ad79",
"parent": null,
"children": [],
"products": [],
"id": "5d567504-62be-4af7-9cab-dcdd66b670ec",
"dateAdded": "2024-10-18T15:09:45.58668Z"
}
],
"products": [],
"id": "464b75f0-f5db-4fac-8023-ec39ae21ad79",
"dateAdded": "2024-10-14T20:51:57.167129Z"
},
{
"name": "Steer Compost",
"parentId": "464b75f0-f5db-4fac-8023-ec39ae21ad79",
"parent": {
"name": "Compost",
"parentId": "e9a9e63b-e71f-4aab-b1bb-97db5687048b",
"parent": {
"name": "Garden",
"parentId": null,
"parent": null,
"children": [
null
],
"products": [],
"id": "e9a9e63b-e71f-4aab-b1bb-97db5687048b",
"dateAdded": "2024-08-07T13:59:27.182185Z"
},
"children": [
null
],
"products": [],
"id": "464b75f0-f5db-4fac-8023-ec39ae21ad79",
"dateAdded": "2024-10-14T20:51:57.167129Z"
},
"children": [],
"products": [],
"id": "5d567504-62be-4af7-9cab-dcdd66b670ec",
"dateAdded": "2024-10-18T15:09:45.58668Z"
}
]
这是我的
Category
对象:
using Newtonsoft.Json;
using ShopBack.Database.Models;
/// <summary>
/// The Category model
/// </summary>
public class Category : ModelBase
{
/// <summary>
/// Name of the category
/// </summary>
public required string Name { get; set; }
/// <summary>
/// Parent CategoryId
/// </summary>
public required Guid? ParentId { get; set; }
/// <summary>
/// Parent CategoryId
/// </summary>
public virtual Category? Parent { get; set; }
/// <summary>
/// Parent CategoryId
/// </summary>
public virtual ICollection<Category> Children { get; set; } = new List<Category>();
/// <summary>
/// List of products for the category
/// </summary>
public virtual ICollection<Product> Products { get; set; } = new List<Product>();
}
你没有告诉它加载 2 个子级别,只加载 1 个。如果你想要 top -> child -> grandchild 那么:
return await _context.Categories
.Include(p => p.Children)
.ThenInclude(c => c.Children)
.Where(category => category.ParentId == null)
.ToListAsync();
请记住,它会停在那里。它不会包括曾孙。
即使您没有指定包含某些孙子项,您也可能会看到它填充了它们,这与 EF 的缓存有关。如果在您读取此查询时 DbContext 碰巧加载并跟踪了孙子实体或任何其他相关实体,那么当您执行启用跟踪的查询时,该实体将自动关联。这可能会误导结果,因为如果您有 2 个孙子需要“堆肥”,但其中一个碰巧已被较早读取和跟踪,而另一个没有,那么堆肥将只填满一个孙子。
为了避免跟踪未明确请求的缓存填充实体,请执行无跟踪查询:
return await _context.Categories
.Include(p => p.Children)
.THenInclude(c => c.Children)
.Where(category => category.ParentId == null)
.AsNoTracking()
.ToListAsync();
这有两个效果。 #1 - 此查询获取的实体不会添加到跟踪缓存中。 #2 - 在构建此查询的结果时,不使用跟踪缓存中的实体。 (仅限数据库中当前持久的数据)我建议在读取数据时应使用
AsNoTracking
并仅将跟踪查询保留用于更新/插入读取。
根据返回的行数和大小(列),您还可能受益于使用
.AsSplitQuery()
来消除连接多个表时产生的笛卡尔积。