LINQ Left and Right Join Query - c#

This is my sql query, how can I generate this query into LINQ:
SELECT
TimeTable.StartTime,
sum(Booking.Quantity) as Total
FROM PromotionSlot
RIGHT JOIN TimeTable
ON PromotionSlot.StartHour = TimeTable.StartTime
LEFT JOIN Booking
ON PromotionSlot.ID = Booking.PromotionSlotID
GROUP BY TimeTable.StartTime
Result:
|StartTime |Total|
---------------------
9 NULL
10 NULL
11 2
12 2
13 NULL
14 NULL
15 NULL
16 NULL
17 NULL
18 NULL
19 NULL
20 NULL
21 NULL
This is what I attempted, I'm not sure that the structure of the linq is correct with my SQL query. But I faced error about The cast to value type 'Int32' failed because the materialized value is null. Either the result type's generic parameter or the query must use a nullable type. Please guide me, thank you all in advance!
var bookingdata =
(from ps in dc.PromotionSlots
join t in dc.TimeTables on ps.StartHour equals t.StartTime into Ts
join bo in dc.Bookings on ps.ID equals bo.PromotionSlotID into Bs
from time in Ts.DefaultIfEmpty()
from book in Bs.DefaultIfEmpty()
group new {time,book} by time.StartTime into NewGroup
select new dataView
{
StartTime = NewGroup.Key,
numBookings = NewGroup.Select(a => a.book!=null? a.book.Quantity: 0).Sum()
}).ToList();
Here is my dataView model
public class dataView
{
public int? StartTime { get; set; }
public int? numBookings { get; set; }
}
UPDATED:
changed StartTime in my dataView model to int? and this is the result by using console.log()
Format: #:console.log("* " + "#item.StartTime" + ":" + "#item.numBookings");
* :0
* 10:0
* 11:2
* 12:2
I found out the reason why the above console.log() will appear this result. I tried to change my SQL Query RIGHT JOIN TimeTable to LEFT JOIN TimeTable. The return result totally the same like the output from my LINQ.

I think the problem you're hitting is that in ad-hoc query results, any column value can be null (eg., the Total column in your results example has mixed values, int and null). But C# is not so lenient, an a null value cannot be stuffed into a integer value.
I think that in your dataView type, the numBookings property is probably set to int, and an int type in C# is not nullable.
I believe if you change it to a nullable int, like:
public int? numBookings { get; set; }
then that may fix your error. If not that, then the following might work:
numBookings = NewGroup.Select(a => a.book!=null ? (a.book.Quantity ?? 0): 0).Sum() ?? 0
But I am not sure about this last one.
UPDATE
What if you update your LINQ query to use LEFT JOIN for both joins, like so:
var bookingdata = (
from t in dc.TimeTables
join ps in dc.PromotionSlots on t.StartTime equals ps.StartHour into Ts
from time in Ts.DefaultIfEmpty()
join bo in dc.Bookings on time.ID equals bo.PromotionSlotID into Bs
from book in Bs.DefaultIfEmpty()
group new {time, book} by time.StartTime into NewGroup
select new dataView
{
StartTime = NewGroup.Key,
numBookings = NewGroup.Select(a => a.book!=null? a.book.Quantity: 0).Sum()
}
).ToList();
Does this make the results more consistent with the SQL query?

Related

How do I do this SQL in LINQ

I have something I can do really easily in SQL but I just can't figure out how to do it in LINQ. So I have 3 tables: Return, ReturnItem, and ReturnItemTest. Return has 1..n ReturnItems and ReturnItem has 0..1 ReturnItemTests. The tables look like this:
Return
======
ReturnId int not null (PK)
ReturnName nvarchar(max) not null
ReturnItem
==========
ReturnItemId int not null (PK)
ReturnId int not null (FK)
ReturnItemStatus int not null
ReturnItemTest
==============
ReturnItemId int not null (PK, FK)
ReturnItemTestStatus int not null
Each return has return items, and each return item may have 0 or 1 tests. Both return items and return item tests have a status. I want to count up how many return item status codes and return item test status codes there are, grouping by the status number of both. However a LEFT OUTER JOIN is needed because a return item may not have a test. So in SQL I say:
SELECT
ri.[ReturnItemStatus] AS ItemStatus,
rit.[ReturnItemTestStatus] AS TestStatus,
COUNT([ReturnItem].[ReturnItemStatus]) as ComboCount
FROM
[Return] r
INNER JOIN [ReturnItem] ri ON r.ReturnId = ri.ReturnId
LEFT OUTER JOIN [ReturnItemTest] rit ON ri.ReturnItemId = rit.ReturnItemId
GROUP BY
ri.[ReturnItemStatus], rit.[ReturnItemTestStatus]
This gives me a result showing all the extant combinations of return item status, return item test status, and the count for each combination. How do I achieve the same with LINQ? I got this far:
var returns =
(
from r in ctx.Returns
join ri in ctx.ReturnItems on r.ReturnID equals ri.ReturnID
join rit in ctx.ReturnItemTests on ri.ReturnItemID equals rit.ReturnItemTestID into ritJoined
from rit in ritJoined.DefaultIfEmpty()
select new {
ReturnItemStatus = ri.ReturnItemStatus,
ReturnItemTestStatus = rit == null ? null : (int?)rit.ReturnItemTestStatus
}
).ToList();
... which shows me the return item statuses LEFT OUTER JOINed to the test statuses, but I can't figure out how to get the grouping and counting to work.
As you do not use the Return table at all, I would skip it. You have this query
SELECT
ri.[ReturnItemStatus] AS ItemStatus,
rit.[ReturnItemTestStatus] AS TestStatus,
COUNT(*) as ComboCount
FROM
[ReturnItem] ri
LEFT OUTER JOIN [ReturnItemTest] rit ON ri.ReturnItemId = rit.ReturnItemId
GROUP BY
ri.[ReturnItemStatus], rit.[ReturnItemTestStatus]
While you can just append grouping to your query, it may not be the best approach. You explicitely define joining keys even when that should not be necessary. In your case you can have at most one test per item so you should be able to write this:
ctx.ReturnItems
.Select(ri => new { ri.ReturnItemStatus, ri.ReturnItemTest.ReturnItemTestStatus })
.GroupBy(x => x, (x, y) => new { x.ReturnItemStatus, x.ReturnItemTestStatus, Count = y.Count() })
Note, that ri.ReturnItemTest.ReturnItemTestStatus is executed on sql server and it would return null when ReturnItemTest is null as a default behaviour of the server.
This is how I managed to do it in the end:
var returns = (
// Grab from returns table
from r in ctx.Returns
// Inner join with return items
join ri in ctx.ReturnItems on r.ReturnID equals ri.ReturnID
// Filter down by return 'closed on' date
where (
r.ClosedOn > startDate &&
r.ClosedOn <= endDate
)
// Join with return item tests. The 'into' clause is powerful and should be used regularly for complex queries;
// really, the lack of an 'into' projection clause can usually be thought of as shorthand. Here, 'into' projects
// the 0..n join hierarchically as an IEnumerable in what is called a 'group join'.
join rit in ctx.ReturnItemTests on ri.ReturnItemID equals rit.ReturnItemID into ritGroupJoined
// 'Flatten out' the join result with the 'from' clause, meaning that group join results with eg. 3 matches will
// cause 3 items in the resultant enumeration, and group join results with zero matches will cause zero items
// in the resultant enumeration. The .DefaultIfEmpty() method means that these results will instead cause one
// item in the resultant enumeration, having the default value for that type (ie. null, as it's a reference type).
// Note that without the 'into' group join above, it's not possible to access the join results with zero matches as
// they are automatically discarded from the results during the default 'inner join'-style flattening.
from rit in ritGroupJoined.DefaultIfEmpty()
// Project these results into an intermediary object to allow ReturnItemTestStatus to be null (as a int? type);
// without this, we couldn't group them because any grouped items whose ReturnItemTestStatus was null would cause
// a type error, null being an invalid value for the ReturnItemTests.ReturnItemTestStatus property (an int type).
select new {
ReturnItemStatus = ri.ReturnItemStatus,
ReturnItemTestStatus = rit == null ? null : (TestStatusEnum?)rit.ReturnItemTestStatus,
} into retData
// Finally, we can now group this flattened data by both item status and item test status; to group by multiple
// fields in LINQ, use an anonymous type containing the fields to group by.
group retData by new { retData.ReturnItemStatus, retData.ReturnItemTestStatus } into retGrouped
// ... and project into an object to get our item status counts.
select new
{
ReturnItemStatus = retGrouped.Key.ReturnItemStatus,
ReturnItemTestStatus = retGrouped.Key.ReturnItemTestStatus,
Count = retGrouped.Count()
}
).ToList();

Translate a SQL query to linq to entity with a cross join

I have to translate this SQL query to a linq to entity expression in C#. Usually I use Linqer to help me with complexe query, but it doesn't work because of the cross join. I read that I need SelectMany for the cross join but I can't get this done myself.
Here the expression:
select allds.DiscountId, allds.SKUId, max(case when ds.DiscountId is not null then 1 else 0 end) as HasPair
from (select distinct discount.Id as DiscountId, sku.Id as SKUId
from Discounts discount cross join
SKUs sku
) as allds left outer join
DiscountSKUs ds
on allds.DiscountId = ds.DiscountId and allds.SKUId = ds.SKUId
group by allds.DiscountId, allds.SKUId
The query will return a matrix like this:
10% discount | 15% discount | 25% discount
SKU #1 Checked NULL NULL
SKU #2 NULL Checked NULL
SKU #3 Checked NULL Checked
Thank you for your help!!
There is no special "cross join" operator in LINQ, the construct is simple no join like this
from a in tableA
from b in tableB
...
Your SQL query should be translated to something like this (not tested)
var query =
from allds in (from discount in db.Discounts
from sku in db.SKUs
select new { DiscountId = discount.Id, SKUId = sku.Id }
).Distinct()
join ds in db.DiscountSKUs
on new { allds.DiscountId, allds.SKUId } equals new { ds.DiscountId, ds.SKUId }
into discountSKUs
from ds in discountSKUs.DefaultIfEmpty()
group new { allds, ds } by new { allds.DiscountId, allds.SKUId } into g
select new
{
g.Key.DiscountId,
g.Key.SKUId,
HasPair = g.Any(e => e.ds != null)
};

Get Count from one value in three table linq2sql select?

I just want the apartment complex count along with the other values. Only adding the count breaks the code. The error I get is "Sequence operators not supported for type 'System.String'." I have also tried changing apartCount to an int with no luck. Any help would be appreciated
using (var db = new DataClasses2DataContext())
{
var zips = (from s in db.ZipCodeServiceAvailabilities
join b in db.ZipCodeBoundaries on s.ZipCode equals b.ZipCode
join a in db.pdx_apart_views on s.ZipCode equals a.Zip_Code
where (s.IsServiced == 1 && b.Ordering % 10 == 0)
orderby b.ZipCode
select new
{
zipCode = b.ZipCode.Trim(),
latitude = b.Latitude,
longitude = b.Longitude,
apartCount = a.Apartment_complex.Count()
}).ToArray();
}
I think you miss group by clause in your query.
or you can use corolated sub query in select clause. please explain your question more

How to rewrite a SQL query in LINQ to Entities?

I'm trying to rewrite a SQL query in LINQ to Entities. I'm using LINQPad with a typed datacontext from my own assembly to test things out.
The SQL query I'm trying to rewrite:
SELECT DISTINCT variantID AS setID, option_value AS name, option_value_description AS description, sort_order as sortOrder
FROM all_products_option_names AS lst
WHERE lst.optionID=14 AND lst.productID IN (SELECT productID FROM all_products_option_names
WHERE optionID=7 AND option_value IN (SELECT name FROM brands
WHERE brandID=1))
ORDER BY sortOrder;
The LINQ to Entities query I've come up with so far (which doesn't work due to a timeout error):
from a in all_products_option_names
where a.optionID == 14 && all_products_option_names.Any(x => x.productID == a.productID && x.optionID == 7 && brands.Any(y => y.name == x.option_value && y.brandID == 1))
select new
{
id = a.variantID,
name = a.option_value,
description = a.option_value_description,
sortOrder = a.sort_order,
}
This is the error I get when I run the above query: An error occurred while executing the command definition. See the inner exception for details.
And the inner exception is: Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding.
Edit:
I use MySQL and probably that's why LINQPad doesn't show me the generated SQL.
The SQL version doesn't time out.
Edit 2:
I solved the problem by completely changing the query, so this question is irrelevant now.
I marked Steven's response as the correct one, because he was closest to what i was trying to achieve and his response gave me the idea which led me to the solution.
Try this:
var brandNames =
from brand in db.Brands
where brand.ID == 1
select name;
var brandProductNames =
from p in db.all_products_option_names
where p.optionID == 7
where brandNames.Contains(p.option_value)
select p.productId;
var results =
from p in db.all_products_option_names
where p.optionID == 14
where brandProductNames.Contains(p.productId)
select new
{
setID = p.variantID,
name = p.option_value,
description = p.option_value_description,
sortOrder = p.sort_order
};
I would recommend doing joins rather than sub-select's as you have them. Sub-selects are not very efficient when you look at performance, it's like having loops inside of loops when you code , not a good idea. This could actually cause that timeout your getting if your database is running slowly even thou that looks like a simple query.
I would try using joins with a distinct at the end like this:
var results =
(from p in db.all_products_option_names
join p2 in db.all_products_option_names on p.productId equals p2.productId
join b in db.Brands on p2.option_value equals b.name
where p.optionID == 14
where p2.optionID == 7
where b.BrandID == 1
select new
{
setID = p.variantID,
name = p.option_value,
description = p.option_value_description,
sortOrder = p.sort_order
}).Distinct();
Or you could try using joins with the into and with an any like so
var results =
from p in db.all_products_option_names
join p2 in (from p3 in db.all_products_option_names.Where(x => x.optionId == 7)
join b in db.Brands.Where(x => x.BrandID == 1) on p3.option_value equals b.name
select p3) into pg
where p.optionID == 14
where pg.Any()
select new
{
setID = p.variantID,
name = p.option_value,
description = p.option_value_description,
sortOrder = p.sort_order
};

Left Join in Linq and using the variable for other joins

I am stuck with the error "value cannot be null. parameter name row" when I use Left join in Linq for DataSet.
Following is the data and the linq query.
DataRowCollection - items (Columns - Item_id, SKU, Quantity)
DataRowCollection - promotions (Columns - Item_id, Promotion_Id)
DataRowCollection - components (Columns - Component_id, Promotion_id)
DataRowCollection - amounts (Columns - Component_id, Amount_Text, currency)
var q =
from Item in items
join promotion in promotions
on Item.Field<int>("Item_id") equals promotion.Field<int?>("Item_id") into promo
from promotion in promo.DefaultIfEmpty()
join disccomponent in components
on promotion.Field<int>("Promotion_Id") equals disccomponent.Field<int?>("Promotion_Id")
join discamounts in amounts
on disccomponent.Field<int>("Component_id") equals discamounts.Field<int?>("Component_id")
where disccomponent.Field<string>("Type") == "Principal"
select new
{
SKU = Item.Field<string>("SKU"),
Quantity = Item.Field<string>("Quantity"),
DiscountAmount = discamounts.Field<string>("Amount_Text"),
DiscountCurrency = discamounts.Field<string>("currency")
};
I need to get the following:
SKU, Qty and Discount details for items which have discount
SKU, Qty for items which do not have any discount
The code worked without left outer join when all items had discount, but if any item does not have any discount it skips that item and hence I had to use the left outer join.
Any help is highly appreciated. Please let me know if any clarification is needed.
Thanks in advance.
I think you are trying to access properties on a null discamounts. Perhaps your select new should look something like this...
select new
{
SKU = Item.Field<string>("SKU"),
Quantity = Item.Field<string>("Quantity"),
DiscountAmount = discamounts == null ? "" : discamounts.Field<string>("Amount_Text"),
DiscountCurrency = discamounts == null ? "" : discamounts.Field<string>("currency")
};
Give your join a name by using into and use DefaultIfEmpty on it. Something like this (look at the last join and from expression in the statement)
var q =
from Item in items
join promotion in promotions
on Item.Field<int>("Item_id") equals promotion.Field<int?>("Item_id") into promo
from promotion in promo.DefaultIfEmpty()
join disccomponent in components
on promotion.Field<int>("Promotion_Id") equals disccomponent.Field<int?>("Promotion_Id")
join discamounts in amounts
on disccomponent.Field<int>("Component_id") equals discamounts.Field<int?>("Component_id") into DiscamountJoin
from discamount in DiscamountJoin.DefaultIfEmpty()
where disccomponent.Field<string>("Type") == "Principal"
select new
{
SKU = Item.Field<string>("SKU"),
Quantity = Item.Field<string>("Quantity"),
DiscountAmount = discamount.Field<string>("Amount_Text"),
DiscountCurrency = discamount.Field<string>("currency")
};
Thanks everyone for your help. I made this work by splitting it into 2 different linq queries below.
The first query gets the promotion data in a var
var promoData =
from promotion in promotions
join component in components
on promotion.Field<int>("Promotion_Id") equals component.Field<int?>("Promotion_Id")
join amount in amounts
on component.Field<int>("Component_id") equals amount.Field<int?>("Component_id")
where component.Field<string>("Type") == "Principal"
select new
{
Item_id = promotion.Field<int?>("Item_id"),
Promotion_id = promotion.Field<int>("Promotion_Id"),
DiscountAmount = amount == null ? "" : amount.Field<string>("Amount_Text"),
DiscountCurrency = amount == null ? "" : amount.Field<string>("currency")
};
The second query uses the promotions var to simplify the query and get the resuls
var q =
from Item in items
join promotion in promoData
on Item.Field<int>("Item_id") equals promotion.Item_id into promo
from promoJoin in promo.DefaultIfEmpty()
select new
{
SKU = Item.Field<string>("SKU"),
Quantity = Item.Field<string>("Quantity"),
DiscountAmount = promoJoin != null ? promoJoin.DiscountAmount : "0",
DiscountCurrency = promoJoin != null ? promoJoin.DiscountCurrency : ""
};
This approach works perfectly and I get all the items whether they have promotions or not. I am still thinking whether doing the same was possible in a single query :)
For now marking this as an answer, if anyone comes up with some other better way will mark that.

Categories