how can I implement CASE WHEN with LINQ? - c#

I wrote a T-SQL code which has used case when in select scope. We couldn't use t-sql or store procedure in application, because of that I need to convert follong code to LINQ. Is there any way to change this code to linq quickly?
SELECT
T.TaskID,
SUM(CASE WHEN T.LogDate<#fromDate AND T.TaskStatusID=2 THEN ISNULL(DA_CHILD.Score,0)*(T.DoneScore/100) ELSE 0 END) PreAmount,
SUM(CASE WHEN T.LogDate>=#fromDate AND T.LogDate<=#toDate AND T.TaskStatusID=2 THEN ISNULL(DA_CHILD.Score,0)*(T.DoneScore/100) ELSE 0 END) CurAmount
FROM
NetTasks$ T
INNER JOIN NetDeviceActions DA ON DA.DeviceActionID=T.DeviceActionID
LEFT JOIN NetFinancialInfoDetail FID ON FID.TaskID=T.TaskID
INNER JOIN NetActionParents AP ON AP.ParentID=DA.ActionID
INNER JOIN NetDeviceActions DA_CHILD ON DA_CHILD.ActionID=AP.ChildID AND
DA_CHILD.DeviceID=DA.DeviceID AND
DA_CHILD.ContractInfoID=DA.ContractInfoID
WHERE
T.ParentTaskID = 0 AND
T.FinishDate<=#toDate AND
DA.ContractInfoID=9
GROUP BY
T.TaskID, T.DoneScore,T.FinishDate

In LINQ you can use C# statements so CASE WHEN is actually not hard.
Assuming you have finished all the joining into a query object called values, you can use something like below for the grouping and select:
var q = from a in values
group a by new {a.TaskID, a.DoneScore, a.FinishDate} into g
select new {
g.Key.TaskID,
PreAmount = g.Where(x => x.LogDate < fromDate && x.TaskStatusID == 2 && x.DA_CHILD.HasValue).Select(x => x.DoneScore).Sum(),
CurAmount = g.Where(x => x.LogDate >= fromDate && x.LogDate < toDate && x.TaskStatusID == 2 && x.DA_CHILD.HasValue).Select(x => x.DoneScore).Sum()
};
And of course, a friendly reminder, left joining in LINQ is very tedious.

Are you just looking for a simple where clause in your statement? (Though I admit this LINQ query is not going to be particularly simple.)
https://msdn.microsoft.com/en-us/library/bb397927.aspx
I advise building it up slowly.

With such good looking SQL, would you not be happier using QueryFirst and forgetting about Linq? You run your SQL directly in your C# app.
disclaimer : I wrote QueryFirst

Related

How to rewrite correlated join from SQL to LINQ

I'm trying to rewrite following SQL query into LINQ:
SELECT `i`.`symbol`, `i`.`id`, `t0`.`close`, `t`.`close`, `t`.`close` - `t0`.`close`, (`t`.`close` - `t0`.`close`) / `t0`.`close`
FROM `investment` AS `i`
LEFT JOIN `investment_record` AS `t0` ON `t0`.id = (
SELECT `i0`.id
FROM `investment_record` AS `i0`
WHERE (`i0`.`date` <= #dateFrom) AND i.id = i0.investment_id
ORDER BY `i0`.`date` DESC
LIMIT 1
)
LEFT JOIN `investment_record` AS `t` ON `t`.id =(
SELECT `i0`.id
FROM `investment_record` AS `i0`
WHERE (`i0`.`date` <= #dateTo) AND i.id = i0.investment_id
ORDER BY `i0`.`date` DESC
LIMIT 1
)
WHERE `i`.`id` IN (#id0, #id1, ....)
My main issues are the AND i.id = i0.investment_id and LIMIT 1 parts of JOINs.
Currently the best I could achieve is this:
from inv in _context.Investment
join recTo in _context.InvestmentRecord on inv.Id equals recTo.InvestmentId into recToColl
from recToNullable in recToColl.Where(x => x.Date <= dateTo).OrderByDescending(x => x.Date).Take(1).DefaultIfEmpty()
join recFrom in _context.InvestmentRecord on inv.Id equals recFrom.InvestmentId into recFromColl
from recFromNullable in recFromColl.Where(x => x.Date <= dateFrom).OrderByDescending(x => x.Date).Take(1).DefaultIfEmpty()
where investmentIds.Contains(inv.Id)
let amountFrom = recFromNullable.Close
let amountTo = recToNullable.Close
select new InvestmentPerformance(
inv.Symbol,
inv.Id,
amountFrom,
amountTo,
amountTo - amountFrom,
(amountTo - amountFrom) / amountFrom
);
but the problem is it doesn't work.
It gives the expression cannot be translated exception:
System.InvalidOperationException: The LINQ expression
'DbSet()
.GroupJoin(
inner: DbSet(),
outerKeySelector: inv => inv.Id,
innerKeySelector: recTo => recTo.InvestmentId,
resultSelector: (inv, recToColl) => new {
inv = inv,
recToColl = recToColl
})' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly
by inserting a call to 'A sEnumerable', 'AsAsyncEnumerable', 'ToList',
or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038
for more information.
Point of this ugly SQL (and LINQ) is to calculate performance of investment for given time interval. User is able to specify from-to dates. Problem is sometimes user could specify date without any records (bank holiday for example). So for given date, I want to use the closest previous record (that is the reason for <= #dateFrom conditions and ORDER BY date DESC LIMIT 1 parts of the SQL.
I tried many variations of the LINQ with different forms of joins, but none of them worked as I need :(
I'm using EF.Core 5 and MySQL database.
The original SQL query seems complex to me. I would already rewrite it using a OUTER APPLY instead of sub-join queries.
SELECT `i`.`symbol`, `i`.`id`, `t0`.`close`, `t`.`close`, `t`.`close` - `t0`.`close`, (`t`.`close` - `t0`.`close`) / `t0`.`close`
FROM `investment` AS `i`
OUTER APPLY (
SELECT `i0`.id
FROM `investment_record` AS `i0`
WHERE (`i0`.`date` <= #dateFrom) AND i.id = i0.investment_id
ORDER BY `i0`.`date` DESC
LIMIT 1
) AS t0
OUTER APPLY (
SELECT `i0`.id
FROM `investment_record` AS `i0`
WHERE (`i0`.`date` <= #dateTo) AND i.id = i0.investment_id
ORDER BY `i0`.`date` DESC
LIMIT 1
) AS t
WHERE `i`.`id` IN (#id0, #id1, ....)
Then I would translate this using EF's way to write OUTER APPLY. This SO post might be of help.
It would look something like this:
from inv in _context.Investments
from rec1 in _context.InvestmentsRecords.Where(ir => ir.InvestmentId = inv.InvestmentId).Where(ir => ir.Date <= DateFrom).OrderByDescending().Take(1)
from rec1 in _context.InvestmentsRecords.Where(ir => ir.InvestmentId = inv.InvestmentId).Where(ir => ir.Date <= DateTo).OrderByDescending().Take(1)
...

How can I use a method withing a linq query?

Here is my query
return (from l in Context.DctLink
join t in Context.DctTabel on l.BType equals t.DocType
join t2 in Context.DctTabel on l.TabelNr equals t2.TabelNr
where l.AType == docType || l.AType == 0
select new { t.TabelNr, t2.Naam, t.Titel })
.Union(from l in Context.DctLink
join t in Context.DctTabel on l.AType equals t.DocType
join t2 in Context.DctTabel on l.TabelNr equals t2.TabelNr
where l.BType == docType || l.BType == 0
select new { t2.TabelNr, t2.Naam, t.Titel })
.Join(Context.TimIcon.Where(q => q.Timweb && q.ShowId.ToInt32() > 0),
x => x.TabelNr,
y => y.TabelNr,
(x, y) => new LookupItem
{
Id = x.TabelNr,
Name = x.Titel,
Tag = x.Naam
}).ToList();
I want to be able to do this q.ShowId.ToInt32() > 0. But I get a System.Unsupported Exception. Isn't this possible in a link query or am I just overlooking something simple
Thanks in advance
You need to fetch the data from db using AsEnumerable or ToList, then you can use any method you want. Otherwise it's not possible because EF Query Provider can't know how to translate your method into SQL.
It depends on your LINQ provider. LINQ to Objects supports pretty much anything. The one you're using (LINQ to Entities or LINQ to SQL or something similar) doesn't support everything, because it needs to understand your expression and translate it to SQL (it can't do that with any kind of expression).
The simplest way to fix this is to call AsEnumerable() at some point, in order to convert the sequence (up to that point) to an in-memory sequence, so you'll fall back to LINQ to Objects and you can perform the (previously unsupported) logic on it.

using "greater than or equal" operator in linq join operation [duplicate]

I had tried to join two table conditionally but it is giving me syntax error. I tried to find solution in the net but i cannot find how to do conditional join with condition. The only other alternative is to get the value first from one table and make a query again.
I just want to confirm if there is any other way to do conditional join with linq.
Here is my code, I am trying to find all position that is equal or lower than me. Basically I want to get my peers and subordinates.
from e in entity.M_Employee
join p in entity.M_Position on e.PostionId >= p.PositionId
select p;
You can't do that with a LINQ joins - LINQ only supports equijoins. However, you can do this:
var query = from e in entity.M_Employee
from p in entity.M_Position
where e.PostionId >= p.PositionId
select p;
Or a slightly alternative but equivalent approach:
var query = entity.M_Employee
.SelectMany(e => entity.M_Position
.Where(p => e.PostionId >= p.PositionId));
Following:
from e in entity.M_Employee
from p in entity.M_Position.Where(p => e.PostionId >= p.PositionId)
select p;
will produce exactly the same SQL you are after (INNER JOIN Position P ON E..PostionId >= P.PositionId).
var currentDetails = from c in customers
group c by new { c.Name, c.Authed } into g
where g.Key.Authed == "True"
select g.OrderByDescending(t => t.EffectiveDate).First();
var currentAndUnauthorised = (from c in customers
join cd in currentDetails
on c.Name equals cd.Name
where c.EffectiveDate >= cd.EffectiveDate
select c).OrderBy(o => o.CoverId).ThenBy(o => o.EffectiveDate);
If you have a table of historic detail changes including authorisation status and effective date. The first query finds each customers current details and the second query adds all subsequent unauthorised detail changes in the table.
Hope this is helpful as it took me some time and help to get too.

Help Converting T-SQL to LINQ

Have the following (non-straightforward) T-SQL query, which i'm trying to convert to LINQ (to be used in a L2SQL expression):
declare #IdAddress int = 481887
select * from
(
select top 3 p.*
from tblProCon p
inner join vwAddressExpanded a
on p.IdPrimaryCity = a.IdPrimaryCity
where a.AddressType = 3
and p.IsPro = 1
and a.IdAddress = #IdAddress
order by AgreeCount desc
) as Pros
union
select * from
(
select top 3 p.*
from tblProCon p
inner join vwAddressExpanded a
on p.IdPrimaryCity = a.IdPrimaryCity
where a.AddressType = 3
and p.IsPro = 0
and a.IdAddress = #IdAddress
order by AgreeCount desc
) as Cons
order by ispro desc, AgreeCount desc
In a nutshell, i have an #IdAddress - and i'm trying to find the top 3 pro's and top 3 con's for that address.
The above query does work as expected. I'm not entirely sure how to convert it to a LINQ query (never done unions before with LINQ). I don't even know where to start. :)
Query-style/Lambda accepted (prefer query-style, for readability).
Also - i have LinqPad installed - but i'm not sure how to "convert T-SQL to Linq" - is there an option for that? Bonus upvote will be awarded for that. :)
The above T-SQL query performs well, and this L2SQL query will be executed frequently, so it needs to perform pretty well.
Appreciate the help.
var baseQuery = (from p in db.tblProCon
join a in db.vwAddresssExpanded
on p.IdPrimaryCity equals a.IdPrimaryCity
where a.AddressType == (byte) AddressType.PrimaryCity &&
a.IdAddress == idAddress
order by p.AgreeCount descending
select p);
var pros = baseQuery.Where(x=> x.IsPro).Take(3);
var cons = baseQuery.Where(x=> !x.IsPro).Take(3);
var results = pros
.Union(cons)
.OrderByDescending(x => x.IsPro)
.ThenByDescending(x => x.AgreeCount)
.ToList();
You can call (some query expression).Union(other query expression).
You can also (equivalently) write Enumerable.Union(some query expression, other query expression).
Note that both expressions must return the same type.
AFAIK, there are no tools that automatically convert SQL to LINQ.
(For non-trivial SQL, that's a non-trivial task)

C# - Linq-To-SQL - Issue with queries

I am thoroughly frustrated right now. I am having an issue with LINQ-To-SQL. About 80% of the time, it works great and I love it. The other 20% of the time, the query that L2S creates returns the correct data, but when actually running it from code, it doesn't return anything. I am about to pull my hair out. I am hoping somebody can see a problem or has heard of this before. Google searching isn't returning much of anything.
Here is the linq query...
var query = from e in DataLayerGlobals.GetInstance().db.MILLERTIMECARDs
where e.deleted_by == -1
&& e.LNAME == lastName
&& e.FNAME == firstName
&& e.TIMECARDDATE == startDate.ToString("MM/dd/yyyy")
group e by e.LNAME into g
select new EmployeeHours
{
ContractHours = g.Sum(e => e.HRSCONTRACT),
MillerHours = g.Sum(e => e.HRSSHOWRAIN + e.HRSOTHER),
TravelHours = g.Sum(e => e.HRSTRAVEL)
};
This is the generated query....
SELECT SUM([t0].[HRSCONTRACT]) AS [ContractHours],
SUM([t0].[HRSSHOWRAIN] + [t0].[HRSOTHER]) AS [MillerHours],
SUM([t0].[HRSTRAVEL]) AS [TravelHours]
FROM [dbo].[MILLERTIMECARD] AS [t0]
WHERE ([t0].[deleted_by] = #p0)
AND ([t0].[LNAME] = #p1)
AND ([t0].[FNAME] = #p2)
AND ([t0].[TIMECARDDATE] = #p3)
GROUP BY [t0].[LNAME]
Now when I plug in the EXACT same values that the linq query is using into the generated query, I get the correct data. When I let the code run, I get nothing.
Any ideas?
What type is TIMECARDDATE? Date, datetime, datetime2, smalldatetime, datetimeoffset or character?
Any chance local date/time settings are messing up the date comparison of startDate.ToString(...)? Since you're sending #p3 as a string, 01/02/2009 may mean Feb 1st or January 2nd, depending on the date/time setting on the server.
My instinct is telling me that you need to be pulling out DataLayerGlobals.GetInstance().db.MILLERTIMECARDs into an IQueryable variable and executing your Linq query against that, although there really should be no difference at all (other than maybe better readability).
You can check the results of the IQueryable variable first, before running the Linq query against it.
To extend this concept a bit further, you can create a series of IQueryable variables that each store the results of a Linq query using each individual condition in the original query. In this way, you should be able to isolate the condition that is failing.
I'd also have a look at the LNAME & FNAME data types. If they're NCHAR/NVARCHAR you may need to Trim the records, e.g.
var query = from e in DataLayerGlobals.GetInstance().db.MILLERTIMECARDs
where e.deleted_by == -1
&& e.LNAME.Trim() == lastName
&& e.FNAME.Trim() == firstName
&& e.TIMECARDDATE == startDate.ToString("MM/dd/yyyy")
group e by e.LNAME into g
select new EmployeeHours
{
ContractHours = g.Sum(e => e.HRSCONTRACT),
MillerHours = g.Sum(e => e.HRSSHOWRAIN + e.HRSOTHER),
TravelHours = g.Sum(e => e.HRSTRAVEL)
};

Categories

Resources