How to rewrite correlated join from SQL to LINQ - c#

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)
...

Related

Automapper ProjectTo membersToExpand with filtering

As far as I know EF Core v5 supports filtering inside .Include() expression.
For example (I use dumb DB just for testing so there is no sense in entities)
var result = dbContext
.Details
.Include(e => e.Permissions
.Where(p => p.UniqueId == Guid.Parse("DCADF7F5-5B86-4A6C-9C7F-6B3D4B55FF27")))
.ToList();
That code produces SQL:
SELECT [d].[UniqueId], [d].[DetailsText], [t].[UniqueId], [t].[DetailsId], [t].[EmployeeID], [t].[PermissionDescription], [t].[PermissionName]
FROM [Details] AS [d]
LEFT JOIN (
SELECT [p].[UniqueId], [p].[DetailsId], [p].[EmployeeID], [p].[PermissionDescription], [p].[PermissionName]
FROM [Permissions] AS [p]
WHERE [p].[UniqueId] = 'dcadf7f5-5b86-4a6c-9c7f-6b3d4b55ff27'
) AS [t] ON [d].[UniqueId] = [t].[DetailsId]
ORDER BY [d].[UniqueId], [t].[UniqueId]
Here I see filer WHERE [p].[UniqueId] = 'dcadf7f5-5b86-4a6c-9c7f-6b3d4b55ff27'
But when I try to use like this:
var result = dbContext
.Details
.ProjectTo<DetailsDto>(
mapper.ConfigurationProvider,
i => i.Permissions
.Where(p => p.UniqueId == Guid.Parse("DCADF7F5-5B86-4A6C-9C7F-6B3D4B55FF27"))
)
.ToList();
I see this SQL:
SELECT [d].[DetailsText], [d].[UniqueId], [p].[DetailsId], [p].[EmployeeID], [p].[PermissionDescription], [p].[PermissionName], [p].[UniqueId]
FROM [Details] AS [d]
LEFT JOIN [Permissions] AS [p] ON [d].[UniqueId] = [p].[DetailsId]
ORDER BY [d].[UniqueId], [p].[UniqueId]
By specifying membersToExpand I see correct join statement but no filter is applied.
Is there a way to filter included navigation properties via ProjectTo ?

how can I implement CASE WHEN with LINQ?

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

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)

Can take be used in a query expression in c# linq instead of using .Take(x)?

I'm trying to write some LINQ To SQL code that would generate SQL like
SELECT t.Name, g.Name
FROM Theme t
INNER JOIN (
SELECT TOP 5 * FROM [Group] ORDER BY TotalMembers
) as g ON t.K = g.ThemeK
So far I have
var q = from t in dc.Themes
join g in dc.Groups on t.K equals g.ThemeK into groups
select new {
t.Name, Groups = (from z in groups orderby z.TotalMembers select z.Name )
};
but I need to do a top/take on the ordered groups subquery. According to http://blogs.msdn.com/vbteam/archive/2008/01/08/converting-sql-to-linq-part-7-union-top-subqueries-bill-horst.aspx in VB I could just add TAKE 5 on the end, but I can't get this syntax to work in c#. How do you use the take syntax in c#?
edit: PS adding .Take(5) at the end causes it to run loads of individual queries
edit 2: I made a slight mistake with the intent of the SQL above, but the question still stands. The problem is that if you use extension methods in the query like .Take(5), LinqToSql runs lots of SQL queries instead of a single query.
Second answer, now I've reread the original question.
Are you sure the SQL you've shown is actually correct? It won't give the top 5 groups within each theme - it'll match each theme just against the top 5 groups overall.
In short, I suspect you'll get your original SQL if you use:
var q = from t in dc.Themes
join g in dc.Groups.OrderBy(z => z.TotalMembers).Take(5)
on t.K equals g.ThemeK into groups
select new { t.Name, Groups = groups };
But I don't think that's what you actually want...
Just bracket your query expression and call Take on it:
var q = from t in dc.Themes
join g in dc.Groups on t.K equals g.ThemeK into groups
select new { t.Name, Groups =
(from z in groups orderby z.TotalMembers select z.Name).Take(5) };
In fact, the query expression isn't really making things any simpler for you - you might as well call OrderBy directly:
var q = from t in dc.Themes
join g in dc.Groups on t.K equals g.ThemeK into groups
select new { t.Name, Groups = groups.OrderBy(z => z.TotalMembers).Take(5) };
Here's a faithful translation of the original query. This should not generate repeated roundtrips.
var subquery =
dc.Groups
.OrderBy(g => g.TotalMembers)
.Take(5);
var query =
dc.Themes
.Join(subquery, t => t.K, g => g.ThemeK, (t, g) => new
{
ThemeName = t.Name, GroupName = g.Name
}
);
The roundtrips in the question are caused by the groupjoin (join into). Groups in LINQ have a heirarchical shape. Groups in SQL have a row/column shape (grouped keys + aggregates). In order for LinqToSql to fill its hierarchy from row/column results, it must query the child nodes seperately using the group's keys. It only does this if the children are used outside of an aggregate.

Categories

Resources