Reputation: 1137
I'm looking for some advice to tackle this problem. We have this large SQL query with many left outer joins.
Here is the query:
Select Top 1 WA.BHATUS_ID, WA.[CRMBHG_WORKINGACCOUNT_ID], BA.BHATUS_ID, BA.[CRMBHG_BILLINGACCOUNT_ID], BAD.BHATUS_ID, [BAD].[CRMBHG_BILLINGADDRESS_ID], SAD.BHATUS_ID, [SAD].[CRMBHG_SERVICEADDRESS_ID], C.BHATUS_ID, [C].[CRMBHG_CONTACT_ID]
From BillingHiBHory As BH With (NoLock)
Left Outer Join WorkingAccount As WA With (NoLock)
On WA.UniversalAccountId = BH.UniversalAccountId
And WA.BillingRegion = BH.BillingRegion
Left Outer Join BillingAccount As BA With (NoLock)
On BA.UniversalAccountId = BH.UniversalAccountId
And BA.BillingRegion = BH.BillingRegion
Left Outer Join BillingAddress As BAD With (NoLock)
On BAD.Grouping = BH.Grouping
And BAD.SubGrouping = BH.SubGrouping
And BAD.BillingRegion = BH.BillingRegion
Left Outer Join dbo.SubScriberInfo SUB With (NoLock)
On SUB.Grouping = BH.Grouping
And SUB.SubGrouping = BH.SubGrouping
And SUB.BillingRegion = BH.BillingRegion
Left Outer Join ServiceAddress As SAD With (NoLock)
On SAD.ControlNumberAssigned = SUB.ServceControlNum
And SAD.BillingRegion = SUB.BillingRegion
Left Outer Join Contact As C With (NoLock)
On BH.AccountNum = C.AccountNum
And BH.BillingRegion = C.BillingRegion
Where BH.Grouping = @Grouping
And BH.SubGrouping = @SubGrouping
Order By BH.SequenceOrder, WA.StatusDate Desc, BA.StatusDate Desc, BAD.StatusDate Desc, SAD.StatusDate Desc, C.StatusDate Desc
Here's my first crack at the LINQ syntax...
var statusIds = (
from wa in db.WorkingAccount
from ba in db.BillingAccount
from bad in db.BillingAddress
from si in db.SubscriberInfo
from sad in db.ServiceAddress
from c in db.CRMSTG_CONTACT
where wa.UniversalAccountId == UniversalSubscriberId && wa.BillingRegion == BillingRegion
where ba.UniversalAccountId == UniversalSubscriberId && ba.BillingRegion == BillingRegion
where bad.Grouping == Grouping && bad.SubGrouping == SubGrouping && bad.BillingRegion == BillingRegion
where si.Grouping == Grouping && si.SubGrouping == SubGrouping && si.BillingRegion == BillingRegion
where sad.ControlNumberAssigned == si.ServceControlNum && sad.BillingRegion == BillingRegion
where c.AccountNum == AccountNumber && c.BillingRegion == BillingRegion
orderby wa.StatusDate descending, ba.StatusDate descending, bad.StatusDate descending, sad.StatusDate descending, c.StatusDate descending
select new
{
workingAddressStatusId = wa.StatusId,
billingAccountStatusId = ba.StatusId,
billingAddressStatusId = bad.StatusId,
serviceAddressStatusId = sad.StatusId,
contactStatusId = c.StatusDate
}
).FirstOrDefault();
This mostly works but feels like a big hack. Where this falls down is when one of the StatusId values comes back as null and statusIds ends up being null as well.
I'm sure there's a better way to do this. I'm open to suggestions.
As a note, yes, I'm aware of the risks associated with "With (NoLock)" option on the TSQL, we're a secondary app on a very busy database and we don't want to create blocking chains as billing and support staff do their work. For what we're doing, the NoLock is a non-issue. It's not like we're doing multiple unions of queries with aggregators. ;)
Upvotes: 1
Views: 240
Reputation: 205599
Well, it's true that you have a lot of left outer joins, and also that left outer joins are not naturally supported in LINQ, but the pattern is well known - take a look at Left Outer Join and Composite Keys sections in join clause (C# Reference), so all you need is to follow the pattern:
from bh in db.BillingHiBHory
join wa in db.WorkingAccount
on new { bh.UniversalAccountId, bh.BillingRegion }
equals new { wa.UniversalAccountId, wa.BillingRegion }
into bh_wa from wa in bh_wa.DefaultIfEmpty() // this turns the above (inner) join into left outer join
join ba in db.BillingAccount
on new { bh.UniversalAccountId, bh.BillingRegion }
equals new { ba.UniversalAccountId, ba.BillingRegion }
into bh_ba from ba in bh_ba.DefaultIfEmpty()
join bad in db.BillingAddress
on new { bh.Grouping, bh.SubGrouping, bh.BillingRegion }
equals new { bad.Grouping, bad.SubGrouping, bad.BillingRegion }
into bh_bad from bad in bh_bad.DefaultIfEmpty()
join si in db.SubscriberInfo
on new { bh.Grouping, bh.SubGrouping, bh.BillingRegion }
equals new { si.Grouping, si.SubGrouping, si.BillingRegion }
into bh_si from si in bh_si.DefaultIfEmpty()
join sad in db.ServiceAddress
on new { si.ServceControlNum, si.BillingRegion }
equals new { sad.ControlNumberAssigned, sad.BillingRegion }
into si_sad from sad in si_sad.DefaultIfEmpty()
join c in db.CRMSTG_CONTACT
on new { bh.AccountNum, bh.BillingRegion }
equals new { c.AccountNum, c.BillingRegion }
into bh_c from c in bh_c.DefaultIfEmpty()
where bh.Grouping == Grouping
&& bh.SubGrouping == SubGrouping
// ... (the rest)
One detail to consider is to include nullable cast when selecting non nullable fields from the right
part of the left outer join. For instance, if the StatusId
type is int
, then you need to use something like this:
select new
{
workingAddressStatusId = (int?)wa.StatusId,
// ...
}
Upvotes: 1