唉,你忘了告诉我们你有什么课程。你也没有告诉我们表之间的关系。我不得不从你的用法中猜测。您提出的下一个问题考虑提供此信息。
您似乎有一个带有Requests 的表和一个带有InternalBudgets 的表。 Requests 和 InternalBudgets 之间似乎存在一对多的关系:每个Request 有零个或多个InternalBudgets,每个InternalBudget 恰好属于一个Request,即外国键是指。
Requests 和 ExternalBudgets 之间似乎存在类似的一对多。
最后,ExternalBudgets 和 ExternalBudgetTypes 之间也存在一对多。我个人希望是多对多关系:每个ExternalBudgetType 都被零个或多个ExternalBudgets 使用。这不会彻底改变答案。
如果您关注了the entity framework conventions,您将拥有类似于以下的课程:
class Request
{
public int Id {get; set;}
...
// Every Request has zero or more InternalBudgets (one-to-many)
public virtual ICollection<InternalBudget> InternalBudgets {get; set;}
// Every Request has zero or more ExternalBudgets (one-to-many)
public virtual ICollection<ExternalBudged> ExternalBudgets {get; set;}
}
内部和外部预算:
class InternalBudget
{
public int Id {get; set;}
...
// Every InternalBudget belongs to exactly one Request, using foreign key
public int RequestId {get; set;}
public virtual Request Request {get; set;}
}
class ExternalBudget
{
public int Id {get; set;}
...
// Every ExternalBudget belongs to exactly one Request, using foreign key
public int RequestId {get; set;}
public virtual Request Request {get; set;}
// Every ExternalBudget has zero or more ExternalBudgetTypess (one-to-many)
public virtual ICollection<ExternalBudgetType> ExternalBudgetTypes {get; set;}
}
终于ExternalBudgetTypes:
class ExternalBudgetType
{
public int Id {get; set;}
...
// Every ExternalBudgetType belongs to exactly one ExternalBudget, using foreign key
public int ExternalBudgetId{get; set;}
public virtual ExternalBudget ExternalBudget {get; set;}
}
由于您遵守约定,这就是实体框架检测您的表、表之间的关系以及主键和外键所需的全部内容
在实体框架中,表的列由非虚拟属性表示。虚拟属性表示表之间的关系(一对多、多对多)
外键是表中的一列。因此它是非虚拟的。外键引用的对象不是表的一部分,因此 if 是虚拟的。
xxx-to-many 关系中的“Many”端应该用ICollection<...> 来实现,而不是IList<...>。 Ilist 提供了数据库中未定义的功能:
Requests fetchedRequest = ...
InternalBudget internalBudget = fetchedRequest[3];
你能说哪个 internalBudget 有列表索引 [3] 吗?
最好不要提供未定义的功能。 ICollection<...> 为您提供Add / Delete / Count 的可能性,所有功能在数据库中都有定义的含义。
最后一个提示:在你的标识符中使用复数名词来指代序列;使用单数名词来指代这些序列中的元素。这使得 LINQ 语句更易于理解。
回到你的问题
要求给定一个 projectCode,给我具有此 projectCode 的请求,以及它们的内部和外部预算的几个属性
简单方法:使用虚拟ICollections
var projectCode = ...
var result = dbContext.Requests
// Keep only the Requests that have projectCode
.Where (request => request.ProjectCode == projectCode)
// order the remaining Requests by descending RequestedOn date
.OrderByDescending(request => request.RequestedOn)
// Select the properties that you want:
.Select(request => new
{
// Select only the Request properties that you plan to use
Id = request.Id,
Name = request.Name,
...
// Internal budgets of this Request
InternalBudgets = request.InternalBudgets.Select(budget => new
{
// Again, only the properties that you plan to use
Id = budget.Id,
...
// not Needed, you know the value
// RequestId = budget.RequestId,
})
.ToList(),
// External budgets of this Request
ExternalBudgets = request.ExternalBudgets.Select(budget => new
{
...
ExternalBudgetTypes = budget.ExternalBudgetTypes
.Select(budgetType => ...)
.ToList(),
})
.ToList(),
});
简而言之:从所有请求中,仅保留属性 ProjectCode 的值等于 projectCode 的请求。按属性 RequestedOn 的降序排列剩余的请求。最后选择请求的几个属性,它的内部预算和它的外部预算。
实体框架知道表之间的关系,并且知道如何在正确的 (Group-)join 中转换使用您的虚拟属性。
注意:结果与您的解决方案略有不同。您的解决方案将给出:
Request InternalBudget (for simplicity: leave out Externalbudget / type
01 10
02 12
04 11
01 14
01 15
02 13
我的解决方案给出:
- 请求 01 及其内部预算 {10、14、15}
- 请求 02 及其 InternalBudgets {12, 13}
- 没有任何内部预算的请求 05
- 请求 04 及其 InternalBudget {11}
恕我直言,这更加直观和高效:每个请求项的属性只传输一次。
请注意,您还会收到没有内部/外部预算或类型的请求。
如果您不想要它们,请使用 Where(... => ...Count != 0) 将它们过滤掉。
如果您不想要“项目及其子项目”(实际上是 GroupJoin),而是标准联接,请使用 SelectMany 来展平结果
自己加入
有些人不喜欢使用virtual ICollection<...>。他们更喜欢自己做(Group-)Join。在方法语法中,超过两个表的联接看起来很糟糕,幸运的是您不必进行联接。
var result = dbContext.Requests
.Where (request => request.ProjectCode == projectCode)
.OrderByDescending(request => request.RequestedOn)
.Select(request => new
{
// Request properties:
Id = request.Id,
Name = request.Name,
...
// Internal budgets:
InternalBudgets = dbContext.InternalBudgets
.Where(budget => budget.RequestId == request.Id)
.Select(budget => new
{
Id = budget.Id,
...
})
.ToList(),
// External budgets:
ExternalBudgets = dbContext.ExternalBudgets
.Where(budget => budget.RequestId == request.Id)
.Select(budget => new
{
Id = budget.Id,
...
BudgetTypes = dbContext.BudgetTypes
.Where(budgetType => budgetType.ExternalBudgetId == budget.Id)
.Select(budgetType => ...)
.ToList(),
})
.ToList(),
});
我不确定你能否说服你的项目负责人相信这种方法比使用虚拟 ICollections 的另一种方法更易读、更可重用、更容易测试和更改。