Alas, you forgot to tell us the classes that you have. You also didn't inform us about the relations between the tables. I'll have to guess from your usage. Next question you ask consider to provide this information.
It seems that you have a table with Requests
, and a table InternalBudgets
. There seems to be a one-to-many relation between Requests
and InternalBudgets
: every Request
has zero or more InternalBudgets
, every InternalBudget
belongs to exactly one Request
, namely the one that the foreign key refers to.
There seems to be a similar one-to-many between Requests
and ExternalBudgets
.
Finally there is also a one-to-many between ExternalBudgets
and ExternalBudgetTypes
. Personally I would expect a many-to-many relation: every ExternalBudgetType
is used by zero or more ExternalBudgets
. This doesn't change the answer drastically.
If you've followed the entity framework conventions, you'll have classes similar to the following:
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;}
}
The Internal and External budgets:
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;}
}
Finally the 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;}
}
Because you stuck to the conventions, this is all that entity framework needs to detect your tables, the relations between the tables and the primary and foreign keys
In entity framework the columns of the tables are represented by the non-virtual properties. The virtual properties represent the relations between the tables (one-to-many, many-to-many)
The foreign key is a column in the table. Hence it is non-virtual. The object that the foreign key refers to is not part of the table, hence if is virtual.
The "Many" side in xxx-to-many relations should be implemented with ICollection<...>
, not with IList<...>
. Ilist provide functionality that is undefined in databases:
Requests fetchedRequest = ...
InternalBudget internalBudget = fetchedRequest[3];
Can you say which internalBudget has list index [3]?
Better not provide functionality that is not defined. ICollection<...>
give you the possibility to Add / Delete / Count
, all functionalities that have a defined meaning in a database.
One final tip: in your identifiers use plural nouns to refer to sequences; use singular nouns to refer to elements in these sequences. That makes LINQ statements easier to understand.
Back to your question
Requirement Given a projectCode, give me the requests that have this projectCode, with several properties of their Internal and External Budgets
Easy method: use the virtual 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(),
});
In words: from all Requests, keep only those Request that have a value of property ProjectCode equal to projectCode. Order the remaining Requests by descending value of property RequestedOn. Finally Select several properties of the Request, its internal budgets and its external budgets.
Entity framework knows the relations between the tables, and knows how to translate the use of your virtual properties in the correct (Group-)join.
Note: the result differs slightly from your solution. Your solution would give:
Request InternalBudget (for simplicity: leave out Externalbudget / type
01 10
02 12
04 11
01 14
01 15
02 13
My Solution gives:
- Request 01 with its InternalBudgets {10, 14, 15}
- Request 02 with its InternalBudgets {12, 13}
- Request 05 without any InternalBudgets
- Request 04 with its InternalBudget {11}
IMHO this is much more intuitive and more efficient: the properties of each Request items are transferred only once.
Note that you will also get the Requests that have no Internal/External budgets or types.
If you don't want them, use a Where(... => ...Count != 0)
to filter them out.
If you don't want "Items with their Sub-items" (which is in fact a GroupJoin), but a standard join, use SelectMany
to flatten the result
Do the Join yourself
Some people don't like to use the virtual ICollection<...>
. They prefer to do the (Group-)Joins themselves. In method syntax Joins with more than two tables look horrible, luckily you don't have to do a 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(),
});
I'm not sure if you can convince your project leader that this method is better readable, more reusable, easier to test and change than the other one with the virtual ICollections.