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.
Related
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.
I have a database with the following schema:
Now, I'm trying to pull all landingpages for a domain and sort those by the first UrlFilter's FilterType that matches a certain group. This is the LINQ I've come up with so far:
var baseQuery = DbSet.AsNoTracking()
.Where(e => EF.Functions.Contains(EF.Property<string>(e, "Url"), $"\"{searchTerm}*\""))
.Where(e => e.DomainLandingPages.Select(lp => lp.DomainId).Contains(domainId));
var count = baseQuery.Count();
var page = baseQuery
.Select(e => new
{
LandingPage = e,
UrlFilter = e.LandingPageUrlFilters.FirstOrDefault(f => f.UrlFilter.GroupId == groupId)
})
.Select(e => new
{
e.LandingPage,
FilterType = e.UrlFilter == null ? UrlFilterType.NotCovered : e.UrlFilter.UrlFilter.UrlFilterType
})
.OrderBy(e => e.FilterType)
.Skip(10).Take(75).ToList();
Now, while this technically works, it's quite slow with execution times ranging from 10-30 seconds, which is not good enough for the use case. The LINQ is translated to the following SQL:
SELECT [l1].[Id], [l1].[LastUpdated], [l1].[Url], CASE
WHEN (
SELECT TOP(1) [l].[LandingPageId]
FROM [LandingPageUrlFilters] AS [l]
INNER JOIN [UrlFilters] AS [u] ON [l].[UrlFilterId] = [u].[Id]
WHERE ([l1].[Id] = [l].[LandingPageId]) AND ([u].[GroupId] = #__groupId_3)) IS NULL THEN 4
ELSE (
SELECT TOP(1) [u0].[UrlFilterType]
FROM [LandingPageUrlFilters] AS [l0]
INNER JOIN [UrlFilters] AS [u0] ON [l0].[UrlFilterId] = [u0].[Id]
WHERE ([l1].[Id] = [l0].[LandingPageId]) AND ([u0].[GroupId] = #__groupId_3))
END AS [FilterType]
FROM [LandingPages] AS [l1]
WHERE CONTAINS([l1].[Url], #__Format_1) AND #__domainId_2 IN (
SELECT [d].[DomainId]
FROM [DomainLandingPages] AS [d]
WHERE [l1].[Id] = [d].[LandingPageId]
)
ORDER BY CASE
WHEN (
SELECT TOP(1) [l2].[LandingPageId]
FROM [LandingPageUrlFilters] AS [l2]
INNER JOIN [UrlFilters] AS [u1] ON [l2].[UrlFilterId] = [u1].[Id]
WHERE ([l1].[Id] = [l2].[LandingPageId]) AND ([u1].[GroupId] = #__groupId_3)) IS NULL THEN 4
ELSE (
SELECT TOP(1) [u2].[UrlFilterType]
FROM [LandingPageUrlFilters] AS [l3]
INNER JOIN [UrlFilters] AS [u2] ON [l3].[UrlFilterId] = [u2].[Id]
WHERE ([l1].[Id] = [l3].[LandingPageId]) AND ([u2].[GroupId] = #__groupId_3))
END
OFFSET #__p_4 ROWS FETCH NEXT #__p_5 ROWS ONLY
Now my question is, how can I improve the execution time of this? Either by SQL or LINQ
EDIT: So I've been tinkering with some raw SQL and this is what I've come up with:
with matched_urls as (
select l.id, min(f.urlfiltertype) as Filter
from landingpages l
join landingpageurlfilters lpf on lpf.landingpageid = l.id
join urlfilters f on lpf.urlfilterid = f.id
where f.groupid = #groupId
and contains(Url, '"barz*"')
group by l.id
) select l.id, 5 as Filter
from landingpages l
where #domainId in (
select domainid
from domainlandingpages dlp
where l.id = dlp.landingpageid
) and l.id not in (select id from matched_urls ) and contains(Url, '"barz*"')
union select * from matched_urls
order by Filter
offset 10 rows fetch next 30 rows only
This performs somewhat okay, cutting the execution time down to ~5 seconds. As this is to be used for a table search I would however like to get it down even further. Is there any way to improve this SQL?
You're right to have a look at the generated SQL. In general, I would advise to learn SQL, write a performing SQL query and work your way back (either use a stored procedure or raw SQL, or design your LINQ query with that same philosophy.
I suspect this will be better (not tested):
var page = (
from e in baseQuery
let urlFilter = e.LandingPageUrlFilters.OrderBy(f => f.UrlFilterType).FirstOrDefault(f => f.UrlFilter.GroupId == groupId)
let filterType = urlFilter == null ? UrlFilterType.NotCovered : e.UrlFilter.UrlFilter.UrlFilterType
select new
{
LandingPage = e,
FilterType = filterType
}
).Skip(10).Take(75).ToList();
one of the way to improve the execution time is see execution plan in SSMS (SQL Server Management Studio).
After look on the execution plan you can design some indexes, or if you have no experiences with this, you can see if SSMS recommends some indexes.
Next try to create the indexes and execute the query again and see if execution time was improved.
Note: this is only one of many possible ways to improve execution time...
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]
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().
I'm quite new to NHibernate and QueryOver and I can't get NHibernate to generate the SQL I need.
I need to make a join and have an extra criteria on so I avoid getting to much data from the table I'm joining with.
The SQL I receive from QueryOver is:
SELECT * FROM adresse this_
left outer join r580_test.afvigelse remarkalia1_ on this_.id=remarkalia1_.adrid
left outer join r580_test.afvigelseklagepunkter remarkcomp5_ on remarkalia1_.id=remarkcomp5_.afvigelseid
left outer join r580_test.klagepunkter complainta2_ on remarkcomp5_.klagepunktid=complainta2_.id
WHERE this_.id = 16633 and remarkalia1_.dato between '2009-03-13 00:00:00' and '02-03-2012 16:34:35'
What I would like is this(the where date between has been moved to the end for the first left outer join):
SELECT * FROM adresse this_
left outer join r580_test.afvigelse remarkalia1_ on this_.id=remarkalia1_.adrid and remarkalia1_.dato between '2009-03-13 00:00:00' and '02-03-2012 16:34:35'
left outer join r580_test.afvigelseklagepunkter remarkcomp5_ on remarkalia1_.id=remarkcomp5_.afvigelseid
left outer join r580_test.klagepunkter complainta2_ on remarkcomp5_.klagepunktid=complainta2_.id
WHERE this_.id = 16633
My QueryOver looks like this:
adr = session.QueryOver<Address>()
.Where(x => x.Id == 16633)
.JoinQueryOver<Remark>(y => y.Remarks).Where(y => y.Created > DateTime.Now.AddDays(-14))
.JoinAlias(y => y.RemarkComplaint, () => complaintAlias, JoinType.LeftOuterJoin)
.SingleOrDefault();
Anyone got an idea about how to fix this?
There are several overloads for joinqueryover - I believe you want something like:
Remark remark = null;
adr = session.QueryOver<Address>()
.Where(x => x.Id == 16633)
.JoinQueryOver<Remark>(y => y.Remarks, () => remark, y => y.Created > DateTime.Now.AddDays(-14))
.JoinAlias(y => y.RemarkComplaint, () => complaintAlias, JoinType.LeftOuterJoin)
.SingleOrDefault();
In this case the third parameter is the withClause which, I believe, will add the restriction to the join.