How to do multiple left outer joins in a Linq Query? - linq

A little background on the query below. Cell has a 1:M to Container and a 1:M with Printer. I want a query that will retrieve all Cells and associated containers, if they exist, and associated printers, if they exist. Essentially I want to do a left outer join on both tables. Here is the query I have:
var query = from cell in Cell
join container in Container.Where (row => row.SerialNumber == "1102141") on cell.CellID equals container.CellID
into containers
join printer in Printer.Where (row => row.Name == "PG10RelWarrPrt3") on cell.CellID equals printer.CellID
into printers
select new { Cell = cell, Containers = containers, Printers = printers };
query.Dump();
This query works, but is not efficient. It does a left outer join on Container, but, for each Cell, it performs a separate query to retrieve any Printer rows, instead of also doing a left outer join on Printer.
How can I change this so that it also does a left outer join on the Printer table? BTW, I want a hierarchical result set. IOW, each Cell should have a list of containers and a list of printers. Each would be empty of course, if none existed for the cell.

Here's a query to produce a flat result set with correct left joins.
var query = from cell in Cell
join container in Container.Where (row => row.SerialNumber == "1102141") on cell.CellID equals container.CellID
into containers
from container2 in containers.DefaultIfEmpty()
join printer in Printer.Where (row => row.Name == "PG10RelWarrPrt3") on cell.CellID equals printer.CellID
into printers
from printer2 in printers.DefaultIfEmpty()
select new { Cell = cell, Container = container2, Printer = printer2 };
You'll have to post-process the results locally to get the hierarchical shape desired.
If you write this post-processing code, you'll understand why linq to sql doesn't process multiple sibling collections for you.
To make this clearer, suppose you had 3 sibling collections.
If all three sibling collections were empty for some parent record, you'd have just the parent record 1 time with a bunch of nulls.
If all three sibling collections had 100 records for some parent record, you'd have 1 million rows, each with a copy of the parent record. Every child record would be duplicated 10,000 times in the result.
It's always important to keep in mind with any ORM that it generates sql and gets back flat result sets, no matter what hierarchically shaped result it eventually present you with.

It's usually wrong to use join in LINQ to SQL.
Try:
var query = from cell in Cell
select new
{
Cell = cell,
Containers = cell.Containers
.Where (row => row.SerialNumber == "1102141"),
Printers = cell.Printers
.Where (row => row.Name == "PG10RelWarrPrt3")
};

Related

ORACLE - DECODE view predicate and apply on base table

I have a view defined as below
CREATE VIEW DQ_DB.DQM_RESULT_VIEW
AS SELECT
res.ACTIVE_FL AS ACTIVE_FL,
res.VERSION as VERSION,
res.rule_constituents_tx,
nvl(ruletable.rule_desc,'N/A') AS rule_ds,
nvl(res.effective_dt, TO_DATE('31-dec-9999','dd-mon-yyyy')) AS effective_dt,
nvl(res.rule_id,'N/A') AS rule_id,
res.audit_update_ts AS rule_processed_at,
res.load_dt,
res.vendor_group_key,
nvl(res.vendor_entity_key,'N/A') AS vendor_entity_key,
res.vendor_entity_producer_nm,
(SELECT category_value_tx FROM dq_db.category_lookup_view WHERE category_nm = 'RESULT_STATUS_NB' AND category_value_cd = res.result_status_nb ) AS result,
--catlkp.category_value_tx as result,
res.entity_type,
nvl(rgrp.grp_nm,'N/A') AS rule_category,
nvl(ruletable.rule_nm,'N/A') AS rule_nm,
feedsumm.feed_run_nm AS file_nm,
res.application_id AS application,
res.data_source_id AS datasource,
res.entity_nm,
res.rule_entity_effective_dt,
res.result_id,
dim.dimension_nm,
dim.sub_dimension_nm,
ruletable.execution_env AS execution_env,
ruletable.ops_action AS ops_action,
rulefunctiontable.func_nm AS rule_func_nm,
-- nvl2(res.primary_dco_sid,dq_db.get_dco_name(res.primary_dco_sid),null) AS dco_primary,
-- nvl2(res.delegate_dco_sid,dq_db.get_dco_name(res.delegate_dco_sid),null) AS dco_delegate,
res.primary_dco_sid AS dco_primary,
res.delegate_dco_sid AS dco_delegate,
ruletable.data_concept_id AS data_concept_id,
res.latest_result_fl as latest_result_fl,
res.batch_execution_ts as batch_execution_ts
FROM
dq_db.dqm_result res
--LEFT OUTER JOIN dq_db.category_lookup_view catlkp on (catlkp.category_nm = 'RESULT_STATUS_NB' AND catlkp.category_value_cd = res.result_status_nb)
LEFT OUTER JOIN dq_db.feed_run_summary feedsumm ON res.vendor_group_key = feedsumm.batch_id
LEFT OUTER JOIN dq_db.dqm_rule ruletable ON res.rule_id = ruletable.rule_id
LEFT OUTER JOIN dq_db.dqm_rule_grp rgrp ON ruletable.rule_grp_id = rgrp.rule_grp_id
LEFT OUTER JOIN dq_db.dqm_rule_function rulefunctiontable ON ruletable.func_id = rulefunctiontable.func_id
LEFT OUTER JOIN dq_db.dq_dimension_view dim ON dim.dimension_id = ruletable.dimension_id
result column of view is a lookup value generated from subquery condition which translates code as
below.
0|PASS 1|ALERT 2|ERROR
My web application adds few predicates to this view which are pushed to base tables. But one particular predicate on view shown below is not pushed due to inline nature of the predicate.
select * from dqm_result_view where result IN ('ALERT','ERROR')
Right now this query applies filter after view JOINs are executed as there is no way to push predicate to DQM_RESULT table.
What is need is.. if we get that result predicate then apply code 0,1,2 instead of applying result predicate at end so that data is filtered ahead of time for JOINs from DQM_RESULT base table and thus improve performance. Any idea on how to achieve this?

linq query grouping and joining getting incorrect sum

I have the following code which is grouping and summing some values.
The sum "TotalCost" value is correct, however, when i uncomment the lines the sum value is wrong (its less than it should be)
Im doing something wrong, but cant figure this out. any ideas?
from orderItem in Order_ProductItem
//join ho in Hardware_Items on orderItem.OuterColour equals ho.Index
//join hi in Hardware_Items on orderItem.InnerColour equals hi.Index
where orderItem.SalesOrderID == 3272 && (orderItem.IsDeleted==null || orderItem.IsDeleted.Value == false)
group new { orderItem/*, hi, ho*/} by orderItem.FrameNo into grp
select new OrderItemModel
{
FrameNo = grp.Key,
TotalCost = grp.Sum(x => x.orderItem.SellingPrice),
//InternalColor = grp.FirstOrDefault().hi.Name,
//ExternalColor = grp.FirstOrDefault().ho.Name,
Quantity = grp.FirstOrDefault().orderItem.Quantity,
}
Basic Schema
Order_ProductItem
FrameNo
OuterColour
InnerColour
SellingPrice
Hardware_Items
Index
Name
The Order_ProductItem has FrameNo which is listed multiple times in the table, so im trying to get it to group them, then sum the SellingPrice of each row that has the same FrameNo.
If i exclude the bit to obtain colour (internal and external) the sum is correct.
In that case how can i also include the inner and outer color names?
You probably need to use a left join, because the inner join is filtering out some of your data. Here is an example on how you would change your first join.
join ho in Hardware_Items on orderItem.OuterColour equals ho.Index into hog
from ho in hog.DefaultIfEmpty()

LINQ join multiple conditions

I have formed the following LINQ query, but it gives error
mcc_season is not an attribute in mcc_product
How do I form the query where I have 2 WHERE conditions and both from different entities in the join
var guestCardProduct =
(from c in CrmOrgServiceContext.mcc_productpriceSet
join d in CrmOrgServiceContext.mcc_productSet
on c.mcc_product.Id equals d.mcc_productId
where d.mcc_producttype.Value == (int)mcc_product.mcc_producttypeOptionSet.GuestCard
&& c.mcc_season.Id == seasonId
select new
{
d.mcc_productId,
c.mcc_price
}).FirstOrDefault();
You might be able to re-write it as follows:
var guestCardProduct =
(from c in CrmOrgServiceContext.mcc_productpriceSet
where c.mcc_season.Id == seasonId
join d in CrmOrgServiceContext.mcc_productSet
on c.mcc_product.Id equals d.mcc_productId
where d.mcc_producttype.Value == (int)c.mcc_product.mcc_producttypeOptionSet.GuestCard
select new
{
d.mcc_productId,
c.mcc_price
}).FirstOrDefault();
We're assuming here that there are 1 - 0..1 relationships between mcc_productpriceSet and both mcc_season and mcc_product and also a 1 - 0..1 relationship between mcc_product and mcc_producttypeOptionSet. If you have 1-n relationships between any of these, then you are going to have to work through those relationships rather than dotting into the single property because you will have collections of child objects rather than a single child object. You may find it helpful to break your query into smaller pieces to narrow down the source of the problem.

Recursive Linq Grouping

Scenario:
I have database table that stores the hierarchy of another table's many-to-many relationship. An item can have multiple children and can also have more than one parent.
Items
------
ItemID (key)
Hierarchy
---------
MemberID (key)
ParentItemID (fk)
ChildItemID (fk)
Sample hierarchy:
Level1 Level2 Level3
X A A1
A2
B B1
X1
Y C
I would like to group all of the child nodes by each parent node in the hierarchy.
Parent Child
X A1
A2
B1
X1
A A1
A2
B B1
X1
Y C
Notice how there are no leaf nodes in the Parent column, and how the Child column only contains leaf nodes.
Ideally, I would like the results to be in the form of IEnumerable<IGrouping<Item, Item>> where the key is a Parent and the group items are all Children.
Ideally, I would like a solution that the entity provider can translate in to T-SQL, but if that is not possible then I need to keep round trips to a minimum.
I intend to Sum values that exist in another table joined on the leaf nodes.
Since you are always going to be returning ALL of the items in the table, why not just make a recursive method that gets all children for a parent and then use that on the in-memory Items:
partial class Items
{
public IEnumerable<Item> GetAllChildren()
{
//recursively or otherwise get all the children (using the Hierarchy navigation property?)
}
}
then:
var items =
from item in Items.ToList()
group new
{
item.itemID,
item.GetAllChildren()
} by item.itemID;
Sorry for any syntax errors...
Well, if the hierarchy is strictly 2 levels you can always union them and let LINQ sort out the SQL (it ends up being a single trip though it needs to be seen how fast it will run on your volume of data):
var hlist = from h in Hierarchies
select new {h.Parent, h.Child};
var slist = from h in Hierarchies
join h2 in hlist on h.Parent equals h2.Child
select new {h2.Parent, h.Child};
hlist = hlist.Union(slist);
This gives you an flat IEnumerable<{Item, Item}> list so if you want to group them you just follow on:
var glist = from pc in hlist.AsEnumerable()
group pc.Child by pc.Parent into g
select new { Parent = g.Key, Children = g };
I used AsEnumerable() here as we reached the capability of LINQ SQL provider with attempting to group a Union. If you try it against IQueryable it will run a basic Union for eligable parents then do a round-trip for every parent (which is what you want to avoid). Whether or not its ok for you to use regular LINQ for the grouping is up to you, same volume of data would have to come through the pipe either way.
EDIT: Alternatively you could build a view linking parent to all its children and use that view as a basis for tying Items. In theory this should allow you/L2S to group over it with a single trip.

Stuck on a subquery that is grouping, in Linq`

I have some Linq code and it's working fine. It's a query that has a subquery in the Where clause. This subquery is doing a groupby. Works great.
The problem is that I don't know how to grab one of the results from the subquery out of the subquery into the parent.
Frst, here's the code. After that, I'll expplain what piece of data i'm wanting to extract.
var results = (from a in db.tblProducts
where (from r in db.tblReviews
where r.IdUserModified == 1
group r by
new
{
r.tblAddress.IdProductCode_Alpha,
r.tblAddress.IdProductCode_Beta,
r.tblAddress.IdProductCode_Gamma
}
into productGroup
orderby productGroup.Count() descending
select
new
{
productGroup.Key.IdProductCode_Alpha,
productGroup.Key.IdProductCode_Beta,
productGroup.Key.IdProductCode_Gamma,
ReviewCount = productGroup.Count()
}).Take(3)
.Any(
r =>
r.IdProductCode_Alpha== a.IdProductCode_Alpha&&
r.IdProductCode_Beta== a.IdProductCode_Beta&&
r.IdProductCode_Gamma== a.IdProductCode_Gamma)
where a.ProductFirstName == ""
select new {a.IdProduct, a.FullName}).ToList();
Ok. I've changed some field and tables names to protect the innocent. :)
See this last line :-
select new {a.IdProduct, a.FullName}).ToList();
I wish to include in that the ReviewCount (from the subquery). I'm jus not sure how.
To help understand the problem, this is what the data looks like.
Sub Query
IdProductCode_Alpha = 1, IdProductCode_Beta = 2, IdProductCode_Gamma = 3, ReviewCount = 10
... row 2 ...
... row 3 ...
Parent Query
IdProduct = 69, FullName = 'Jon Skeet's Wonder Balm'
So the subquery grabs the actual data i need. The parent query determines the correct product, based on the subquery filters.
EDIT 1: Schema
tblProducts
IdProductCode
FullName
ProductFirstName
tblReviews (each product has zero to many reviews)
IdProduct
IdProductCode_Alpha (can be null)
IdProductCode_Beta (can be null)
IdProductCode_Gamma (can be null)
IdPerson
So i'm trying to find the top 3 products a person has done reviews on.
The linq works perfectly... except i just don't know how to include the COUNT in the parent query (ie. pull that result from the subquery).
Cheers :)
Got it myself. Take note of the double from at the start of the query, then the Any() being replaced by a Where() clause.
var results = (from a in db.tblProducts
from g in (
from r in db.tblReviews
where r.IdUserModified == 1
group r by
new
{
r.tblAddress.IdProductCode_Alpha,
r.tblAddress.IdProductCode_Beta,
r.tblAddress.IdProductCode_Gamma
}
into productGroup
orderby productGroup.Count() descending
select
new
{
productGroup.Key.IdProductCode_Alpha,
productGroup.Key.IdProductCode_Beta,
productGroup.Key.IdProductCode_Gamma,
ReviewCount = productGroup.Count()
})
.Take(3)
Where(g.IdProductCode_Alpha== a.IdProductCode_Alpha&&
g.IdProductCode_Beta== a.IdProductCode_Beta&&
g.IdProductCode_Gamma== a.IdProductCode_Gamma)
where a.ProductFirstName == ""
select new {a.IdProduct, a.FullName, g.ReviewCount}).ToList();
While I don't understand LINQ completely, but wouldn't the JOIN work?
I know my answer doesn't help but it looks like you need a JOIN with the inner table(?).
I agree with shahkalpesh, both about the schema and the join.
You should be able to refactor...
r => r.IdProductCode_Alpha == a.IdProductCode_Alpha &&
r.IdProductCode_Beta == a.IdProductCode_Beta &&
r.IdProductCode_Gamma == a.IdProductCode_Gamma
into an inner join with tblProducts.

Resources