EF Core LINQ query failing due to limitation? - c#

I am trying to do quite a simple group by, and sum, with EF Core 3.0
However am getting a strange error:
System.InvalidOperationException: 'Processing of the LINQ expression
'AsQueryable((Unhandled parameter:
y).TransactionLines)' by 'NavigationExpandingExpressionVisitor'
failed. This may indicate either a bug or a limitation in EF Core.
var creditBalances = await context.Transaction
.Include(x => x.TransactionLines)
.Include(x=>x.CreditAccount)
.Where(x => x.CreditAccount.UserAccount.Id == userAccount.Id)
.GroupBy(x => new
{
x.CreditAccount.ExternalId
})
.Select(x => new
{
x.Key.ExternalId,
amount = x.Sum(y => y.TransactionLines.Sum(z => z.Amount))
})
.ToListAsync();
I'm battling to see where an issue can arise, so not even sure where to start. I am trying to get a sum of all the transaction amounts (Which is a Sum of all the TransactionLines for each transaction - i.e. A Transaction amount is made of the lines associated to it).
I then sum up all the transactions, grouping by then CreditAccount ID.
The line, Unhandled parameter: y is worrying. Maybe my grouping and summing is out.

So start at the TransactionLines level and this is as simple as:
var q = from c in context.TransactionLines
where c.Transaction.CreditAccount.UserAccount.Id == userAccount.Id
group c by c.Transaction.CreditAccount.ExternalId into g
select new
{
ExternalId = g.Key,
Amount = g.Sum(x => x.Amount)
};
var creditBalances = await q.ToListAsync();
( You don't need any Include() since you're not returning an Entity with related data. You're projecting a custom data shape. )
Which translates to:
SELECT [c].[ExternalId], SUM([t].[Amount]) AS [Amount]
FROM [TransactionLines] AS [t]
LEFT JOIN [Transaction] AS [t0] ON [t].[TransactionId] = [t0].[Id]
LEFT JOIN [CreditAccounts] AS [c] ON [t0].[CreditAccountId] = [c].[Id]
LEFT JOIN [UserAccount] AS [u] ON [c].[UserAccountId] = [u].[Id]
WHERE [u].[Id] = #__userAccount_Id_0
GROUP BY [c].[ExternalId]

Related

Remove duplicated SUM subquery in Entity Framework Core query

I have the following LINQ code:
return from policy in db.Policy.Include(it => it.LedgerLines)
let balance = policy.LedgerLines.Sum(it => it.Amount)
where balance > 0m && balance < 5m
select policy;
This gets translated to
SELECT ...
FROM [Policy] AS [p]
LEFT JOIN [PolicyLedger] AS [p0] ON [p].[Id] = [p0].[PolicyId]
WHERE (((SELECT SUM([p1].[Amount])
FROM [PolicyLedger] AS [p1]
WHERE [p].[Id] = [p1].[PolicyId]) > 0.0))
AND ((SELECT SUM([p2].[Amount])
FROM [PolicyLedger] AS [p2]
WHERE [p].[Id] = [p2].[PolicyId]) < 5.0)
ORDER BY [p].[Id], [p0].[Id]
Is there any way to only execute the SUM([p1].[Amount]) subquery once?
(EF Core 3.1)
The line
let balance = policy.LedgerLines.Sum(it => it.Amount)
which is the equivalent of intermediate projection clearly indicates the intent to reuse the expression.
But EF Core query translator puts a lot of efforts to produce "pretty" queries by eliminating subqueries as much as possible. Unfortunately in this case it seems to go too much in that regard.
With that being said, you can consider it to be a translation defect, leave the LINQ query "as is" and wait for improved translation - EFC 5.x doesn't improve that, may be EFC 6.0 or later, if ever.
But here is one not so distracting trick to let EFC 3.1 / 5.x generate JOIN to GROUP BY subquery and reuse the SUM expression.
The only change to the original LINQ query is to replace the above let statement with the following
from balance in policy.LedgerLines
.GroupBy(it => it.PolicyId)
.Select(g => g.Sum(it => it.Amount))
which gets translated to
SELECT ...
FROM [Policy] AS [p]
INNER JOIN (
SELECT SUM([p0].[Amount]) AS [c], [p0].[PolicyId]
FROM [PolicyLedger] AS [p0]
GROUP BY [p0].[PolicyId]
) AS [t] ON [p].[Id] = [t].[PolicyId]
LEFT JOIN [PolicyLedger] AS [p1] ON [p].[Id] = [p1].[PolicyId]
WHERE ([t].[c] > 0.0) AND ([t].[c] < 5.0)
ORDER BY [p].[Id], [p1].[Id]
You could start your query from the LedgerLine entity and use a GroupBy() to build the sum of the Amount column for each policy. However, you can't group on a navigation property, so you have to group on the PolicyId instead. This means you need to join the PolicyId column with the Policies table/DbSet afterwards to get the actual Policy entity (with any required included collection properties).
The code can look like this:
var result = context.LedgerLines
.Include(it => it.Policy)
.GroupBy(it => it.PolicyId)
.Select(it => new {
policyId = it.Key,
sum = it.Sum(a => a.Amount)
})
.Join(context.Policies.Include(it => it.LedgerLines),
it => it.policyId,
it => it.Id,
(a,b) => new {
a.sum,
policy=b
})
.Where(it => it.sum > 0m && it.sum < 5m)
.Select(it => it.policy)
.ToList();
This will generate a query like this (for MySQL):
SELECT `p`.`Id`, `p`.`Name`, `l0`.`Id`, `l0`.`Amount`, `l0`.`PolicyId`
FROM (
SELECT `l`.`PolicyId`, SUM(`l`.`Amount`) AS `c`
FROM `LedgerLines` AS `l`
GROUP BY `l`.`PolicyId`
) AS `t`
INNER JOIN `Policies` AS `p` ON `t`.`PolicyId` = `p`.`Id`
LEFT JOIN `LedgerLines` AS `l0` ON `p`.`Id` = `l0`.`PolicyId`
WHERE (CAST(`t`.`c` AS decimal(18, 2)) > 0) AND (CAST(`t`.`c` AS decimal(18, 2)) < 5)
ORDER BY `p`.`Id`, `l0`.`Id`
As you see only one SUM() call is used, but I'm unsure about the performance as you JOIN over the LedgerLines table twice, not to mention that this code looks weird and cumbersome.

EF: Include with where clause, + SubIncludes

This is a follow-up to the question here: Include with where clause. That question wants to find all Awake Passengers on Driving Busses
Without the WHERE Clause on Passengers, that is very simple, like so:
var result = Context.Busses.Where(x => x.IsDriving)
.Include(x => x.Passengers);
Without the WHERE Clause on Passengers, it is also very simple to include Sub-Relationships from Passengers, like so:
var result = Context.Busses.Where(x => x.IsDriving)
.Include(x => x.Passengers.CarryOns)
.Include(x => x.Passengers.Luggage);
But that question requires the use of a WHERE Clause on the Navigation Property.
The Answer to the prior question works perfect, sans Sub-Relationships:
var result = Context.Busses.Where(x => x.IsDriving)
.Select(bus => new {bus, Passengers = bus.Passengers.Where(x => x.Awake)})
.AsEnumerable().Select(x => x.bus).ToList();
But how do you also include the Passenger's CarryOns and Luggage using this method? Passengers is not queryable, so you can't Include at this point. I attempted something like this, but the first portion was just overwritten by the second portion:
var bussesQuery = Context.Busses.Where(x => x.IsDriving)
.Include(x => x.Passengers.CarryOns)
.Include(x => x.Passengers.Luggage);
// var check = bussesQuery.ToList();
// The Sub-Relationship data is included here, but the Passengers are not filtered.
var result = bussesQuery
.Select(bus => new {bus, Passengers = bus.Passengers.Where(x => x.Awake)})
.AsEnumerable().Select(x => x.bus).ToList();
// The Sub-Relationship data is missing, but the Passengers are filtered
By looking on Your previous query I see that You go from the extreme of N+1 to the extreme of single query. You used to have lots of queries, and now You want to have one, but please consider what is going on under the hood. To fetch the data entity framework needs to cross join all entities, so for each included entity you get extra columns in Your results AND result is crossjoined with that include.
Let's say You have 5 driving buses, 30 awake passengers and 15 luggage per bus, as a result You get buses x luggage x passengers = 2250 records, each containing passenger and luggage data`. If you query passengers and luggage with separate queries You will have much less records (5 * 30 + 5 * 15 = 225) and each entity will be fetched once.
Doing one big query that will return everything is not that good idea - it is slower, harder to maintain and not worth Your time. Just do query for awake passengers, and then query for luggage.
For the answer, scroll down to the answer section.
Disclaimer: I love EF. For 99.999% of calls made in my system, I can write the code (LINQ) the fastest, and the OR-Mapping is the fastest system. Also, the Generated Queries (While confusing to look at) have much faster execution plans than hand-written SQL. But that isn't the case here.
Research Section
To begin with an Aside: The raw SQL to view my final request is something like this:
SELECT * FROM [Busses] [bus]
LEFT JOIN [Passengers] [passenger] ON [passenger].[BusID] = [bus].[BusID] AND [passenger].[Awake] <> 1
LEFT JOIN [CarryOns] [carryOn] ON [carryOn].[PassengerID] = [passenger].[PassengerID]
LEFT JOIN [Luggages] [luggage] ON [luggage].[PassengerID] = [passenger].[PassengerID]
WHERE [bus].[IsDriving] = 1
Of course, if EF were to generate something for these results, it would require nesting and key fields to know how to map them. No big deal.
Unfortunately, in order to achieve this with a single hit to the database, I have to do the following:
var busses = context.Set<BusEntity>().Where(x => x.IsDriving);
var passengers = context.Set<PassengerEntity>().Where(x => x.Awake);
var carryOns = context.Set<CarryOnEntity>();
var luggages = context.Set<LuggageEntity>();
var passengerJoins = passengers.GroupJoin(
carryOns,
x => x.PassengerID,
y => y.PassengerID,
(x, y) => new { Passenger = x, CarryOns = y }
)
.SelectMany(
x => x.CarryOns.DefaultIfEmpty(),
(x, y) => new { Passenger = x.Passenger, CarryOns = x.CarryOns }
).GroupJoin(
luggages,
x => x.Passenger.PassengerID,
y => y.PassengerID,
(x, y) => new { Passenger = x.Passenger, CarryOns = x.CarryOns, Luggages = y }
)
.SelectMany(
x => x.Luggages.DefaultIfEmpty(),
(x, y) => new { Passenger = x.Passenger, CarryOns = x.CarryOns, Luggages = x.Luggages }
);
var bussesToPassengers = busses.GroupJoin(
passengerJoins,
x => x.BusID,
y => y.Passenger.BusID,
(x, y) => new { Bus = x, Passengers = y }
)
.SelectMany(
x => x.Passengers.DefaultIfEmpty(),
(x, y) => new { Bus = x.Bus, Passengers = x.Passengers }
)
.GroupBy(x => x.Bus);
var rez = bussesToPassengers.ToList()
.Select(x => x.First().Bus)
.ToList();
I don't complain about the EF Generated SQL, but the single SQL statement was a couple hundred lines. I hacked at it, removed the SELECT columns, and altered some ID's to match this question, it was something like this:
SELECT *
FROM ( SELECT *
FROM (SELECT *
FROM ( SELECT DISTINCT *
FROM [dbo].[Bus] AS [Extent1]
LEFT OUTER JOIN (SELECT *
FROM [dbo].[Passenger] AS [Extent2]
LEFT OUTER JOIN [dbo].[CarryOns] AS [Extent3] ON [Extent2].[PassengerId] = [Extent3].[PassengerId]
LEFT OUTER JOIN [dbo].[Luggages] AS [Extent4] ON [Extent2].[PassengerId] = [Extent4].[PassengerId]
WHERE [Extent1].[IsDriving] = 1
) AS [Distinct1] ) AS [Project2]
OUTER APPLY (SELECT *
FROM (SELECT *
FROM [dbo].[Bus] AS [Extent6]
LEFT OUTER JOIN (SELECT *
FROM [dbo].[Passenger] AS [Extent7]
LEFT OUTER JOIN [dbo].[CarryOns] AS [Extent8] ON [Extent7].[PassengerId] = [Extent8].[PassengerId]
LEFT OUTER JOIN [dbo].[Luggages] AS [Extent9] ON [Extent7].[PassengerId] = [Extent9].[PassengerId]
WHERE ([Extent6].[IsDriving] = 1) AND ([Project2].[BusId] = [Extent6].[BusId]) ) AS [Project3]
OUTER APPLY (SELECT *
FROM [dbo].[Passenger] AS [Extent11]
LEFT OUTER JOIN [dbo].[CarryOns] AS [Extent12] ON [Extent11].[PassengerId] = [Extent12].[PassengerId]
LEFT OUTER JOIN [dbo].[Luggages] AS [Extent13] ON [Extent11].[PassengerId] = [Extent13].[PassengerId]
LEFT OUTER JOIN [dbo].[CarryOns] AS [Extent15] ON [Extent11].[PassengerId] = [Extent15].[PassengerId]
WHERE ([Extent11].[IsAwake] = 1) AND ([Project3].[BusId] = [Extent11].[BusId])
UNION ALL
SELECT *
FROM [dbo].[Passenger] AS [Extent16]
LEFT OUTER JOIN [dbo].[CarryOns] AS [Extent17] ON [Extent16].[PassengerId] = [Extent17].[PassengerId]
LEFT OUTER JOIN [dbo].[Luggages] AS [Extent18] ON [Extent16].[PassengerId] = [Extent18].[PassengerId]
WHERE ([Extent16].[IsAwake] = 1) AND ([Project3].[BusId] = [Extent16].[BusId])
) AS [Project7]
ORDER BY ........................
For my personal test data, My Hand-Written SQL Query returns 54 rows, and the EF Generated Query returns about 30,000 rows. So if you only consider the increase in time for the Over-The-Wire transfer of the data, that is not acceptable.
Answer Section
The answer is: You can use Linq to Entities (on DB) and Linq to Objects (in code) to achieve your results in a single call, but it will not be performant. You can instead choose multiple calls with better performance, including less data transferred over the wire, more readable generated queries, and more understandable code.
The best bet is to perform multiple queries. This is the way I am doing it:
var bus = context.Set<BusEntity>().Where(x => x.IsDriving).ToList();
var busIDs = bus.Select(x => x.BusID).ToList();
var passengers = context.Set<PassengerEntity>().Where(x => x.IsAwake && busIDs.Contains(x.BusID)).ToList();
var passengerIDs = passengers.Select(x => x.PassengerID).ToList();
var carryOns = context.Set<CarryOnEntity>().Where(x => passengerIDs.Contains(x.PassengerID)).ToList();
var luggages = context.Set<LuggageEntity>().Where(x => passengerIDs.Contains(x.PassengerID)).ToList();
passengers.ForEach(x => {
x.CarryOns = carryOns.Where(y => y.PassengerID == x.PassengerID).ToList();
x.Luggages = luggages.Where(y => y.PassengerID == x.PassengerID).ToList();
});
bus.ForEach(x => x.Passengers = passengers.Where(y => y.BusID == x.BusID).ToList());
This generated 4 calls. Altogether, the SQL had about 40 lines. I hacked at it, removed the SELECT columns, and altered some ID's to match this question, it was something like this:
SELECT * FROM [dbo].[Busses] AS [Extent1]
WHERE [Extent1].[IsDriving] = 1
SELECT * FROM [dbo].[Passengers] AS [Extent1]
WHERE ([Extent1].[Awake] = 1) AND ([Extent1].[BusID] IN (......................))
SELECT * FROM [dbo].[CarryOns] AS [Extent1]
WHERE [Extent1].[PassengerID] IN (......................)
SELECT * FROM [dbo].[Luggages] AS [Extent1]
WHERE [Extent1].[PassengerID] IN (......................)
The EF Generated Query returns about 100 rows total across the 4 round-trip calls. So that means 4 calls to the database, but all very small, readable, and very quick.
I didn't time it, but whenever I pause on a breakpoint above this answer's code, and F5 to the other side of the result, it is instant. When I do the same thing for the Single-Call in my research, it took a solid second or more, noticeable lag running over that.

Linq Query With Multiple Joins Not Giving Correct Results

I have a Linq query which is being used to replace a database function. This is the first one with multiple joins and I can't seem to figure out why it returns 0 results.
If you can see any difference which could result in the incorrect return it would be greatly appreciated......I've been trying to solve it longer than I should have.
Linq Query
context.StorageAreaRacks
.Join(context.StorageAreas, sar => sar.StorageAreaId, sa => sa.Id, (sar, sa) => new { sar, sa })
.Join(context.StorageAreaTypes, xsar => xsar.sar.StorageAreaId, sat => sat.Id, (xsar, sat) => new { xsar, sat })
.Join(context.Racks, xxsar => xxsar.xsar.sar.RackId, r => r.Id, (xxsar, r) => new { xxsar, r })
.Where(x => x.xxsar.sat.IsManual == false)
.Where(x => x.r.IsEnabled == true)
.Where(x => x.r.IsVirtual == false)
.Select(x => new { x.xxsar.sat.Id, x.xxsar.sat.Name })
.Distinct()
.ToList();
This is the query which is generated by the LINQ query
SELECT
[Distinct1].[C1] AS [C1],
[Distinct1].[Id] AS [Id],
[Distinct1].[Name] AS [Name]
FROM ( SELECT DISTINCT
[Extent2].[Id] AS [Id],
[Extent2].[Name] AS [Name],
1 AS [C1]
FROM [dbo].[StorageAreaRacks] AS [Extent1]
INNER JOIN [dbo].[StorageAreaTypes] AS [Extent2] ON [Extent1].[StorageAreaId] = [Extent2].[Id]
INNER JOIN [dbo].[Racks] AS [Extent3] ON [Extent1].[RackId] = [Extent3].[Id]
WHERE (0 = [Extent2].[IsManual]) AND (1 = [Extent3].[IsEnabled]) AND (0 = [Extent3].[IsVirtual])
) AS [Distinct1]
Sql Query which produces required results
SELECT DISTINCT sat.Name, sat.Id
FROM StorageAreaRacks sar
JOIN StorageAreas sa on sa.id = sar.StorageAreaId
JOIN StorageAreaTypes sat on sat.id = sa.StorageAreaTypeId
JOIN Racks r on r.id = sar.RackId
WHERE sat.IsManual = 0
AND r.IsEnabled = 1
AND r.IsVirtual = 0
Using joins with LINQ method syntax is hard to read and error prone.
Using joins with LINQ query syntax is better, but still error prone (you can join by the wrong key as you did) and does not give you information about join cardinality.
The best for LINQ to Entities queries is to use navigation properties (as Gert Arnold suggested in the comments and not only - see Don’t use Linq’s Join. Navigate!) because they have none of the aforementioned drawbacks.
The whole query should be something like this:
var query = context.StorageAreaRacks
.Where(sar => !sar.StorageArea.StorageAreaType.IsManual
&& sar.Rack.IsEnabled && !sar.Rack.IsVirtual)
.Select(sar => new
{
sar.StorageArea.StorageAreaType.Id,
sar.StorageArea.StorageAreaType.Name,
})
.Distinct();
or
var query = (
from sar in context.StorageAreaRacks
let sat = sar.StorageArea.StorageAreaType
let r = sar.Rack
where !sat.IsManual && r.IsEnabled && !r.IsVirtual
select new { sat.Id, sat.Name })
.Distinct();
Simple, readable and almost no place for mistakes. Navigation properties are one of the most beautiful features of EF, don't miss them.
Your LINQ doesn't translate the SQL properly; it Joins the StorageAreaTypes on the StorageAreaRack.StorageAreaId instead of on the StorageAreas.StorageAreaTypeId, which is why EF drops the StorageAreas Join - it has no effect on the outcome.
I think it is clearer if you elevate the members of each join to flatten the anonymous objects and name them based on their members (that are the join tables). Also, no reason to separate the Where clauses, LINQ can use && as well as SQL using AND. Also, if you have boolean values, don't compare them to true or false. Also there is no reason to pass range variables through that aren't used later.
Putting it all together:
var ans = context.StorageAreaRacks
.Join(context.StorageAreas, sar => sar.StorageAreaId, sa => sa.Id, (sar, sa) => new { sar, sa })
.Join(context.StorageAreaTypes, sarsa => sarsa.sa.StorageAreaTypeId, sat => sat.Id, (sarsa, sat) => new { sarsa.sar, sat })
.Join(context.Racks, sarsat => sarsat.sar.RackId, r => r.Id, (sarsat, r) => new { sarsat.sat, r })
.Where(satr => !satr.sat.IsManual && satr.r.IsEnabled && !satr.r.IsVirtual)
.Select(satr => new { satr.sat.Id, satr.sat.Name })
.Distinct()
.ToList();
However, I think when multiple joins are involved and when translating SQL, LINQ comprehension syntax can be easier to understand:
var ans = (from sar in context.StorageAreaRacks
join sa in context.StorageAreas on sar.StorageAreaId equals sa.Id
join sat in context.StorageAreaTypes on sa.StorageAreaTypeId equals sat.Id
join r in context.Racks on sar.RackId equals r.Id
where !sat.IsManual && r.IsEnabled && !r.IsVirtual
select new {
sat.Name,
sat.Id
}).Distinct().ToList();
You are missing a Where for your rack ID != null in your LINQ statement, and a Distinct().

Entity Framework grouping by column from join

I have the next query:
select VisitLines.ProcedureId, COUNT(DISTINCT VisitLines.VisitId) as nt
from Visits
LEFT JOIN VisitLines ON Visits.Id = VisitLines.VisitId
WHERE Visits.VisitStatusId = 1 AND Visits.IsActive = 1 AND VisitLines.IsActive = 1
GROUP BY VisitLines.ProcedureId
Main question: Does ability exists to grouping by column from join using linq ? I'm wondering how to do it using 'collection' column.
Is it possible to force EF to generate COUNT(DISTINCT column) ? IQueryable.GroupBy.Select(x => x.Select(n => n.Number).Distinct().Count()) generate query with few subqueries which much slower then COUNT(DISTINCT )
I found. Need to use SelectMany with second parameter resultSelector:
dbContext.Visits.Where(x => x.IsActive)
.SelectMany(x => x.VisitLines, (v, vl) => new
{
v.Id,
vl.ProcedureId
})
.GroupBy(x => x.ProcedureId)
.Select(x => new
{
Id = x.Key,
VisitCount = x.Count()
}).ToArray();
It generates the desired SQL, but with exception that I need distinct count by visit.
And if I change VisitCount = x.Distinct().Count() then EF generates a query with few subqueries again. But the main issue resolved

optimize Entity Framework Linq query (selected 1 unexpected field)

All,
Can anyone help me optimize the following EF/Linq query:
The EF/Linq query (taken from LinqPad):
Articles
.AsNoTracking()
.Where(a => a.Active == "J")
.SelectMany(a => KerlServices
.Where(ks => ks.Service.SAPProductNumber == a.SAPProductNumber))
.Select(ks => new {
ks.KerlCode,
ks.Service.SAPProductNumber,
ks.Service.Type })
.ToList()
The relation between Articles and Services (ks.Service.SAPProductNumber == a.SAPProductNumber) is in theory a 1:optional relation with cannot be defined in EF. This is however not my question.
The resulting SQL query:
SELECT
[Join1].[F_SERVICESID] AS [F_SERVICESID],
[Join1].[F_KERLCOD] AS [F_KERLCOD],
[Join1].[F_SAPARTNUM] AS [F_SAPARTNUM],
[Join1].[F_TYPE] AS [F_TYPE]
FROM [dbo].[T_ART] AS [Extent1]
INNER JOIN (SELECT [Extent2].[F_KERLCOD] AS [F_KERLCOD], [Extent2].[F_SERVICESID] AS [F_SERVICESID], [Extent3].[F_SAPARTNUM] AS [F_SAPARTNUM], [Extent3].[F_TYPE] AS [F_TYPE]
FROM [dbo].[T_SERVICESKERL] AS [Extent2]
INNER JOIN [dbo].[T_SERVICES] AS [Extent3] ON [Extent2].[F_SERVICESID] = [Extent3].[F_ID] ) AS [Join1] ON [Extent1].[F_SAPARTNUM] = [Join1].[F_SAPARTNUM]
WHERE N'J' = [Extent1].[F_ACTIND]
Why does EF generate a query that selects [Join1].[F_SERVICESID]? I don't need this field. Does anyone know a way to prevent this?
Kind regards, Jan.
ADDITION 1:
KerlServices
.AsNoTracking()
.Select(ks => new {
ks.KerlCode,
ks.Service.SAPProductNumber,
ks.Service.Type })
.Join(
Articles,
ks => ks.SAPProductNumber,
a => a.SAPProductNumber,
(ks, a) => new { ks, a.Active })
.Where(ksa => ksa.Active == "J")
.Select(ksa => ksa.ks)
.ToList()
results in:
SELECT
[Extent1].[F_SERVICESID] AS [F_SERVICESID],
[Extent1].[F_KERLCOD] AS [F_KERLCOD],
[Extent2].[F_SAPARTNUM] AS [F_SAPARTNUM],
[Extent2].[F_TYPE] AS [F_TYPE]
FROM [dbo].[T_SERVICESKERL] AS [Extent1]
INNER JOIN [dbo].[T_SERVICES] AS [Extent2] ON [Extent1].[F_SERVICESID] = [Extent2].[F_ID]
INNER JOIN [dbo].[T_ART] AS [Extent3] ON [Extent2].[F_SAPARTNUM] = [Extent3].[F_SAPARTNUM]
WHERE N'J' = [Extent3].[F_ACTIND]
This 'improvement' does not answer my own question, but the result surely looks prettier to me.
UPDATE 1:
The query in Ivan Stoev's answer produces the following SQL:
SELECT
[Extent1].[F_SERVICESID] AS [F_SERVICESID],
[Extent1].[F_KERLCOD] AS [F_KERLCOD],
[Extent2].[F_SAPARTNUM] AS [F_SAPARTNUM],
[Extent2].[F_TYPE] AS [F_TYPE]
FROM [dbo].[T_SERVICESKERL] AS [Extent1]
INNER JOIN [dbo].[T_SERVICES] AS [Extent2] ON [Extent1].[F_SERVICESID] = [Extent2].[F_ID]
WHERE EXISTS (SELECT
1 AS [C1]
FROM [dbo].[T_ART] AS [Extent3]
WHERE (N'J' = [Extent3].[F_ACTIND]) AND ([Extent3].[F_SAPARTNUM] = [Extent2].[F_SAPARTNUM])
)
Why does EF generate a query that selects [Join1].[F_SERVICESID]? I don't need this field.
That's weird if true, I have no explanation for that.
Can anyone help me optimize the following EF/Linq query
It's worth trying the following, which for me represents the most logical way to retrieve the data in question:
KerlServices
.AsNoTracking()
.Select(ks => new {
ks.KerlCode,
ks.Service.SAPProductNumber,
ks.Service.Type })
.Where(ks => Articles.Any(a => a.Active == "J" && a.SAPProductNumber == ks.SAPProductNumber)
.ToList()
UPDATE: Recently I've encountered that EF includes some additional fields in the generated SQL query when dialing with foreign key relations. These fields are not included in the projected result, so I think you should not worry about. Take any of the queries above, execute it inside the real code environment (VS Debug) and check the the projected list - I'm pretty sure the field in question will not be there.

Categories