I was trying to do a sub query inside a left join using LINQ, it looks like this, in SQL:
SELECT fields
FROM table1 A
LEFT JOIN table2 B ON B.Establishment = A.Establishment
LEFT JOIN table3 C ON C.Vdb = B.Vdb AND C.Year = (SELECT MAX(Year) FROM table3 D WHERE D.Vdb = C.Vdb)
Using LINQ, I did the following:
var query = await (
from a in _context.Table1
join b in _context.Table2 on a.Establishment equals b.Establishment
join c0 in _context.Table3 on b.Vdb equals c0.Vdb into gGroup
from c in gGroup.Where(x => x.Year == (from c1 in _context.Table3
where c1.Vdb == x.Vdb
select c1.Year).Max()).DefaultIfEmpty()
select new
{
fields
})
.ToListAsync();
I built this code using LINQPad, so I was trying run there and everything was going fine, but when I put this code in my IDE and tried to run it I got the following error:
{
"Message": "The LINQ expression (expression here) 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 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.",
"ErrorName": "InvalidOperationException"
}
So I don't know exactly what's wrong so I can fix this, can anyone help me?
GroupJoin is almost non translatable by EF Core. Do not use this operator if you do not do simple LEFT JOIN.
I have rewritten query to use another technique which returns the same result:
var table3 = _context.Table3;
var latest =
from t in table3.Select(t => new { t.Vdb }).Distinct()
from r in table3
.Where(r => r.Vdb == t.Vdb)
.OrderByDescending(r.Year)
.Take(1)
select r;
var query =
from a in _context.Table1
join b in _context.Table2 on a.Establishment equals b.Establishment into gb
from b in gb.DefaultIfEmpty()
join c in latest on b.Vdb equals c.Vdb into gc
from c in gc.DefaultIfEmpty()
select new
{
// fields
};
Related
I am getting below error while executing LINQ query -
The LINQ expression 'DbSet()' 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 'AsEnumerable',
'AsAsyncEnumerable', 'ToList', or 'ToListAsync'.
The issue is occurring in one of the join
var result = await (from stds in _context.Student
join depts in _context.departments on stds.DeptId equals depts.id into a
from b in a.Where(f => f.stdId == 123 && f.Premium.Value).DefaultIfEmpty()
select new StudentModel
{
stdName = b.stdName
}).ToListAsync();
I tried using ToList() and other options instead of ToListAsync() but nothing is working. Can someone help me out here?
LEFT JOIN can be expressed also by SelectMany. GroupJoin has very limited translation support.
var query =
from stds in _context.Student
from depts in _context.departments
.Where(depts => stds.DeptId == depts.id)
.Where(depts => depts.stdId == 123 && depts.Premium.Value)
.DefaultIfEmpty()
select new StudentModel
{
stdName = depts.stdName
};
I have the following query
SELECT sdb.student_due_by_month_id, sdb.due_month_year, sum(sdb.due_total_month) FROM user_student ust
INNER JOIN student std ON ust.student_id = std.student_id
INNER JOIN student_due_by_month sdb ON std.student_id = sdb.student_id
WHERE ust.user_id = 2
GROUP BY sdb.student_due_by_month_id, sdb.student_due_by_month_id, sdb.due_month_year
I need to convert it to linq or a lambda expression.
I'm trying to turn him into a linq and this is what I have so far:
var user_due_month = await (from ust in _context.Users_students
join std in _context.Students on ust.student_id equals std.student_id
join sdb in _context.Students_dues_by_months on std.student_id equals sdb.student_id
where ust.user_id == user_id
group sdb by new { sdb.student_due_by_month_id, sdb.due_month_year } into g //from here i have yet to complete the linq query, help?
I don't know what would be missing to complete my linq query correctly, and also be able to pass it to a viewmodel.
Thanks a lot!!
I'm using sql profiler to see sql generated by Ef core2.1,
this is my linq query :
var resulat = (from a in A
join b in B equals a.level=b.level
where ...
select new M1 {AId = a.id}).Distinct();
(from r in resulat
join c in C equals r.AId = c.AId
select new M2
{
CId = c.Id,
level = _helper(c.level)
}).Distinct();
Sql generated:
select t.AId,c.Id,c.level
from
(
select distinct a.id
from A a
inner join B b on a.level=b.level
where ...
) as t
inner join C c on t.AId = c.AId
What i want as result is :
select distinct c.Id,c.level
from
(
select distinct a.id
from A a
inner join B b on a.level=b.level
where ...
) as t
inner join C c on t.AId = c.AId
I have tried also using select/distinct with result IQueryable, but the sql generated is the same.
what i missed in my linq query or what i have to add to have this sql query
That's what worked for me:
Delete Distinct() from result query, this avoid adding t.AId to my selection.
Delete a helper method from one of my selection fields performe adding Distinct() to final query.
This is my query after correction:
var resulat = from a in A
join b in B equals a.level=b.level
where ...
select new M1 {AId = a.id};
(from r in resulat
join c in C equals r.AId = c.AId
select new M2
{
CId = c.Id
level = c.level
}).Distinct();
Many thanks for your comments, it really helped me.
I'm always a fan of querying the data you want directly from the table (well, DbSet) that returns the data. The process looks a bit like these steps:
I want C.Id and C.Level
That's context.Cs.
Which Cs do I want?
The ones that have a parent A, of which at least one B has the same 'level' as A and meets a couple of other criteria (the where ...).
That amounts to:
from c in context.Cs
where context.Bs.Any(b => b.level == c.A.level && <other criteria>)
select new { c.Id, c.Level }
If the where ... also contains filter criteria for A you can add predicates like && c.A == ... to the where.
Note that I assume a navigation property c.A to be present, otherwise to be created, because C has AId.
Please note below is entirely made up for example sake. I have a similar query based on an sql code but couldn't translate it to LINQ to get correct value.
The sql basically looks like this:
select * from customers c
join proucts p on c.id = p.customerid
join credit r on r.customerid=c.id and ISNULL(r.trandate, c.registeredDate) >= c.registeredDate
I also tried to tweak the above sql and put the condition inside where and it also returns the same value I am getting in my #2 LINQ below(which is incorrect).
How can I use c (customer) inside .Where of credit? see code
1.
from c in customers
join p in products on c.id = p.customerid
join cr in credit.Where(r=> r.tranDate => c.registeredDate!=null?c.registeredDate : r.purchaseDate) on c.id=cr.customerid
...
2.
I know you would suggest why not just put it in a where below like below but I am getting incorrect value.
from c in customers
join p in products on c.id = p.customerid
join cr in credit on c.id=cr.customerid
where r.tranDate => c.registeredDate!=null?c.registeredDate : r.purchaseDate
Is there a workaround? I have tried tons of others but won't get me the correct one.
LINQ supports only equijoins. Any additional criteria should go to where clause. And yes, the other range variables are inaccessible from the join inner sequence, so the filtering should happen before or after the join.
So this SQL query:
select * from customers c
join products p on c.id = p.customerid
join credit r on r.customerid = c.id
and ISNULL(r.trandate, c.registeredDate) >= c.registeredDate
directly translates to this LINQ query:
from c in customers
join p in products on c.id equals p.customerid
join cr in credit on c.id equals cr.customerid
where (cr.tranDate ?? c.registeredDate) >= c.registeredDate
select new { c, p, cr };
Optionally, the condition
(cr.tranDate ?? c.registeredDate) >= c.registeredDate
can be replaced with
(cr.tranDate == null || cr.tranDate >= c.registeredDate)
I am struggling with how to write the below equivalent as LINQ. Truly I guess I am only struggling with how I represent the INNER JOIN () portion. Is that called a Nested Join? Anonymous Join? I am not even sure. Anyway, big thanks to anyone who can point me true. Even if it is just what this is called so I can BING it properly.
SELECT p.PersonID, p.FirstName, p.MiddleName, p.LastName, cp.EnrollmentID, cp.EnrollmentDate, cp.DisenrollmentDate
FROM vwPersonInfo AS p
INNER JOIN (
SELECT c.ClientID, c.EnrollmentID, c.EnrollmentDate, c.DisenrollmentDate
FROM tblCMOEnrollment AS c
LEFT OUTER JOIN tblWorkerHistory AS wh
ON c.EnrollmentID = wh.EnrollmentID
INNER JOIN tblStaffExtended AS se
ON wh.Worker = se.StaffID
WHERE (wh.EndDate IS NULL OR wh.EndDate >= getdate())
AND wh.Worker = --WorkerID Param Here
) AS cp
ON p.PersonID = cp.ClientID
ORDER BY p.PersonID
just put the inner query in its own variable. (It will be translated into one single SQL expression)
var innerQuery = from x in db.tblCMOEnrollment
where ...
select ...;
var query = from a in vwPersonInfo
join b innerQuery on p.PersonID equals cp.ClientID
select ...;
I think you can do this by writing a second method and joining on that method:
private static IEnumerable<Table> GetData(int joinKey)
{
return (from x in context.TableB.Where(id => id.Key == joinKey select x).AsQueryable();
}
Then you can do your normal query:
var query = from c in context.TableA
join GetData(c.PrimaryKeyValue)