How do I do this SQL in LINQ - c#

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();

Related

Group data and retrieve every line of the grouping with Entity Framework

I was thinking that maybe, once the grouped data are retrieved in the C# part, I would be able loop through the list of items that were grouped.
var res = db.Commandes.Where(t => t.idMatiere == mod.idMatiereChoisie).GroupBy(t => t.UA_idCa);
foreach(var group in res)
{
foreach(var groupedLines in group)
{
// Always a single line, this loop is useless
}
}
It seems the logic applied here is more like SQL than C#: the grouping result in a single line and you won't see all the grouped items.
It's not a problem that I can't overcome
Tactic I will use: instead of grouping, I'll just query all the lines, and then, while looping, I will verify if UA_idCa is different form the previous data and that will means the next "group" has been reached.
But I wonder... How does someone normally do this cleanly, if it's possible?
Do you have to query again to retrieve a group's content?
Or is the "Tactic I will use" closer to what's best?
This problem is a matter of the combination of SQL server AND Entity Framework.
Seems like one of the value in the grouped part (a value that is different for all the line inside the group) must be marked as not null.
Because when looking for what could be a key, entity doesn't give a damn about nullable values : they could be unique, they could be never null, EF won't even check that.
Once it is marked as NOT NULL in the sql part, EF suddenly understand that there could multiple different unique values in the grouped part...
So basically This :
ALTER view [dbo].[Commandes] as
SELECT top(50000000)
isnull(ex.unitAdm, '000') UnitAdm
,c.id as idCahier
,isnull(ex.unitAdm, '000') + cast(c.id as nvarchar(6)) as UA_idCa
,c.NomCahier
,[Qte]
,c.prix as PrixCahier
,sc.id, 0 as idSousCahier /* THIS IS WHAT I COULD NOT COMPLETELY RETRIEVE
because it could be null ? */
,sc.NomCahier as sousCahier
,sc.prix as PrixSC
,m.id as idMatiere
,m.Code
,m.NomMatiere
,ep.id as idEpreuve
,ep.Titre
FROM [CahierExamen] cex
join Cahier c on c.id = cex.Fk_Cahier
join Examen ex on cex.FK_Examen = ex.id
join epreuve ep on ex.FK_Epreuve = ep.id
join Matiere m on ep.FK_Matiere = m.id
left join SousCahier sc on c.id = sc.FK_Cahier
order by code, unitAdm, idCahier
GO
As been changed to this:
ALTER view [dbo].[Commandes] as
SELECT top(50000000)
isnull(ex.unitAdm, '000') UnitAdm
,c.id as idCahier
,isnull(ex.unitAdm, '000') + cast(c.id as nvarchar(6)) as UA_idCa
,c.NomCahier
,[Qte]
,c.prix as PrixCahier
,isnull(sc.id, 0) as idSousCahier /* WOW, NOW EF UNDERSTAND
THERE COULD BE MULTIPLE DIFFERENTS VALUES ONCE DATA ARE GROUPED*/
,sc.NomCahier as sousCahier
,sc.prix as PrixSC
,m.id as idMatiere
,m.Code
,m.NomMatiere
,ep.id as idEpreuve
,ep.Titre
FROM [CahierExamen] cex
join Cahier c on c.id = cex.Fk_Cahier
join Examen ex on cex.FK_Examen = ex.id
join epreuve ep on ex.FK_Epreuve = ep.id
join Matiere m on ep.FK_Matiere = m.id
left join SousCahier sc on c.id = sc.FK_Cahier
order by code, unitAdm, idCahier
GO

SQL inner select to LINQ

This thing is driving me crazy.
I think its not that complicated but I don't get it.
I have this working SQL statement and I need the Linq statement for it.
select
a.id, a.date,
(select top 1 b.price from b where a.id = b.id and a.date >= b.date) as price
from a;
For explanation:
I have a table a with articles and a table b with a price history.
Now I need a datagrid where I can enter new entries for table a (so a view is not working) and after saving its showing me associated price
I hope I could express myself understandably
For translating SQL to LINQ query comprehension:
Translate FROM subselects as separately declared variables.
Translate each clause in LINQ clause order, leaving monadic operators (DISTINCT, TOP, etc) as functions applied to the whole LINQ query.
Use table aliases as range variables. Use column aliases as anonymous type field names.
Use anonymous types (new { }) for multiple columns
Left Join is simulated by using a into join_variable and doing another from from the join variable followed by .DefaultIfEmpty().
Replace COALESCE with the conditional operator and a null test.
SELECT * must be replaced with select range_variable or for joins, an anonymous object containing all the range variables.
SELECT fields must be replaced with select new { ... } creating an anonymous object with all the desired fields or expressions.
Proper FULL OUTER JOIN must be handled with an extension method.
For your query:
var ans = from ra in a
select new {
ra.id,
ra.date,
price = (from rb in b
where ra.id == rb.id && ra.date >= rb.date
select rb.price).First()
};
I'm not sure which syntax you're aiming for, but one of these should do the trick. I haven't tested it though.
from xa in a
select new
{
id,
date,
price = (
from xb in b
where xa.id == xb.id && xa.date >= xb.date
select xb.price
).First() // or .FirstOrDefault() if you want to allow null prices
};
or
a.Select(xa => new
{
id,
date,
price = b.First(xb => xa.id == xb.id && xa.date >= xb.date) // or .FirstOrDefault() if you want to allow null prices
});

Linq return distinct values from table if not exist in another

I am trying to return all distinct rows from Staging below where Staging.CenterCode does not exist in Centers.CenterCode.
At the moment Stagings has around 850 distinct CenterCodes and Centers is empty so I should be getting all of the distinct rows, but count begs to differ :)
Any ideas?
var query =
(from s in db.Stagings
join t in db.Centers on s.CenterCode equals t.CenterCode into tj
from t in tj.DefaultIfEmpty()
where s.CenterCode != t.CenterCode
select s.CenterCode).Distinct();
var c = query.Count();
I only need the unique columns from staging so not sure if I actually need a join with the above as I am not ever using data returned from Centers - I have however tried both and get the same 0 value for count.
Any ideas?
I would not use a join, but use a Contains.
var centerCodesQuery = db.Centers.CenterCode
.Select(x => x.CenterCode);
var query = db.Staging
.Where(x => !centerCodesQuery.Contains(x.CenterCode))
.Select(x => x.CenterCode)
.Distinct();
var c = query.Count();
the join is an inner join. So, if none of the rows in 1 table match the other table on the specified identifier then it will return 0. In yours you are trying to join 1 table with 850 distinct rows with an empty table. This will return 0.
If you actually want to return only those rows in 1 table that aren't in another you can use Except:
var query = (from s in db.Stagings
select s.CenterCode)
.Except(from t in db.Centers
select t.CenterCode);
var c = query.Count();
Looks like you are trying to implement antijoin via left outer join, which is one of the possible ways, but in order to make it work, you need to change
where s.CenterCode != t.CenterCode
to
where t == null

Linq NOT IN query - based on SQL query

I'm trying to figure out how I can convert this same SQL query into a Linq query, but I'm not seeing a way to do NOT IN with Linq like you can with SQL.
SELECT COUNT(DISTINCT ID)
FROM References
WHERE ID NOT IN (
SELECT DISTINCT ID
FROM References
WHERE STATUS = 'COMPLETED')
AND STATUS = 'FAILED'
I need to know how many distinct [ID] values exist that contain a [Status] value of "FAILED" that do not also have a [Status] of "COMPLETED". Basically, if there is a failed without a completed, i need the distinct amount for that.
var query_5 = from r in Records where r.ID NOT IN(from r in Records where
r.Record_Status == "COMPLETED" ) && (r.Record_Status == "FAILED")
select r.ID;
var rec_5 = query_5;
Console.WriteLine(rec_5.Distinct());
This was my attempt to do it, but I'm receiving numerous errors as it is not the right way to code it. Any examples on how to accomplish this would be much appreciated!
This is how the rest of my setup is looking.
public class References
{
public string ID;
public string Record_Status;
}
public static List<References> Records = new List<References>
{
};
The rough equivalent of a (not) in is using Contains(). Since the inner subquery doesn't reference the outer, you could write it like this:
var completedIds =
(from r in ctx.References
where r.Status == "COMPLETED"
select r.Id).Distinct();
var count =
(from r in ctx.References
where !completedIds.Contains(r.ID)
where r.Status == "FAILED"
select r.Id).Distinct().Count();
You could use the Except method:
var completed =
(from r in References
where r.Record_Status == "COMPLETED"
select r.Id).Distinct();
var failed =
(from r in References
where r.Record_Status == "FAILED"
select r.Id).Distinct();
var countFailedNotCompleted = failed.Except(completed).Count();
Note that this does not require using Contains during iteration. The sequences will be compared all at once within the Except method. You could also tack ToArray() on to each of the distinct sequences to ensure minimal iteration in the case where you want to use those sequences more than once.

Converting a SQL join into LINQ with EF with filtering

I'm learning Entity Framework and attempting to convert an existing SQL query to a LINQ query, but struggling to convert it.
SELECT taskItems.Description,taskItemResponses.IsCompleted,TaskItemResponses.userId,TaskItemResponses.Notes
FROM TaskLists
LEFT JOIN TaskItems ON TaskLists.TaskListId = TaskItems.TaskListId
LEFT JOIN TaskItemResponses ON TaskItemResponses.TaskItemId = TaskItems.TaskItemId
AND TaskItemResponses.UserId = '1'
This works fine for me, it brings back the following data, always showing the list of Tasks, and if a user has responded to any of them, if they've completed it and what notes they've added.
Description IsCompleted userId Notes
Task A NULL NULL NULL
Task B NULL NULL NULL
Task C NULL NULL NULL
Task D 1 1 I've done this now.
Task E NULL NULL NULL
But when I'm trying to convert this to a LINQ query within C# I can't figure out the syntax, so far i've got
var query = from t in DbContext.TaskList
join ti in DbContext.TaskItem on t.TaskListId equals ti.TaskListId
join tr in DbContext.TaskItemResponse on ti.TaskItemId equals tr.TaskItemId into trj
from x in trj.DefaultIfEmpty()
where x.UserId == userId
select t;
But this isn't filtering for a particular UserId, and instead returns
Description IsCompleted userId Notes
Task A 0 2 Great
Task B 1 2 Okay
Task C 1 3 Nope
Task D 1 1 I've done this now.
Task E 0 5 Ok.
See that. May be you need ome minor changes.
var query = from t in DbContext.TaskList
join ti in DbContext.TaskItem on t.TaskListId equals ti.TaskListId
join tr in DbContext.TaskItemResponse on ti.TaskItemId equals tr.TaskItemId
where(tr.UserId == '1')
select new tempObjList
{
Description = taskItems.Description,
IsCompleted = taskItemResponses.IsCompleted,
userId = TaskItemResponses.userId,
Notes = TaskItemResponses.Notes
};
make "tempObjList" model class.
The correct way to convert SQL LEFT JOIN with right side filter to LINQ is to apply the right side filter before the join operator.
Here is the LINQ equivalent of your SQL query (of course you can correct the field names/types if needed):
var query =
from t in DbContext.TaskList
join ti in DbContext.TaskItem on t.TaskListId equals ti.TaskListId
into tij from ti in tij.DefaultIfEmpty() // left join
join tr in DbContext.TaskItemResponse.Where(x => x.UserId == userId) // filter
on ti.TaskItemId equals tr.TaskItemId
into trj from tr in trj.DefaultIfEmpty() // left join
select new
{
ti.Description,
IsCompleted = (bool?)tr.IsCompleted,
userId = (int?)tr.userId,
tr.Notes
};
In your EF code, the first join is an INNER join and your second one is a LEFT OUTER join
Have a look at these:
First Option
Second Option
Hope these could save you some time.

Categories