EF LINQ query to SQL OUTER APPLY - entity-framework

How to generate such SQL code:
SELECT TOP(10) c.Id AS CarId, r.FranceAccessoriesCount, r.GermanyAccessoriesCount, r.ItalyAccessoriesCount
FROM [Cars] AS c
OUTER APPLY (SELECT
SUM(CASE WHEN a.ManufacturerCountry = 'France' then 1 ELSE 0 END) as FranceAccessoriesCount,
SUM(CASE WHEN a.ManufacturerCountry = 'Germany' then 1 ELSE 0 END) as GermanyAccessoriesCount,
SUM(CASE WHEN a.ManufacturerCountry = 'Italy' then 1 ELSE 0 END) as ItalyAccessoriesCount
FROM [Accessories] a
WHERE a.CarId = c.Id AND a.[Year] > 1999) r
using LINQ query (EF Core)?
I've tried:
await this.context.Cars
.Take(10)
.Select(c => new
{
CarId = c.Id,
Accessories = this.context.Accessories.Where(a => a.CarId == c.Id && a.Year > 1999)
})
.Select(c => new
{
CarId = c.CarId,
FranceAccessoriesCount = c.Accessories.Count(a => a.ManufacturerCountry == "France"),
GermanAccessoriesCount = c.Accessories.Count(a => a.ManufacturerCountry == "Germany"),
ItalyAccessoriesCount = c.Accessories.Count(a => a.ManufacturerCountry == "Italy")
})
.ToListAsync();
But this doesn't generate OUTER APPLY. Instead it translates to:
SELECT TOP(10) [c].[Id] AS CarId (
SELECT COUNT(*)
FROM [Accessories] AS [a]
WHERE (([a].[CarId] = [c].[Id]) AND [a].[Year] > 1999) AND ([a].[ManufacturerCountry ] = N'France')) AS [FranceAccessoriesCount ], (
SELECT COUNT(*)
FROM [Accessories] AS [a1]
WHERE (([a].[CarId] = [c].[Id]) AND [a1].[Year] > 1999) AND ([a1].[ManufacturerCountry ] = N'Germany')) AS [GermanyAccessoriesCount ], (
SELECT COUNT(*)
FROM [Accessories] AS [a2]
WHERE (([a2].[CarId] = [c].[Id]) AND [a2].[Year] > 1999) AND ([a2].[ManufacturerCountry ] = N'Italy')) AS [ItalyAccessoriesCount]
FROM [Cars] AS [c]
How to write LINQ query which will translate in OUTER APPLY?

EF is a mapper. You tell it what you want using entities as you have them defined and it generates an SQL statement to retrieve and project that data. It may not be optimal in all circumstances but generally it is quite good from a performance perspective.
If the EF query is not returning the data you expect to see, then approach it from the desired data you want to project to and express a question from what projection you want vs. what projection you get.
If the EF query is returning the data you expect, but simply isn't using a CROSS APPLY like you expect, then the answer is that if this is important, don't rely on the EF generated query and instead have it execute a hand-crafted SQL statement.

Related

How to use sql query withpivot () into entityframework core 5.0

SELECT BuildingID,
BuildingName,
[-1] AS 'In Active',
[0] 'Booked',
[1] 'Occupied',
[2] 'Vacant',
[3] 'Temporary Booked',
[4] 'Under Clearance',
[5] 'Under Construction',
[6] 'Reserved',
[7] 'Temporary Leased',
[8] 'Paid Booked',
[9] 'Booking Payment Pending',
Total=(SELECT Sum(TB.total_count)
FROM (VALUES([-1]),([0]),([1]),([2]),([3]),([4]),([5]),([6]),([7]),([8]),([9])) AS TB(total_count))
FROM (SELECT b.buildingid,
b.BuildingName,
f.FacStatusID,
f.FacilityNo
FROM Facility AS f
INNER JOIN FacilityBuilding b
ON f.BuildingID = b.BuildingID
WHERE FacilityTypeID =1
AND FacilitySubTypeID = #facilitySubTypeId and f.BuildingID=#buildingId) AS SourceTable
PIVOT ( Count(FacilityNo)
FOR FacStatusId IN ([-1],[0],[1],[2],[3],[4],[5],[6],[7],[8],[9]) ) AS pivottable
ORDER BY BuildingID;
I need to either convert this query in entity framework or use this stored procedure there. I don't have any entity in my db context related to this result. But I have separate entities such as facilities and facilityBuildings where buildingId is the primary key and serves as foreign key in facilities table
I have attached the screenshot too of the desired output.
please help me with how we can use this query for getting the desired attached output using entity framework core 5.0
In LINQ, as in SQL the alternative form for this query is a conditional aggregate query, so something like:
var q = from facility in db.Set<Facility>()
where facility.FacilityTypeId == 1
where facility.FacilitySubTypeId == subtype
where facility.FacilityBuilding.BuildingId == buildingId
group facility by facility.FacStatusId into byStatus
select new
{
BuildingId = byStatus.Max(g => g.FacilityBuilding.BuildingId),
BuildingName = byStatus.Max(g => g.FacilityBuilding.BuildingName),
Booked = byStatus.Count(f => f.FacStatusId == -1),
Occupied = byStatus.Count(f => f.FacStatusId == 0),
Vacant = byStatus.Count(f => f.FacStatusId == 1),
TemporaryBooked = byStatus.Count(f => f.FacStatusId == 2),
Total = byStatus.Count()
};
translates to
SELECT MAX([f1].[BuildingId]) AS [BuildingId], MAX([f1].[BuildingName]) AS [BuildingName], COUNT(CASE
WHEN [f].[FacStatusId] = -1 THEN 1
END) AS [Booked], COUNT(CASE
WHEN [f].[FacStatusId] = 0 THEN 1
END) AS [Occupied], COUNT(CASE
WHEN [f].[FacStatusId] = 1 THEN 1
END) AS [Vacant], COUNT(CASE
WHEN [f].[FacStatusId] = 2 THEN 1
END) AS [TemporaryBooked], COUNT(*) AS [Total]
FROM [Facility] AS [f]
LEFT JOIN [FacilityBuilding] AS [f0] ON [f].[FacilityBuildingId] = [f0].[FacilityBuildingId]
LEFT JOIN [FacilityBuilding] AS [f1] ON [f].[FacilityBuildingId] = [f1].[FacilityBuildingId]
WHERE (([f].[FacilityTypeId] = 1) AND ([f].[FacilitySubTypeId] = #__subtype_0)) AND ([f0].[BuildingId] = #__buildingId_1)
GROUP BY [f].[FacStatusId]

Querying for any entries after traversing multiple conditional includes in EF Core 5

Consider the following data model:
A Principal has a number of Roles (many-to-many);
Roles grant multiple Permissions (many-to-many);
Now i want to use LINQ to determine whether a Principle has a permission, i.e. whether he is in any Roles that possess this permission.
Usually I would go for AnyAsync on the join table for cases like this , but since I am traversing more than one join table I am really struggling. I initially came up with this:
var query = context.Principals
.Where(p => p.Id == "SomeUUID")
.Include(p => p.Roles)
.ThenInclude(r => r.Permissions
.Where(p => p.Name == "SomePermission"));
But now I would have to traverse, probably in-memory, the attached Roles through the Principal again to search for Any Permission.
Is there a way I can smoothly apply this check within the same query?
EDIT: Here is the generated SQL to complement Parks'(bottom) answer in comparison:
SELECT CASE
WHEN EXISTS (
SELECT 1
FROM [Principals] AS [a]
INNER JOIN (
SELECT [a1].[Id], [a1].[Description], [a1].[DisplayName], [a0].[RoleId], [a0].[PrincipalId]
FROM [PrincipalRoles] AS [a0]
INNER JOIN [Roles] AS [a1] ON [a0].[RoleId] = [a1].[Id]
) AS [t] ON [a].[PrincipalId] = [t].[PrincipalId]
INNER JOIN (
SELECT [a3].[Id], [a3].[Name], [a2].[RoleId], [a2].[PermissionId]
FROM [RolePermissions] AS [a2]
INNER JOIN [Permissions] AS [a3] ON [a2].[PermissionId] = [a3].[Id]
) AS [t0] ON [t].[Id] = [t0].[RoleId]
WHERE ([a].[PrincipalId] = "SomePrincipal") AND ([t0].[Name] = "SomePermission")) THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END
SELECT CASE
WHEN EXISTS (
SELECT 1
FROM [Principals] AS [a]
WHERE ([a].[PrincipalId] = "SomePrincipal") AND EXISTS (
SELECT 1
FROM [PrincipalRoles] AS [a0]
INNER JOIN [Roles] AS [a1] ON [a0].[RoleId] = [a1].[Id]
WHERE ([a].[PrincipalId] = [a0].[PrincipalId]) AND EXISTS (
SELECT 1
FROM [RolePermissions] AS [a2]
INNER JOIN [Permissions] AS [a3] ON [a2].[PermissionId] = [a3].[Id]
WHERE ([a1].[Id] = [a2].[RoleId]) AND ([a3].[Name] = "SomePermission")))) THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END
To see if a principal has a Role in Roles that has a Permission in Permissions you can use the following Where condition:
var result = await context.Principals
.Where(p => p.Id == "SomeUUID" && p.Roles.Any(r => r.Permissions.Any(x => x.Name == "SomePermission")))
.FirstOrDefaultAsync();
If result is null then either the Principal did not exist or did not have the permission you checked for. If result still needs to have the navigation properties then you can add the include statements originally included in your question.
Probably you are looking for this query:
var hasPermssion = context.Principals
.Where(p => p.Id == "SomeUUID")
.SelectMany(p => p.Roles)
.SelectMany(r => r.Permissions)
.Any(p => p.Name == "SomePermission");
Here is the generated SQL statement:
SELECT CASE
WHEN EXISTS (
SELECT 1
FROM [Principals] AS [a]
INNER JOIN (
SELECT [a1].[Id], [a1].[Description], [a1].[DisplayName], [a0].[RoleId], [a0].[PrincipalId]
FROM [PrincipalRoles] AS [a0]
INNER JOIN [Roles] AS [a1] ON [a0].[RoleId] = [a1].[Id]
) AS [t] ON [a].[PrincipalId] = [t].[PrincipalId]
INNER JOIN (
SELECT [a3].[Id], [a3].[Name], [a2].[RoleId], [a2].[PermissionId]
FROM [RolePermissions] AS [a2]
INNER JOIN [Permissions] AS [a3] ON [a2].[PermissionId] = [a3].[Id]
) AS [t0] ON [t].[Id] = [t0].[RoleId]
WHERE ([a].[PrincipalId] = "SomePrincipal") AND ([t0].[Name] = "SomePermission")) THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END

How to do left join with group by in NET Core 3.0

As we know in .NET Core 3.0 a lot has changed and I'm building queries which don't run on client side memory.
I want to achieve a left join which count the items from child table keeping the parent intact if there no children against it.
Desired output query:
SELECT [c].[Type] AS [Name], COUNT([c0].Id) AS [Count]
FROM [CustomerTypes] AS [c]
LEFT JOIN [Customers] AS [c0] ON [c].[Id] = [c0].[CustomerTypeId]
WHERE [c0].[Id] IS NULL OR (([c0].[CompanyId] = 1) AND [c0].[CompanyId] IS NOT NULL)
GROUP BY [c].[Id], [c].[Type]
Entity Framework Queries
from types in _dbManager.CustomerTypes
join customers in _dbManager.Customers
on types.Id equals customers.CustomerTypeId into tempJoin
from leftJoined in tempJoin.DefaultIfEmpty()
where leftJoined == null || leftJoined.CompanyId == 1
group leftJoined by new { types.Id, types.Type } into grouped
select new TabCountBindingModel()
{
Name = grouped.Key.Type,
Count = grouped.Count(c => c != null)
}
Output Query
SELECT [c].[Type] AS [Name], COUNT(*) AS [Count]
FROM [CustomerTypes] AS [c]
LEFT JOIN [Customers] AS [c0] ON [c].[Id] = [c0].[CustomerTypeId]
WHERE [c0].[Id] IS NULL OR (([c0].[CompanyId] = 1) AND [c0].[CompanyId] IS NOT NULL)
GROUP BY [c].[Id], [c].[Type]
No matter what I try I always end up with query above, either this or an exception.
Tried these too
from types in _dbManager.CustomerTypes
join customers in _dbManager.Customers
on types.Id equals customers.CustomerTypeId into tempJoin
from leftJoined in tempJoin.DefaultIfEmpty()
where leftJoined == null || leftJoined.CompanyId == 1
group leftJoined by new { types.Id, types.Type } into grouped
select new TabCountBindingModel()
{
Name = grouped.Key.Type,
Count = grouped.Where(c=>c != null).Count()
}
Results in exception.

Select one record for group with Entity Framework Core method syntax

In Entity Framework Core I need to express the following SQL query by using the method syntax instead.
select *
from [Rule] as t1
where
(
select count(*)
from [Rule] as t2
where t1.ProductID = t2.ProductID
and t2.Priority > t1.Priority
) = 0
The query returns the records with the highest Priority for each ProductID.
Is there a way?
Thanks.
Try this :
var rules = db.Rules.Where(
a=>db.Rules.Where(
b => b.ProductId == a.ProductId && b.Priority > a.Priority
).Count() == 0
);
if you'd like use Linq syntax :
var rules = from a in db.Rules
where (
from b in db.Rules
where a.ProductId == b.ProductId
&& b.Priority > a.Priority
select b
).Count() == 0
select a;

linq subquery join and group by

Hi is it possible to translate the below queries into linq ? ...
I am using entity frameworks core and tried to use the stored procedure but it seems like i have to create a model that applies to the metadata of the stored procedure. So i am trying to understand whether this kinda such query can be translated into linq so i don't have to create a separate db model.
SELECT
Stock.stockID ProductID,
stockName ProductName,
categoryName ProductCategory,
typeName ProductType,
sizeName ProductSize,
currentQuantity CurrentQuantity,
standardQuantity QuantityPerBox,
CONVERT(VARCHAR(255),CONVERT(INT,Stock.price)) AvgUnitCost,
CONVERT(VARCHAR(255),CONVERT(INT,x.lastUnitCost)) LastOrderUnitCost,
CONVERT(VARCHAR(10),CONVERT(DATE,x.lastOrderDate)) LastOrderDate
FROM dbo.Stock
LEFT JOIN
(
SELECT stockID,unitPrice lastUnitCost ,orderDate lastOrderDate, ROW_NUMBER() OVER (PARTITION BY stockID ORDER BY orderDate DESC) rn FROM dbo.SalesOrder
JOIN dbo.SalesOrderDetail ON SalesOrderDetail.salesOrderID = SalesOrder.salesOrderID
WHERE customerID = #customerID AND salesStatus = 'S'
) x ON x.stockID = Stock.stockID AND rn = 1
LEFT JOIN dbo.StockCategory ON StockCategory.stockCategoryID = Stock.stockCategoryID
LEFT JOIN dbo.StockType ON StockType.stockTypeID = Stock.stockTypeID
LEFT JOIN dbo.StockSize ON StockSize.stockSizeID = Stock.stockSizeID
WHERE disStock = 0
Almost everything is possible. you just need to be careful with performance.
var query =
from stock in db.Stocks
join x in (
from grp in (
from so in db.SalesOrders
join sod in db.SalesOrderDetails on so.SalesOrderId equals sod.SalesOrderId
where so.CustomerId == customerId && so.SalesStatus == "S"
orderby so.OrderDate descending
select new {
sod.StockId,
LastUnitCost = sod.UnitPrice,
LastOrderDate = so.OrderDate
} into inner
group inner by inner.StockId)
select grp.Take(1)) on x.StockId equals stock.StockId into lastStockSales
from x in lastStockSales.DefaultIfEmpty()
join sc in db.StockCategories on stock.StockCatergotyId equals sc.StockCategoryId into scLeft
from sc in scLeft.DefaultIfEmpty()
join st in db.StockTypes on stock.StockTypeId equals st.StockTypeId into stLeft
from st in stLeft.DefaultIfEmpty()
join ss in db.StockSizes on stock.StockSizeId equals ss.StockSizeId into ssLeft
from ss in ssLeft.DefaultIfEmpty()
where stock.DisStock == 0
select new MyDTO {
ProductId = stock.StockId,
ProductName = stock.StockName,
ProductType = st.TypeName,
ProductSize = ss.SizeName,
CurrentQuantity = stock.CurrentQuantity,
QuantityPerBox = stock.StandardQuantity,
AvgUnitCost = stock.Price,
LastOrderUnitCost = x.LastUnitCost,
LastOrderDate = x.LastOrderDate
};
As you can see is easy to rewrite these queries, I had to change a little bit the logic on how to get the latest sales for a stock item since ROW_NUMBER() OVER (PARTITION... is not supported from a LINQ perspective. Again, you would have to consider performance when rewriting queries.
Hope this helps.