Nested If Statement in Linq to SQL Query - asp.net

In SQL I would normally type this out, hard to read - yes, but it does work
(IIF(Table1.Field1 = 1,
-- Field1 is True
IIF(Table2.Field2= 1, (SUM(Table1.Field3+Table1.Field4)),(SUM(Table1.Field3+Table1.Field4)*-1))
, -- Field1 is False
IIF(Table2.Field2= 1, (SUM((Table1.Field3+Table1.Field4)*-1)), (SUM(Table1.Field3+Table1.Field4))))) AS Result
How would I translate this to a Linq query, I know I can do ternary, but is it possible to nest ternary?
Also my goals are to do this in one database call.

You could write this using the ternary operator (?:) like this:
from t in db.Table1
group t by new { t.Field1, t.Field2 } into g
select
g.Key.Field1 == 1
? g.Key.Field2 == 1
? g.Sum(t => t.Field3 + t.Field4)
: -g.Sum(t => t.Field3 + t.Field4)
: g.Key.Field2 == 1
? -g.Sum(t => t.Field3 + t.Field4)
: g.Sum(t => t.Field3 + t.Field4)
But we can simplify this a bit to:
from t in db.Table1
group t by new { t.Field1, t.Field2 } into g
let s1 = g.Key.Field1 == 1 ? 1 : -1
let s2 = g.Key.Field2 == 1 ? 1 : -1
select s1 * s2 * g.Sum(t => t.Field3 + t.Field4)

Related

How do i count a column of bools grouped by date in LINQ?

I have a table that holds 15 session bookings by dates. Each row is a persons db entry for the sessions they attend.
For each date I need to count the bools for each session S1, S2 etc
How many are in S1 for 1/1/2019?.
Here is the table in asp.net core
![Pic of the table][https://imgur.com/a/SguJmLI]
It looks like this
Session S1 S2 S3 S4 S5 S6 S7
1/1/2019 1 0 1 1 1 0 1
1/1/2019 0 1 1 1 1 1 1
2/1/2019 0 0 1 1 1 1 1
2/1/2019 0 1 1 1 1 1 1
There are multiple session dates, and I need to get a summary count of the bools for each date.
1/1/2019 1 1 2 2 2 1 2
2/1/2019 0 1 2 2 2 2 2
Sadly I havn't got a single thing to generate
But this is how I am generating the grid
return View(await _context.SeatBooking
.OrderByDescending(s => s.SeatDate)
.ToListAsync());
Thanks guys,
Just a quick update this seems to work for me, but its clunky
ViewData["CountSessions"] = _context.SeatBooking
// .Where(s => s.StudentEmail == _userManager.GetUserId(User))
.GroupBy(d => d.SeatDate)
.Select(s => new
{
SumS1 = s.Count(c => c.S1 == true),
SumS2 = s.Count(c => c.S2 == true),
SumS3 = s.Count(c => c.S3 == true),
SumS4 = s.Count(c => c.S4 == true),
SumS5 = s.Count(c => c.S5 == true),
SumS6 = s.Count(c => c.S6 == true),
SumS7 = s.Count(c => c.S7 == true),
SumS8 = s.Count(c => c.S8 == true),
SumS9 = s.Count(c => c.S9 == true),
SumS10 = s.Count(c => c.S10 == true)
}).ToListAsync();
Since the Types of columns are bool, the first to do is to convert the bool to int .
Func<bool, int> convertBoolToInt = flag => flag ? 1 : 0;
var query = _context.SeatBooking
.Select(s => new SeatBookingDto
{
SeatDate = s.SeatDate,
S1 = convertBoolToInt(s.S1),
S2 = convertBoolToInt(s.S2),
S3 = convertBoolToInt(s.S3),
S4 = convertBoolToInt(s.S4),
S5 = convertBoolToInt(s.S5),
S6 = convertBoolToInt(s.S6),
S7 = convertBoolToInt(s.S7),
});
And now we can group and calculate the sum of each column for each group:
query.GroupBy(m => m.SeatDate, m => m, (k, g) => new
{
Date = k,
S1= g.Sum(m=>m.S1),
S2= g.Sum(m=>m.S2),
S3= g.Sum(m=>m.S3),
S4= g.Sum(m=>m.S4),
S5= g.Sum(m=>m.S5),
S6= g.Sum(m=>m.S6),
S7= g.Sum(m=>m.S7),
})
If you're using the EFCore , the corresponding query and result are :
App> SELECT [s].[SeatDate], [s].[S1], [s].[S2], [s].[S3], [s].[S4], [s].[S5], [s].[S6], [s].[S7]
App> FROM [SeatBooking] AS [s]
App> ORDER BY [s].[SeatDate]
App> 1/1/2019 12:00:00 AM : 1,1,2,2,2,1,2
App> 2/1/2019 12:00:00 AM : 0,1,2,2,2,2,2
Note the LINQ expression g.sum() here are all evaluated locally which means not evaluated by database . It seems that the EFcore has not evolved to be able to translate the Aggregate expression (such as Sum) to SQL query that can be executed on server .
If you want to query objects in memory only , there's another way to do that (not supported by EFCore yet) :
query.GroupBy(m=>m.SeatDate,m=>m,(k,g)=>new {
Date = k,
Count = g.Aggregate((c,n)=>new SeatBookingDto{
SeatDate=c.SeatDate,
S1=c.S1+n.S1,
S2=c.S2+n.S2,
S3=c.S3+n.S3,
S4=c.S4+n.S4,
S5=c.S5+n.S5,
S6=c.S6+n.S6,
S7=c.S7+n.S7,
})
})
I think you can try this:
_context.SeatBooking.Select(i => i.Session).Distinct().Select(i =>
new
{
Session = i,
S1 = _context.SeatBooking.Count(j => j.Session == i && j.S1 == 1),
S2 = _context.SeatBooking.Count(j => j.Session == i && j.S2 == 1),
}
).OrderByDescending(s => s.Session).ToList();

How can i add extra columns to my select statement using with clause

How can add V_SP_CDRA_WEEKLY_UPDATE.indication column to following sql statement. As of now it is displaying only str_out column. any help will be appreciated.
with
transformation(str_in,flag,str_out) as
(select substr(s,instr(s,'href=') + 5),1,cast(substr(s,1,instr(s,'href=') + 4) as varchar2(4000))
from (select KEY_DOCUMENTS s From V_SP_CDRA_WEEKLY_UPDATE Where KEY_DOCUMENTS like '%%https:%%')
union all
select substr(str_in,2),
case when flag = 1
and substr(str_in,1,1) != '>'
then 1
when substr(str_in,1,1) = '>'
then 0
when substr(str_in,1,5) = 'href='
then 1
else 0
end,
str_out || case when substr(str_in,1,1) = ' '
and flag = 1
then '%20'
else substr(str_in,1,1)
end
from transformation
where length(str_in) > 0
)
select str_out
from transformation
where str_in is null;
I don't know your data, so I try to ask your question:
with
transformation(str_in, flag, str_out, indication) as
(select substr(s,instr(s,'href=') + 5),
1,
cast(substr(s,1,instr(s,'href=') + 4) as varchar2(4000)),
i
from (select KEY_DOCUMENTS s, indication i
From V_SP_CDRA_WEEKLY_UPDATE
Where KEY_DOCUMENTS like '%%https:%%')
union all
select substr(str_in,2),
case when flag = 1 and substr(str_in,1,1) != '>'
then 1
when substr(str_in,1,1) = '>'
then 0
when substr(str_in,1,5) = 'href='
then 1
else 0
end,
str_out || case when substr(str_in,1,1) = ' ' and flag = 1
then '%20'
else substr(str_in,1,1)
end,
null as i
from transformation
where length(str_in) > 0)
select str_out, indication
from transformation
where str_in is null;

Entity framework equivalent for NOT IN

I have a query like this
SELECT id
FROM params
WHERE valid=1
AND id NOT IN (SELECT pid
FROM clientparams
WHERE update = 0 AND client=15)
LIMIT 25
I am trying to convert it to entity framework equivalent:
IQueryable<Params> parame = db.Params.Where(p => p.valid.Equals(1)).Except(....);
But I am stuck at the point where I need to make an equivalent for the subquery
Any ideas how to solve it?
Try this
IQueryable<Params> parame = db.Params.Where(p => p.valid.Equals(1) &&
!db.clientParams.Any(e => e.pid == p.id && e.update == 0 && e.client == 15))
.OrderBy(e => e.id) //Order by any field.
.Take(25);

How do you write a LINQ query that filters a sub table to a specific time period and sums the results of the sub table?

That is the most efficient way to perform a left outer join in LINQ if I must do the following...
Filter Table 2 by a beginning and ending date.
All rows in Table 1 must remain, even if the filtering of Table 2 returns no rows.
The result must be grouped so that the columns from Table 2 get summed.
For example (example code variable names changed for propietary reasons), supposed I have a database with two tables. Table 1 has a list of doors with a building code, door ID and current status (open or closed) - the building code and door ID are the primary key. Table 2 has a list of events for all doors (an event is an opening or closing) plus a timestamp. So the columns are building code, door ID, timestamp, opening, closing. Opening and closing are integers with a 1 in the column for the appropriate event. There is a foreign key relationship between the two tables on the building code and door ID.
For my query I need to return a list of all the unique doors with the current door status and a sum of all the opening and closing events for a selected time period. An entry must be returned for each door, even if no events occured during the selected time period.
Below is the best LINQ code I could come up with. It works, but it seems really inefficient and hard to understand. How would you make it more efficient and easier to understand?
var query =
from doors in Context.Doors
join fevents in
(
from events in db.Events
where events.TimeStamp >= date1 && events.TimeStamp <= date2
select new { events.BuildingCode, events.DoorID, events.TimeStamp, events.Opening, events.Closing }
)
on new { doors.BuildingCode, doors.DoorID } equals { fevents.BuildingCode, fevents.DoorID }
into g1
from c in g1.DefaultIfEmpty()
group c by new
{
doors.BuildingCode,
doors.DoorID,
doors.DoorStatus
} into g2
select new
{
BuildingCode = g2.Key.BuildingCode,
DoorID = g2.Key.DoorID,
Status = g2.Key.DoorStatus
NumOpenings = g2.Sum(i => (i == null ? 0 : i.Opening)),
NumClosings = g2.Sum(i => (i == null ? 0 : i.Closing))
};
I think this is slightly easier to read
var query =
from doors in Context.Doors
from c in db.Events
.Where(events => doors.BuildingCode == events.BuildingCode)
.Where(events => doors.DoorID == events.DoorID)
.Where(events => events.TimeStamp >= date1 && events.TimeStamp <= date2)
.Select(events => new { events.BuildingCode, events.DoorID, events.TimeStamp, events.Opening, events.Closing })
.DefaultIfEmpty()
group c by new
{
doors.BuildingCode,
doors.DoorID,
doors.DoorStatus
} into g2
select new
{
BuildingCode = g2.Key.BuildingCode,
DoorID = g2.Key.DoorID,
Status = g2.Key.DoorStatus
NumOpenings = g2.Sum(i => (i == null ? 0 : i.Opening)),
NumClosings = g2.Sum(i => (i == null ? 0 : i.Closing))
};
The answer from #adducci helped me come up with a slightly different solution that I think is even more readable, albeit possibly less efficient.
var query =
from doors in Context.Doors
from events in doors.Events
.Where(i => i.TimeStamp >= date1 && i.TimeStamp <= date2)
.DefaultIfEmpty()
group new { doors, events }
by doors into g
select new
{
BuildingCode = g.Key.BuildingCode,
DoorID = g.Key.DoorID,
Status = g.Key.DoorStatus,
NumOpenings = g.Sum(i => (i.events == null ? 0 : i.events.Opening)),
NumClosings = g.Sum(i => (i.events == null ? 0 : i.events.Closing))
};
Note that an alternative method for filtering by date would be directly in the summing function, as below, but this is much less efficient since all records would be retrieved from the database and then filtered locally.
...
//from events in doors.Events
// .Where(i => i.TimeStamp >= date1 && i.TimeStamp <= date2)
// .DefaultIfEmpty()
from events in doors.Events
.DefaultIfEmpty()
...
NumOpenings = g.Sum(i => (i.events == null ? 0 : (i.events.Timestamp >= date1 && i.events.TimeStamp <= date2) ? i.events.Opening : 0)),
NumClosings = g.Sum(i => (i.events == null ? 0 : (i.events.Timestamp >= date1 && i.events.TimeStamp <= date2) ? i.events.Closing : 0))
...

Querying against LINQ to SQL relationships

Using LINQ to SQL
db.Products.Where(c => c.ID == 1).Skip(1).Take(1).ToList();
executes
SELECT [t1].[ID], [t1].[CategoryID], [t1].[Name], [t1].[Price], [t1].[Descripti
n], [t1].[IsFeatured], [t1].[IsActive]
FROM (
SELECT ROW_NUMBER() OVER (ORDER BY [t0].[ID], [t0].[CategoryID], [t0].[Name
, [t0].[Price], [t0].[Description], [t0].[IsFeatured], [t0].[IsActive]) AS [ROW
NUMBER], [t0].[ID], [t0].[CategoryID], [t0].[Name], [t0].[Price], [t0].[Descrip
ion], [t0].[IsFeatured], [t0].[IsActive]
FROM [dbo].[Products] AS [t0]
WHERE [t0].[ID] = #p0
) AS [t1]
WHERE [t1].[ROW_NUMBER] BETWEEN #p1 + 1 AND #p1 + #p2
ORDER BY [t1].[ROW_NUMBER]
-- #p0: Input Int (Size = 0; Prec = 0; Scale = 0) [1]
-- #p1: Input Int (Size = 0; Prec = 0; Scale = 0) [1]
-- #p2: Input Int (Size = 0; Prec = 0; Scale = 0) [1]
It's using ROW_NUMBER for pagination... good.
Now, I'm trying to use relationships generated by LINQ to SQL to paginate data. Using the query...
var cat = db.Categories.Where(c => c.ID == 1).SingleOrDefault();
cat.Products.Where(c => c.ID == 1).Skip(1).Take(1).ToList();
SELECT [t0].[ID], [t0].[Name]
FROM [dbo].[Categories] AS [t0]
WHERE [t0].[ID] = #p0
-- #p0: Input Int (Size = 0; Prec = 0; Scale = 0) [1]
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 3.5.30729.1
SELECT [t0].[ID], [t0].[CategoryID], [t0].[Name], [t0].[Price], [t0].[Descriptio
n], [t0].[IsFeatured], [t0].[IsActive]
FROM [dbo].[Products] AS [t0]
WHERE [t0].[CategoryID] = #p0
-- #p0: Input Int (Size = 0; Prec = 0; Scale = 0) [1]
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 3.5.30729.1
Now the use of ROW_NUMBER and pagination is gone... it's getting all Products where CategoryID = 1... why is it getting ALL rows?
I think its because the category is in memory. You are asking it, implicitly, to get the products of the category. This implicit request for data is for filled, and then in memory (where the category is at this point) the query is executed.
I'm thinking its equivalent to :
var cat = db.Categories.Where(c => c.ID == 1).SingleOrDefault();
var prods = db.Products.Where(c => c.ID == 1).ToList();
var r = prods.Where(p.CategoryID == cat.ID).Skip(1).Take(1);
Note the significance, what if cat changes in memory? The size of the collection could vary.
NOTE: Thanks for the headache :)
have you tried:
var cat = db.Categories.Where(c => c.ID == 1);
var prod = cat.Products.Where(c => c.ID == 1).Skip(1).Take(1).ToList();
You haven't assigned the output of your second LINQ query. So 'cat' is still the first query only.

Resources