?
Solved

Advanced LINQ Query

Posted on 2011-03-09
5
Medium Priority
?
600 Views
Last Modified: 2013-11-11
I need some help with an advanced LINQ query, to be executed as Linq to SQL (SQL Server), in C# using ASP.NET 4.0.
To boil it down to the essential problem, I'll use the following table structure:

3 tables:  Mothers, Daughters, Sons

Columns in each table:  
Mothers: MotherId, FirstName, LastName
Daughters: DaughterId, MotherId, int Age
Sons: SonId, MotherId, int Height

Each Daughter belongs to a Mother (using MotherId foreign key).
Each Son belongs to a Mother (using MotherId foreign key).
Each Mother can have zero or more Sons and zero or more Daughters.

I'm trying to write a query that will return the following:
Give me all Mothers with FirstName "Mary" who has:
- a Daughter of Age 21 (or no daughters),
- or a son with Height 72 inches (or no sons).
It's OK to have both matching Daughter(s) and Son(s).

In other words, if the Mother has any daughters, at least one of the daughters must be 21.
And if the Mother has any sons, at least one of the sons must be 72 inches.

In the result set, return all the matching Sons and Daughters along with the Mother, with each Mother/Daughter/Son triplet in a flattened record.  

Also the query needs to be efficient, because we have millions of Mothers/Sons/Daughters in the database.


I have tried multiple approaches using LINQ expression syntax and lambda expressions / extension methods, but I either am only getting the Mothers that have both a matching son and daughter (which is not necessary), or I am getting too many Mothers.


So here's some sample data and the desired output.

Mothers:
MotherId 1 FirstName "Mary" LastName "Thompson"
MotherId 2 FirstName "Mary" LastName "Jones"
MotherId 3 FirstName "Mary" LastName "Smith"
MotherId 4 FirstName "Red" LastName "Herring"
      
Daughters
DaughterId 1 MotherId 1 Age 21
DaughterId 2 MotherId 3 Age 15
DaughterId 3 MotherId 4 Age 21

Sons
SonId 1 MotherId 1 Height 72
SonId 2 MotherId 1 Height 66
SonId 3 MotherId 2 Height 72
SonId 4 MotherId 2 Height 66
SonId 5 MotherId 3 Height 72


Desired Results (format is not important, just need to return these objects):
MotherId 1 "Mary Thompson" / DaughterId 1 Age 21 / SonId 1 Height 72
MotherId 2 "Mary Jones" / nulls for Daughter / SonId 3 Height 72


0
Comment
Question by:jblindberg
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 3
  • 2
5 Comments
 
LVL 63

Expert Comment

by:Fernando Soto
ID: 35091841
Hi jblindberg;

The Following code snippet I believe will do what you want. Also the results you posted is missing one set of values, it should be:

  1 Mary  Thompson   1  21   1  72
  2 Mary         Jones              3  72
  3 Mary         Smith              5  72

Open in new window


DataClasses1DataContext db = new DataClasses1DataContext( );

var results = from m in db.Mothers
              where m.FirstName == "Mary"
              where m.Daughters.Any( d => d.Age == 21 ) || m.Sons.Any( s => s.Height == 72 )
              let did = m.Daughters.Where( d => d.Age == 21 ).First( ).DaughterId
              let age = m.Daughters.Where( d => d.Age == 21 ).First( ).Age
              let sid = m.Sons.Where( s => s.Height == 72 ).First( ).SonId
              let height = m.Sons.Where( s => s.Height == 72 ).First( ).Height
              select new
              {
                  MotherId = m.MotherId,
                  MotherFirstName = m.FirstName,
                  MotherLastName = m.LastName,
                  DaughterId = (did == null)? 0 : did,
                  Age = (age == null)? 0 : age,
                  SonId = (sid == null)? 0 : sid,
                  Height = (height == null)? 0 : height
              };

foreach( var r in results )
{
    string output = String.Format( "{0,3}{1,5}{2,10}{3,4}{4,4}{5,4}{6,4}", r.MotherId, r.MotherFirstName, r.MotherLastName,
        ( r.DaughterId == 0 ) ? " " : r.DaughterId.ToString( ), ( r.Age == 0 ) ? " " : r.Age.ToString( ),
        ( r.SonId == 0 ) ? " " : r.SonId.ToString( ), ( r.Height == 0 ) ? " " : r.Height.ToString( ) );
    Console.WriteLine( output );
}

Open in new window


Fernando
0
 

Author Comment

by:jblindberg
ID: 35091887
Fernando,

Thanks for the answer.  But actually I don't want the "3 Mary         Smith              5  72" in the result set.  Mother 3 has a Daughter which is the wrong Age, and no Daughter of the correct Age.  So that disqualifies Mother 3 from the desired results.

That's what I was trying to say with this part of the problem statement:
In other words, if the Mother has any daughters, at least one of the daughters must be 21.
And if the Mother has any sons, at least one of the sons must be 72 inches.


And that's what makes this a tricky query to generate.

Jeff
0
 
LVL 63

Accepted Solution

by:
Fernando Soto earned 2000 total points
ID: 35096708
Hi Jeff;

Seeming I misunderstood the question, try it like this then :

DataClasses1DataContext db = new DataClasses1DataContext( );

var results = from m in db.Mothers
              where m.FirstName == "Mary"
              where (m.Daughters.Count == 0  || m.Daughters.Any( d => d.Age == 21 )) && 
                    (m.Sons.Count == 0 || m.Sons.Any( s => s.Height == 72 ))
              let did = m.Daughters.Where( d => d.Age == 21 ).First( ).DaughterId
              let age = m.Daughters.Where( d => d.Age == 21 ).First( ).Age
              let sid = m.Sons.Where( s => s.Height == 72 ).First( ).SonId
              let height = m.Sons.Where( s => s.Height == 72 ).First( ).Height
              select new
              {
                  MotherId = m.MotherId,
                  MotherFirstName = m.FirstName,
                  MotherLastName = m.LastName,
                  DaughterId = (did == null)? 0 : did,
                  Age = (age == null)? 0 : age,
                  SonId = (sid == null)? 0 : sid,
                  Height = (height == null)? 0 : height
              };

foreach( var r in results )
{
    string output = String.Format( "{0,3}{1,5}{2,10}{3,4}{4,4}{5,4}{6,4}", r.MotherId, r.MotherFirstName, r.MotherLastName,
        ( r.DaughterId == 0 ) ? " " : r.DaughterId.ToString( ), ( r.Age == 0 ) ? " " : r.Age.ToString( ),
        ( r.SonId == 0 ) ? " " : r.SonId.ToString( ), ( r.Height == 0 ) ? " " : r.Height.ToString( ) );
    Console.WriteLine( output );
}

Open in new window


The results should then be the following:

  1 Mary  Thompson   1  21   1  72
  2 Mary     Jones           3  72

Open in new window


Fernando
0
 

Author Comment

by:jblindberg
ID: 35103061
Fernando,

Thanks, that solution worked.  I was missing the Any() methods, and the "let ... Where(...).First()" statements -- those were the key to making it work where my attempts failed.

Now I just need to partially turn this into extension method calls to fit in with my existing code (because of the way it builds the query dynamically).  I'll use LinqPad as a tool to help with this, by looking at the lambda pane to see how your query was converted into method calls.

Thanks!
Jeff
0
 
LVL 63

Expert Comment

by:Fernando Soto
ID: 35103974
Not a problem Jeff; always glad to help.
0

Featured Post

Give Your Engineering Team a Productivity Boost

Learn why container technology is so powerful and how it can provide your team with productivity gains and other benefits.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Today I had a very interesting conundrum that had to get solved quickly. Needless to say, it wasn't resolved quickly because when we needed it we were very rushed, but as soon as the conference call was over and I took a step back I saw the correct …
The article shows the basic steps of integrating an HTML theme template into an ASP.NET MVC project
Add bar graphs to Access queries using Unicode block characters. Graphs appear on every record in the color you want. Give life to numbers. Hopes this gives you ideas on visualizing your data in new ways ~ Create a calculated field in a query: …
In this video you will find out how to export Office 365 mailboxes using the built in eDiscovery tool. Bear in mind that although this method might be useful in some cases, using PST files as Office 365 backup is troublesome in a long run (more on t…
Suggested Courses

765 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question