EF: selecting all the relations of a relation

  • Thread starter Thread starter Markla
  • Start date Start date
M

Markla

Hi, I have been using EF since it's release, for the most part, it's saved me
thousands of lines of code. (This is a repost at the request of MS)

I've a scenario I havn't been able to solve with well performing code.

Imagine a model of a phone book, with Companies, Departments and People. A
company has an ID and name, department has an ID and name, and a person has
an ID and name (keeping it simple).

How can I write an entities statement which will return a collections of all
people under a company (regardless of department)?

I considered using something like this:

context.People.Where(p=>p.Department.Company.CompanyId == CompanyIdSearched)

However, my understanding is this goes back to the database to pull the data?

I need to query based on the in-memory data, so I can include data which has
changed but not yet been saved (eg. to check if I've already added a Person
in an import).

What's the most efficient way to query the in-memory context, to get all
People who work for a Company?

Thanks in advance :-)

+M
 
The entity framework, linq, dlinq, datasets in the .NET framework are
all "hacks" made for idiots that have no idea about architecture,
design, databases, application requirements process. Instead, the
idiots write their applications using these "hacks" so that after a
couple of months, it displays "Application not Responding" and "Server
timeout" messages.

Get a beginners level book on application architecture, database
design, and application design. A hybrid developer (one that knows
about writing applications and SQL) is much better than an idiot that
uses ORMs and "RAD" tools.
 
Hi Mark,

If the desired colum is not in the Person table, but in another table that
has a 1-n(n-n) relationship with the Person table. The entity framework
needs to load that table into the memory, otherwise it cannot perform the
query. So the question really depends on how we design the database. Based
on my perspective, since the Person can only have one company to work with,
why not record the CompanyID in the Person table. So we can perform the
query like this,

context.People.Where(p=>p.CompanyId == CompanyIdSearched)

What do you think about?


Best regards,
Colbert Zhou
Microsoft Online Community Support

Delighting our customers is our #1 priority. We welcome your comments and
suggestions about how we can improve the support we provide to you. Please
feel free to let my manager know what you think of the level of service
provided. You can send feedback directly to my manager at:
(e-mail address removed).
 
Thanks for your response, although I'm left a little confused why something
like this is impossible in EF without redundant database changes to support
it. The problem with storing the CompanyId in the person table, is you're
working away from a normalised database, putting risk on developers/app
owners. What I read in your response, is that because EF can't handle it, MS
want the developer to compensate by having to write more code + denormalise
the database, soley because EF can't handle it!

So in answer, I think it's a bad idea!

Surely there's a way?

Perhaps do a .Join()?

Perhaps a Linq query which returns a collection of collections, which can
get union'd together?

Something like this perhaps?
IEnumerable<IQueryable<Person>> rss = this.Depts.Select(c =>
c.People.AsQueryable());
if (rss.Count() == 0)
return new Person[0];
List<Person> ret
= rss.Aggregate((workingset, next) =>
workingset.Concat(next)).ToList();

As much as I'm thankful EF has been released, surely there's an efficient
way to do this, without denormalising the database just to compensate for
EF?!?

Thanks,
+M
 
Hi Mark,

I am very sorry for the late response! I think maybe I misunderstood the
issue here. Now I can see the main concern here is, when we add, modify,
delete some data from the dataContext and do not want to submit changes
immediately, the query will be still performed on the target database
again. So, it will not return the local new added object, right?

Actually, this is how the Entity Framework behaves now, that all query is
performed by the target database. Currently, the only way to get the
in-memory new added, modified, deleted data is using the
ObjectStateManager. Danny from the Entity Framework product group has
created a extension method to do such a in-memory query. He blogs this
topic in the following thread. Please have a read and let me know if this
addresses your main concern,
http://blogs.msdn.com/dsimmons/archive/2009/02/21/local-queries.aspx

Again, sorry for the inconvenience taken to you.


Best regards,
Colbert Zhou
Microsoft Newsgroup Support Team.
 
Hello Mark,

Does the last reply address the issue? If you have any future questions or
concerns, please let me know. I will try my best to research and provide
future support here.


Best regards,
Colbert Zhou
Microsoft Online Community Support

Delighting our customers is our #1 priority. We welcome your comments and
suggestions about how we can improve the support we provide to you. Please
feel free to let my manager know what you think of the level of service
provided.
 
Thank you Colbert, that's a good post, it helps solve a few issues, and is
half of my question.

Let me see if I can re-ask the other half clearly, using the original
example again...

What is the most efficient way to construct the code, so I can call
"Companies.People" where the People property returns all people working for a
company (regardless of department)? And without adding a CompanyId to the
Person table.

The approaches I've tried have seemed clumsy and inefficient- I'm hoping
this has been asked before, and there's a great fast approach to write the
code, to return all people for a company. I don't want to have a round-trip
for each department either- I want EF to see that a SQL join is the most
efficient way, and return the results in one query...

Thanks,
+Mark
 
Hello Mark,

Why do you think the direct query clumsy and inefficient? Only after
clarifying the performance bottleneck, we can do some specified research
and find dedicated solution for this.

Currently, the following is my research result,

Based on your description, I create the following three tables and test in
my side,

People,
PeopleId
PeopleName
DepartmentId

Department,
DepartmentId
DepartmentName
CompanyId

Company
CompanyId
CompanyName

Company.CompanyId links to Department.CompanyId and Department.DepartmentId
links to People.DepartmentId. Is the the same schema as you describe? If
that is the case, I think, the Department acts as an bridge between People
and Company. We cannot get a specified company's whole people without
indirect querying the Department table. The application itself will not
know which people belongs to which company without the department
information.

--------------------------------
Let's go back to the question how to perform such a query,

1. The first way is as you described in your original question,
var people = from p in context.People
where p.Department.Company.CompanyId == 1
select p;

foreach (var p in people)
{
Console.WriteLine(p.PeopleID);
Console.WriteLine(p.PeopleName);
}
This way results the following SQL command to be executed and retrieve the
desired results. Actually, I do not think this way is inefficient.

SELECT
1 AS [C1],
[Extent1].[PeopleID] AS [PeopleID],
[Extent1].[PeopleName] AS [PeopleName],
[Extent1].[DepartmentId] AS [DepartmentId]
FROM [dbo].[People] AS [Extent1]
INNER JOIN [dbo].[Department] AS [Extent2] ON [Extent1].[DepartmentId] =
[Extent2].[DepartmentId]
WHERE 1 = [Extent2].[CompanyID]


2. I am not sure if you are trying to look after the second way I am going
to describe. Actually, we can query about the Company and use Eager Loading
to load all departments and people into the memory. After that iterating
through the results will not cause additional query to database. The codes
look like,
var company = (from c in
context.Company.Include("Department.People")
where c.CompanyId == 1
select c).First();

foreach (var d in company.Department)
{
foreach (var p in d.People)
{
Console.WriteLine(p.PeopleID);
Console.WriteLine(p.PeopleName);
}
}
And this way generate the following SQL command to retrieve all information
regarding to a company's department and people.
SELECT
[Project3].[CompanyId] AS [CompanyId],
[Project3].[CompanyName] AS [CompanyName],
[Project3].[C1] AS [C1],
[Project3].[C4] AS [C2],
[Project3].[DepartmentId] AS [DepartmentId],
[Project3].[DepartmentName] AS [DepartmentName],
[Project3].[CompanyId1] AS [CompanyId1],
[Project3].[C3] AS [C3],
[Project3].[C2] AS [C4],
[Project3].[PeopleID] AS [PeopleID],
[Project3].[PeopleName] AS [PeopleName],
[Project3].[DepartmentId1] AS [DepartmentId1]
FROM ( SELECT
[Limit1].[CompanyId] AS [CompanyId],
[Limit1].[CompanyName] AS [CompanyName],
[Limit1].[C1] AS [C1],
[Project2].[DepartmentId] AS [DepartmentId],
[Project2].[DepartmentName] AS [DepartmentName],
[Project2].[CompanyID] AS [CompanyId1],
[Project2].[PeopleID] AS [PeopleID],
[Project2].[PeopleName] AS [PeopleName],
[Project2].[DepartmentId1] AS [DepartmentId1],
CASE WHEN ([Project2].[C1] IS NULL) THEN CAST(NULL AS int) ELSE CASE WHEN
([Project2].[PeopleID] IS NULL) THEN CAST(NULL AS int) ELSE 1 END END AS
[C2],
CASE WHEN ([Project2].[C1] IS NULL) THEN CAST(NULL AS int) ELSE CASE WHEN
([Project2].[PeopleID] IS NULL) THEN CAST(NULL AS int) ELSE 1 END END AS
[C3],
[Project2].[C1] AS [C4]
FROM (SELECT TOP (1)
[Extent1].[CompanyId] AS [CompanyId],
[Extent1].[CompanyName] AS [CompanyName],
1 AS [C1]
FROM [dbo].[Company] AS [Extent1]
WHERE 1 = [Extent1].[CompanyId] ) AS [Limit1]
LEFT OUTER JOIN (SELECT
[Extent2].[DepartmentId] AS [DepartmentId],
[Extent2].[DepartmentName] AS [DepartmentName],
[Extent2].[CompanyID] AS [CompanyID],
[Extent3].[PeopleID] AS [PeopleID],
[Extent3].[PeopleName] AS [PeopleName],
[Extent3].[DepartmentId] AS [DepartmentId1],
1 AS [C1]
FROM [dbo].[Department] AS [Extent2]
LEFT OUTER JOIN [dbo].[People] AS [Extent3] ON [Extent2].[DepartmentId] =
[Extent3].[DepartmentId] ) AS [Project2] ON [Limit1].[CompanyId] =
[Project2].[CompanyID]
) AS [Project3]
ORDER BY [Project3].[CompanyId] ASC, [Project3].[C4] ASC,
[Project3].[DepartmentId] ASC, [Project3].[C3] ASC

To know more about the Eager loading, please have a look at the following
artilces,
http://blogs.msdn.com/alexj/archive/2009/03/23/tip-6-when-and-how-to-use-eag
er-loading.aspx
http://blogs.msdn.com/adonet/archive/2008/10/07/migrating-from-linq-to-sql-t
o-entity-framework-eager-loading.aspx

Have a nice day Sir!


Best regards,
Colbert Zhou
Microsoft Newsgroup Support Team
 
Hi,

Thanks, that's great info! (The inefficient approach was a poor first try
using code I didn't include above because it was so inefficient- hence I
started studying alternatives)

The first approach looks to be a good choice.

Is that equivalent to this:

var people = context.People.Where(p=>p.Department.Company.CompanyId == 1);

I'm still coming to terms with the consequences of semantic choices, on the
surface it looks equivalent, wondering if there's some consequence I havn't
picked up on.

Thanks again for your responses, they're greatly appreciated.

Cheers,
+M

So I studied alternate approaches, and came up with the suggestions above-
although there are wiser people who may know more.

Colbert Zhou said:
Hello Mark,

Why do you think the direct query clumsy and inefficient? Only after
clarifying the performance bottleneck, we can do some specified research
and find dedicated solution for this.

Currently, the following is my research result,

Based on your description, I create the following three tables and test in
my side,

People,
PeopleId
PeopleName
DepartmentId

Department,
DepartmentId
DepartmentName
CompanyId

Company
CompanyId
CompanyName

Company.CompanyId links to Department.CompanyId and Department.DepartmentId
links to People.DepartmentId. Is the the same schema as you describe? If
that is the case, I think, the Department acts as an bridge between People
and Company. We cannot get a specified company's whole people without
indirect querying the Department table. The application itself will not
know which people belongs to which company without the department
information.

--------------------------------
Let's go back to the question how to perform such a query,

1. The first way is as you described in your original question,
var people = from p in context.People
where p.Department.Company.CompanyId == 1
select p;

foreach (var p in people)
{
Console.WriteLine(p.PeopleID);
Console.WriteLine(p.PeopleName);
}
This way results the following SQL command to be executed and retrieve the
desired results. Actually, I do not think this way is inefficient.

SELECT
1 AS [C1],
[Extent1].[PeopleID] AS [PeopleID],
[Extent1].[PeopleName] AS [PeopleName],
[Extent1].[DepartmentId] AS [DepartmentId]
FROM [dbo].[People] AS [Extent1]
INNER JOIN [dbo].[Department] AS [Extent2] ON [Extent1].[DepartmentId] =
[Extent2].[DepartmentId]
WHERE 1 = [Extent2].[CompanyID]


2. I am not sure if you are trying to look after the second way I am going
to describe. Actually, we can query about the Company and use Eager Loading
to load all departments and people into the memory. After that iterating
through the results will not cause additional query to database. The codes
look like,
var company = (from c in
context.Company.Include("Department.People")
where c.CompanyId == 1
select c).First();

foreach (var d in company.Department)
{
foreach (var p in d.People)
{
Console.WriteLine(p.PeopleID);
Console.WriteLine(p.PeopleName);
}
}
And this way generate the following SQL command to retrieve all information
regarding to a company's department and people.
SELECT
[Project3].[CompanyId] AS [CompanyId],
[Project3].[CompanyName] AS [CompanyName],
[Project3].[C1] AS [C1],
[Project3].[C4] AS [C2],
[Project3].[DepartmentId] AS [DepartmentId],
[Project3].[DepartmentName] AS [DepartmentName],
[Project3].[CompanyId1] AS [CompanyId1],
[Project3].[C3] AS [C3],
[Project3].[C2] AS [C4],
[Project3].[PeopleID] AS [PeopleID],
[Project3].[PeopleName] AS [PeopleName],
[Project3].[DepartmentId1] AS [DepartmentId1]
FROM ( SELECT
[Limit1].[CompanyId] AS [CompanyId],
[Limit1].[CompanyName] AS [CompanyName],
[Limit1].[C1] AS [C1],
[Project2].[DepartmentId] AS [DepartmentId],
[Project2].[DepartmentName] AS [DepartmentName],
[Project2].[CompanyID] AS [CompanyId1],
[Project2].[PeopleID] AS [PeopleID],
[Project2].[PeopleName] AS [PeopleName],
[Project2].[DepartmentId1] AS [DepartmentId1],
CASE WHEN ([Project2].[C1] IS NULL) THEN CAST(NULL AS int) ELSE CASE WHEN
([Project2].[PeopleID] IS NULL) THEN CAST(NULL AS int) ELSE 1 END END AS
[C2],
CASE WHEN ([Project2].[C1] IS NULL) THEN CAST(NULL AS int) ELSE CASE WHEN
([Project2].[PeopleID] IS NULL) THEN CAST(NULL AS int) ELSE 1 END END AS
[C3],
[Project2].[C1] AS [C4]
FROM (SELECT TOP (1)
[Extent1].[CompanyId] AS [CompanyId],
[Extent1].[CompanyName] AS [CompanyName],
1 AS [C1]
FROM [dbo].[Company] AS [Extent1]
WHERE 1 = [Extent1].[CompanyId] ) AS [Limit1]
LEFT OUTER JOIN (SELECT
[Extent2].[DepartmentId] AS [DepartmentId],
[Extent2].[DepartmentName] AS [DepartmentName],
[Extent2].[CompanyID] AS [CompanyID],
[Extent3].[PeopleID] AS [PeopleID],
[Extent3].[PeopleName] AS [PeopleName],
[Extent3].[DepartmentId] AS [DepartmentId1],
1 AS [C1]
FROM [dbo].[Department] AS [Extent2]
LEFT OUTER JOIN [dbo].[People] AS [Extent3] ON [Extent2].[DepartmentId] =
[Extent3].[DepartmentId] ) AS [Project2] ON [Limit1].[CompanyId] =
[Project2].[CompanyID]
) AS [Project3]
ORDER BY [Project3].[CompanyId] ASC, [Project3].[C4] ASC,
[Project3].[DepartmentId] ASC, [Project3].[C3] ASC

To know more about the Eager loading, please have a look at the following
artilces,
http://blogs.msdn.com/alexj/archive/2009/03/23/tip-6-when-and-how-to-use-eag
er-loading.aspx
http://blogs.msdn.com/adonet/archive/2008/10/07/migrating-from-linq-to-sql-t
o-entity-framework-eager-loading.aspx

Have a nice day Sir!


Best regards,
Colbert Zhou
Microsoft Newsgroup Support Team
 
Hi Mark,

Yes. The statement in my post equals to this one. Actually, these two kinds
of syntaxes are same. We call the first one "Query Syntax" and the second
one "Method Syntax". Here is the documentation for this topic,
http://msdn.microsoft.com/en-us/library/bb397947.aspx

If you have any future questions or concerns, you are welcome to post here.
:-) Have a nice day.


Best regards,
Colbert Zhou
Microsoft Newsgroup Support Team
 
Back
Top