So I have three tables:
CREATE TABLE tblUser
(
[pkUserID] [int] IDENTITY(1,1) NOT NULL,
[userName] [varchar](150) NULL,
[fkCompanyID] [int] NOT NULL
)
CREATE TABLE tblCompany
(
[pkCompanyID] [int] IDENTITY(1,1) NOT NULL,
[name] [varchar](255) NULL
)
CREATE TABLE tblSystem
(
[pkSystemID] [int] IDENTITY(1,1) NOT NULL,
[systemName] [varchar](150) NULL,
[fkCompanyID] [int] NULL
)
These are my data transfer objects:
public class SystemDTO
{
public int pkSystemId { get; set; }
public string Name { get; set; }
public int? fkCompanyId { get; set; }
}
public class CompanyDTO
{
public int pkCompanyId { get; set; }
public string Name { get; set; }
public IEnumerable<SystemDTO> Systems { get; set; }
}
public class UserDTO
{
public int pkUserId { get; set; }
public string Name { get; set; }
public IEnumerable<CompanyDTO> Companies { get; set; }
}
This is the Linq query I am trying to do:
var result= (
from user in db.tblUsers
select new UserDTO()
{
pkUserId=user.pkUserID,
Name=user.realName,
Companies=
(
from company in db.tblCompanies
where user.fkCompanyID==company.pkCompanyID
select new CompanyDTO()
{
pkCompanyId=company.pkCompanyID,
Name=company.name,
Systems=
(
from system in db.tblSystem
where system.fkCompanyId==company.pkCompanyId
select new SystemDTO()
{
pkSystemId=system.pkSystemID,
Name=system.systemName,
fkCompanyId=system.fkCompanyID
}
)
}
)
}
).ToList();
The problem with this query is that the most inner query
from system in db.tblSystem
where system.fkCompanyId==company.pkCompanyId
select new SystemDTO()
{
pkSystemId=system.pkSystemID,
Name=system.systemName,
fkCompanyId=system.fkCompanyID
}
cause linq to translate the sql to one select per entity. I know that I can skip the select and loop the result and set the property. Like this:
var lsSystem= db.tblSystem.Select (s =>new SystemDTO(){pkSystemId=s.pkSystemID,Name=s.systemName,fkCompanyId=s.fkCompanyID}).ToList();
foreach (var user in result)
{
foreach (var company in user.Companies)
{
company.Systems=lsSystem.Where (a =>a.fkCompanyId==company.pkCompanyId).ToList();
}
}
This will cause linq to do two select and not one per entity. So now to my questions. Is there any another way of doing this? Can I populate the inner collection in another way?
Any suggesting will be appreciated
EDIT
A suggesting was to use loadoption. I can't find a loadoption between system and company. But I can include the loadoption between. Company and user like this:
var option=new DataLoadOptions();
option.LoadWith<tblCompany>(a=>a.fkCompanytblUsers);
db.LoadOptions=option;
But this has no effect on the query it is still translated to many selects
EDIT2
As said in the answers comments the load options do not apply for this kinda linq query.
I figure it out myself. The best way what i can see is to do it like this (but again if anyone else have a better suggestion please add them):
And then use the lsSystem in the linq query like this:
This will result in two select statements one for system and one for users to companies
Ok, here is a proposal that you an use to get everything in a single query. I'll simplify the data model for demonstration purposes:
That query will give you all three tree levels at once. It will be quite efficient. But the data will be redundant. You need to do some client processing to make it usable again:
You need to remove the redundancies by doing groupings. In other words: The query has denormalized the data and we need to normalize it again.
This approach is very cumbersome but fast.
Have you looked into the
LoadOptions
and more particularLoadWith
.This will stop Linq2sql from lazy loading and will do eager loading.
Simple example here: http://davidhayden.com/blog/dave/archive/2007/08/05/LINQToSQLLazyLoadingPropertiesSpecifyingPreFetchWhenNeededPerformance.aspx