0

I have the following challenge to covert a T-SQL query to a Linq query in a C# project. As best as I can and for simplicity's sake, it is represented in the following script:

DECLARE @TableA TABLE (ID INT NOT NULL IDENTITY(1, 1)
                        , Data VARCHAR(10) NOT NULL)
DECLARE @TableDetailsA TABLE (ID INT NOT NULL IDENTITY(1, 1)
                                , TableAID INT NOT NULL
                                , TableBID INT NOT NULL)
DECLARE @TableDetailsB TABLE (ID INT NOT NULL IDENTITY(1, 1)
                                , TableAID INT NOT NULL
                                , TableBID INT NOT NULL)
DECLARE @TableB TABLE (ID INT NOT NULL IDENTITY(1, 1)
                        , Data VARCHAR(10) NOT NULL
                        , TableCID INT NOT NULL)
DECLARE @TableC TABLE (ID INT NOT NULL IDENTITY(1, 1)
                        , Data VARCHAR(10) NOT NULL)

INSERT INTO @TableA(Data)
VALUES ('Data set A')
INSERT INTO @TableC(Data)
VALUES ('Data set C')
INSERT INTO @TableB(Data, TableCID)
VALUES ('Data set B', 1)

--INSERT INTO @TableDetailsA(TableAID, TableBID)
--VALUES (1, 1)
INSERT INTO @TableDetailsB(TableAID, TableBID)
VALUES (1, 1)


SELECT A.Data AS [Data A]
        , B.Data AS [Data B]
        , C.Data AS [Data C]
FROM @TableA A
JOIN @TableDetailsA DA
    ON A.ID = DA.TableAID
JOIN @TableB B
    ON DA.TableBID = B.ID
JOIN @TableC C
    ON B.TableCID = C.ID
WHERE B.ID = 1
UNION
SELECT A.Data AS [Data A]
        , B.Data AS [Data B]
        , C.Data AS [Data C]
FROM @TableA A
JOIN @TableDetailsB BA
    ON A.ID = BA.TableAID
JOIN @TableB B
    ON BA.TableBID = B.ID
JOIN @TableC C
    ON B.TableCID = C.ID
WHERE B.ID = 1


SELECT A.Data AS [Data A]
        , B.Data AS [Data B]
        , C.Data AS [Data C]
FROM @TableA A
LEFT JOIN @TableDetailsA DA
    ON A.ID = DA.TableAID
LEFT JOIN @TableDetailsB DB
    ON A.ID = DB.TableAID
JOIN @TableB B
    ON B.ID = ISNULL(DA.TableBID, DB.TableBID)
JOIN @TableC C
    ON B.TableCID = C.ID
WHERE B.ID = 1

Notices that I have a many to many relationship between two tables, TableA and TableB through two different detail tables TableDetailsA and TableDetailsB. In this case TableDetailsA does not have data inserted into it.

So basically, using linq, in my C# project, I am able to replicate the union statement like so (ignore error in the linq if there are any...):

var firstQuery =    from ta in repo.TableA
                    join tda in repo.TableDetailsA
                        on ta.ID equals tda.TableAID
                    join tb in repo.TableB
                        on tb.ID equals tda.TableBID
                    join tc in repo.TableC
                        on tb.TableCID = tc.ID
                    select new
                            {
                                ta.Data
                                , tb.Data
                                , tc.Data
                            };

var secondQuery =   from ta in repo.TableA
                    join tdb in repo.TableDetailsB
                        on ta.ID equals tdb.TableAID
                    join tb in repo.TableB
                        on tb.ID equals tdb.TableBID
                    join tc in repo.TableC
                        on tb.TableCID = tc.ID
                    select new
                            {
                                ta.Data
                                , tb.Data
                                , tc.Data
                            };

var unionQuery = firstQuery.Union(secondQuery);

var data = unionQuery.ToList();

However, I don't know how to replicate the second SQL statement that uses the ISNULL(...) to join TableB to TableA, which I would like to be able to do as this seems more efficient and elegant and would require less variables to be declared in my code (although I know this can be done in one var but I like to keep it clear).

[EDIT]

Using the second query given in the answer by Cetin Basoz, I've managed to build the following queries that generate the result I'm looking for. However, I still would prefer to find a way to for Linq to SQL to generate the ISNULL(..., ...), as seen in my last select statement above.

var firstQuery = from ta in repo.TableA
                    from tda in ta.TableDetailsA.DefaultIfEmpty()
                    from tdb in ta.TableDetailsB.DefaultIfEmpty()
                    where ta.ID == 1
                    select new
                            {
                                TableAID = ta.ID
                                , TableBID = tda.TableB != null
                                                ? ns.TableB.ID
                                                : nsc.TableB.ID
                            };

var secondQuery = from fq in firstQuery
                    join ta in repo.TableA
                        on fq.TableAID equals ta.ID
                    join tb in repo.TableB
                        on fq.TableBID equals tb.ID
                    join tc in repo.TableC
                        on tb.TableCID  equals tc.ID
                    select new
                            {
                                TableAData = ta.Data
                                , TableBData = tb.Data
                                , TableCData = tc.Data
                            };

So, we are still waiting for our champion to unlock this deathly secret!

MacGyver
  • 11
  • 4
  • Are the answers here any good for you? http://stackoverflow.com/questions/413084/equivalent-of-sql-isnull-in-linq – Ilessa Oct 23 '15 at 14:40
  • Typically, a many-to-many relationship can be modeled using one junction table. E.g. TableDetailsA or TableDetailsB. Why do you have the relationship defined in both? Removing one of the tables will significantly reduce the complexity of the linq query. – Terence Oct 23 '15 at 14:59
  • @Ilessa Thanks for the link. Sadly, after trying out an idea I got from it, I still have not found a solution. Any other ideas? – MacGyver Oct 26 '15 at 07:21
  • @Terence Concerning the structure of the db, I strongly agree with you! However, this is out of my hands for now as I'm new on the project and the system is very old and painfully hard to change. Strangely, this kind of thing seems to be prevalent in many old systems that I have worked on and come across. I can only assume it was the practice of the day in the late 90's and early 2000's. In this case it is used for auditing purposes. However, I'm just looking for a Linq to SQL solution so that i don't have to duplicate code. – MacGyver Oct 26 '15 at 07:30

2 Answers2

0
var firstQuery = (from tda in TableDetailsA
                  where tda.TableB.ID == 1
                 select new
                 {
                   DataA = tda.TableA.Data,
                   DataB = tda.TableB.Data,
                   DataC = tda.TableB.TableC.Data
                 })
                 .Union(from tdb in TableDetailsB
                   where tdb.TableB.ID == 1
                   select new
                   {
                     DataA = tdb.TableA.Data,
                     DataB = tdb.TableB.Data,
                     DataC = tdb.TableB.TableC.Data
                    });


var secondQuery = from a in TableA
                  from tda in a.TableDetailsA.DefaultIfEmpty()
                  from tdb in a.TableDetailsB.DefaultIfEmpty()
                  select new {
                    DataA = a.Data,
                    DataB = tda.TableB.Data ??  tdb.TableB.Data,
                    DataC = tda.TableB.TableC.Data ??  tdb.TableB.TableC.Data
                  };
Cetin Basoz
  • 22,495
  • 3
  • 31
  • 39
  • Thanks for your answer. I like the second query you submitted and have used it and extended my original question with a version of your answer. However, I'm still looking for a way to generate the ISNULL(DA.TableBID, DB.TableBID) in the Linq. Concerning the first query, I'm not as keen for as I'm trying to avoid using a union statement. – MacGyver Oct 26 '15 at 11:00
  • tda.TableB.Data ?? tdb.TableB.Data is doing that, no? – Cetin Basoz Oct 26 '15 at 13:52
  • Initially the ?? was not helpful as i was getting an error. However, i found that casting the comparing values to Nullable int's, worked. I've posed an answer that produces the kind of SQL I'm happy with. Thanks for your help as this lead me in the right direction. – MacGyver Oct 27 '15 at 08:09
  • I see that you are mimicing the SQL in Linq which is not needed. Linq doesn't need those joins. – Cetin Basoz Oct 27 '15 at 08:45
0

Building on Cetin Basoz answer, I was able to find a solution, which I am satisfied with. It does not produce the ISNULL(DA.TableBID, DB.TableBID), that I was looking for, however it does produce the following in the join clause: CASE WHEN (DA.TableBID IS NULL) THEN DA.TableBID ELSE DB.TableBID END.

The key to unlock the question was an answer that i found in the second answer to this question where the tda.TableB.ID is cast to a nullable int type via the (int?) notation.

The code to build the Linq queries, looks like this:

var firstQuery = from ta in repo.TableA
                    from tda in ta.TableDetailsA.DefaultIfEmpty()
                    from tdb in ta.TableDetailsB.DefaultIfEmpty()
                    where ta.ID == 1
                    select new
                            {
                                TableAID = ta.ID
                                , TableBID = (int?)tda.TableB.ID ?? (int?)tdb.TableB.ID
                            };

var secondQuery = from fq in firstQuery
                    join ta in repo.TableA
                        on fq.TableAID equals ta.ID
                    join tb in repo.TableB
                        on fq.TableBID equals tb.ID
                    join tc in repo.TableC
                        on tb.TableCID equals tc.ID
                    select new
                            {
                                TableAData = ta.Data
                                , TableBData = tb.Data
                                , TableCData = tc.Data
                            };
Community
  • 1
  • 1
MacGyver
  • 11
  • 4