Automapper ProjectTo membersToExpand with filtering - c#

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 ?

Related

How do I mimic a SQL Outer Apply in Linq using Entity Framework?

I would like to mimic a SQL OUTER APPLY using linq.
I have 2 tables: Main and Sub
The SQL looks something like this:
select
M.Id, M.Number, M.Stuff, SD.SubName
from
Main as M
outer apply (
select top 1 SubName
from Sub S
where M.Id = S.Id and M.Number = S.Number
) as SD
Based answers here and elsewhere like this one,I've tried too many iterations of Linq to put in here, but here's one:
var query1 =
from m in dbContext.Main
join s in dbContext.Sub on new {m.Id, m.Number} equals new {s.Id, s.Number} into subs
select new
{
m,
SubName = subs.FirstOrDefault().SubName
}
This compiles fine, but when I run it I get this exception:
Processing of the LINQ expression 'DbSet<Main>
// EF's attempt to translate my query
'NavigationExpandingExpressionVisitor' failed. This may indicate either a bug or a limitation in EF Core. See https://go.microsoft.com/fwlink/?linkid=2101433 for more detailed information.
and a stack trace.
Does anyone have any suggestions on how to go about coding this the correct way?
I'm running .NET core 3.1 against SQL Server 2017.
Try the following queries. EF Core 3.1, should translate this to Outer Appply, but higher versions may use JOIN and ROW_NUMBER
var query1 =
from m in dbContext.Main
from s in dbContext.Sub
.Where(s => m.Id == s.Id && m.Number == s.Number)
.Take(1)
.DefaultIfEmpty()
select new
{
m,
SubName = s.SubName
}
Or this variant:
var query1 =
from m in dbContext.Main
select new
{
m,
SubName = dbContext.Sub
.Where(s => m.Id == s.Id && m.Number == s.Number)
.Select(s => s.SubName)
.FirstOrDefaut()
}

How to rewrite correlated join from SQL to LINQ

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

Get a list result from another list and a property

I have the following 3 (simplified) model classes, each of which contains a collection of the other:
Group.CollectionOfPermissions
Group.CollectionOfUsers
User.CollectionOfGroups
User.CollectionOfPermissions
Permission.CollectionOfGroups
Permission.CollectionOfUsers
I have a View that is based off a single User.ID, and I want to be able to return the effective permissions for said user.
The effective permissions are based off:
The individual users' permissions, which is simply the User.CollectionOfPermissions property.
The derived permissions of the groups that the user is a part of. That is, for every Group to which the User belongs to, I need to grab those Permissions as well.
Number 1 is obviously as simple as referencing the collection property.
Number 2 is where I'm having a bit more trouble with a LINQ selection.
I could write a stored proc along the lines of:
SELECT * FROM PERMISSIONS P WHERE P.ID IN
(SELECT PERMISSION_ID FROM PERMISSION_GROUP_REF PGR WHERE PGR.GROUP_ID IN
(SELECT ID FROM GROUPS G WHERE G.ID IN
(SELECT GROUP_ID FROM GROUP_USER_REF GUR WHERE GUR.USER_ID IN
(SELECT ID FROM USERS U WHERE U.ID = #USERID))))
But I'd much rather keep this in line with the rest of the project and continue to use LINQ, especially since I want to avoid directly querying the reference tables in code (given that the collections already exist as class properties). How would I approach this kind of LINQ query?
Edit: This is using Entity Framework 6 with Razor 3
Users.Where(u => u.UserId == userId)
.SelectMany(u => u.CollectionOfPermissions)
.Select (cp=>cp.Permission) // you might need to do this too
.Union(Users.Where(u => u.UserId == userId)
.SelectMany(u => u.CollectionOfGroups)
.SelectMany(cg => cg.Permission))
May be something like this.
EDIT: For reference, this produces the following SQL (slightly different column names in my test rig):-
SELECT
[Distinct1].[C1] AS [C1]
FROM ( SELECT DISTINCT
[UnionAll1].[Permission_Id] AS [C1]
FROM (SELECT
[Extent1].[Permission_Id] AS [Permission_Id]
FROM [dbo].[PermissionPersons] AS [Extent1]
WHERE 1 = [Extent1].[Person_Id]
UNION ALL
SELECT
[Extent3].[Permission_Id] AS [Permission_Id]
FROM [dbo].[PersonGroups] AS [Extent2]
INNER JOIN [dbo].[PermissionGroups] AS [Extent3] ON [Extent2].[Group_Id] = [Extent3].[Group_Id]
WHERE 1 = [Extent2].[Person_Id]) AS [UnionAll1]
) AS [Distinct1]
On another thought, why not query through Permission entity all together?
context.Permissions.Where(p=>
p.Groups.Any(gr=>gr.Users.Any(u=>u.UserId == userId))
|| p.Users.Any(u=>u.UserId == userId))
.Distinct()
The SQL you posted translates to this:
PERMISSIONS.Where(p =>
PERMISSION_GROUP_REF.Where(pg =>
GROUPS.Where(g =>
GROUP_USER_REF.Where(gu => gu.USER_ID == USERID)
.Any(gu => gu.GROUP_ID == g.ID))
.Any(g => g.ID == pg.GROUP_ID))
.Any(pg => pg.PERMISSION_ID == p.ID))
Maybe you can simplify it a bit, but this should work.

Convert this NHibernate query with queryover

I have some problem to convert this NHibernate queries into the left join queryover
var query = session.Query<T>.Join(
Session.Query<RecordOrder>(),
q=>q.MiniDbName,
o=>o.DatabaseName,
(q,o)=>new{Record = q, Order = o.OrderValue})
Anyone can help me, I want this query support the left join.
The default join is an inner-join. Each of the additional join types can be specified using the methods .Inner, .Left, .Right, or .Full. For example, to left outer-join on Kittens use:
IQueryOver<Cat,Kitten> catQuery =
session.QueryOver<Cat>()
.Left.JoinQueryOver(c => c.Kittens)
.Where(k => k.Name == "Tiddles");
In your case :
var list =
session.QueryOver<RecordOrder>()
.Left.JoinQueryOver(c => c.Orders).ToList()

How do I create multiple joins using LINQ extension methods?

I'm having trouble using LINQ method calls with multiple joins. I'm trying to do something like this:
if (!isDepSelect)
{
query = (from Items in db.DEPARTMENTs
select Items);
}
else
{
query = (from Items in db.DEPARTMENTs
from gDept in db.DEPT_PROFILE
from wAccess in db.WEB_ACCESS
where Items.DEPT_CODE == gDept.DEPT_CODE && gDept.USER_ID == wAccess.USER_ID && wAccess.EMP_ID == id
select Items);
}
I had done this:
IQueryable<DEPARTMENT> query = db.DEPARTMENTs;
if (isDepSelect)
{
query = query.Join(db.DEPT_PROFILE,depts => depts.DEPT_CODE,prof => prof.DEPT_CODE,(depts, prof) => depts);
}
But now I don't know how to add the JOIN of DEPT_PROFILE table with the WEB_ACCESS table and the condition of the EMP_ID = id.
The reason I'm doing this is that the isDepSelect boolean is not the only condition that this query will change its relations and I need someway to add this relations without repeating my LINQ for each of my conditions.
Thank you for your time.
Try with,
List<DEPARTMENTs> list = db.DEPARTMENTs.Join(db.DEPT_PROFILE, dept => dept.DEPT_CODE, prof => prof.DEPT_CODE, (dept,prof) => new {dept, prof})
.Join(Wdb.WEB_ACCESS, depts => depts.prof.USER_ID,web => web.USER_ID,(depts,web) => new { depts, web})
.Where(result => result.web.EMP_ID== id).Select(s => s.depts.dept).ToList<DEPARTMENTs>();
If you have your associations setup, you can do this without any joins in your code at all:
query = db.DEPARTMENTs
.Any(item => item.DEPT_PROFILEs
.Any(gDept => gDept.WEB_ACCESSs
.Any(wAccess => wAccess.EMP_ID == id)));
Of course this is assuming a 1-m relationship between each of the objects in the graph. You can eliminate some of the Any methods if there are 1-0..1 relationships in the graph as necessary.
you should use the equals operator...
query = from Items in db.DEPARTMENTs
from gDept in db.DEPT_PROFILE
join wAccess in db.WEB_ACCESS on
gDept.DEPT_CODE equals Items.DEPT_CODE
select Items;
thats just a snippet of your example query, but you can see how i am using the join operator to introduce a 2nd table and the equals operator to declare the joining columns.
This should work:
query = (from Items in db.DEPARTMENTs
join gDept in db.DEPT_PROFILE
on Items.DEPT_CODE equals gDept.DEPT_CODE
join wAccess in db.WEB_ACCESS
on gDept.USER_ID equals wAccess.USER_ID
where wAccess.EMP_ID == id
select Items);

Categories

Resources