EF Core join entity IQueryable with id IQueryable - c#

I try to join a IQueryable<int>, which is a list of entity ids with an IQueryable<Entity>.
For this I use the following code:
IQueryable<Entity> entityQuery = _context.Entities.Where(x => ...);
IQueryable<int> idQuery = _context.AccessRights.Where(x => ...).Select(x => x.Id);
query = entityQuery.Join(idQuery, x => x.Id, x => x, (x, y) => x);
This code is working with in-memory lists, however if I try it with LINQ to sql, the Join is getting ignored.
I don't want to load the ids into memory. Is this even possible with LINQ to SQL?

You could do something like:
IQueryable<Entity> entityQuery = _context.Entities.Where(x => ...);
IQueryable<int> idQuery = _context.AccessRights.Where(x => ...).Select(x => x.Id);
entityQuery = entityQuery.Where(x => idQuery.Contains(x.Id));
This will get the result you're looking for without loading the ids into memory. But it may convert it into a giant SQL IN statement, which wouldn't be ideal.
Or you can join to AccessRights directly:
IQueryable<Entity> entityQuery = from e in _context.Entities.Where(x => ...)
join ar in _context.AccessRights.Where(x => ...)
on e.Id equals ar.Id
select e;

Related

How do I get the most recent entry by condition in EF Core?

I have a table with the following structure (and sample data):
Identifier
UseDate
PartId
a123
05/01/2000
237
a123
05/01/2000
4656
a123
01/01/2000
2134
a124
04/01/2000
5234
a124
01/01/2000
2890
I need to get the most recent entry of every (non-unique) identifier, but at most one per identifier.
The SQL-Query (MariaDB) that seems to fulfill my problem is the following:
SELECT a.Identifier, a.MaxDate, b.PartId, b.UseDate
FROM
(SELECT Identifier, MAX(UseDate) AS MaxDate FROM MyTable GROUP BY Identifier) a
LEFT JOIN MyTable b ON a.Identifier = b.Identifier
WHERE a.MaxDate = b.UseDate GROUP BY a.Identifier;
However I need this to work with C# and EF Core (Pomelo.EntitiFrameworkCore.MySql 5.0.3), my attempts have been:
var q1 = db.MyTable
.GroupBy(t => t.Identifier)
.Select(t => new { Identifier = t.Key, MaxDate = t.Max(x => x.UseDate) });
return new ObjectResult(db.MyTable
.Join(
q1,
t1 => t1.Identifier,
t2 => t2.Identifier,
(t1, t2) => new { Identifier = t2.Identifier, PartId = t1.PartId, MaxDate = t1.MaxDate, UseDate = t1.UseDate })
.Where(t => t.UseDate == q1.First(x => x.Identifier == t.Identifier).MaxDate)
.GroupBy(t => t.Identifier)
.ToList()
);
and
return new ObjectResult(db.MyTable
.GroupBy(t => t.Identifier)
.Select(t => t.OrderByDescending(x => x.UseDate).FirstOrDefault())
.ToList()
);
The first one throws this error:
System.InvalidOperationException: "Unable to translate the given 'GroupBy' pattern. Call 'AsEnumerable' before 'GroupBy' to evaluate it client-side."
The second one essentially yields the same, just complaining about the LINQ expression instead of the GroupBy.
I want to avoid using raw SQL, but how do I correctly (and hopefully efficiently) implement this?
There are many ways to write such query in LINQ, with most of them being able to be translated by EF Core 5/6+.
The straightforward approach once you have defined a subquery for the necessary grouping and aggregates is to join it to the data table, but not with join operator - instead, use row limiting correlated subquery (SelectMany with Where and Take), e.g.
var query = db.MyTable
.GroupBy(t => t.Identifier)
.Select(t => new { Identifier = t.Key, MaxDate = t.Max(x => x.UseDate) })
.SelectMany(g => db.MyTable
.Where(t => t.Identifier == g.Identifier && t.UseDate == g.MaxDate)
.Take(1));
If the ordering field is unique per each other key value (i.e. in your case if UseDate is unique per each unique Identifier value), you can use directly Join operator (since lo limiting is needed), e.g.
var query = db.MyTable
.GroupBy(t => t.Identifier)
.Select(t => new { Identifier = t.Key, MaxDate = t.Max(x => x.UseDate) });
.Join(db.MyTable,
g => new { g.Identifier, UseDate = g.MaxDate },
t => new { t.Identifier, t.UseDate },
(g, t) => t);
or directly apply Max based Where condition to the data table:
var query = db.MyTable
.Where(t => t.UseDate == db.MyTable
.Where(t2 => t2.Identifier == t.Identifier)
.Max(t2 => t2.UseDate)
);
Finally, the "standard" LINQ way of getting top 1 item per group.
For EF Core 6.0+:
var query = db.MyTable
.GroupBy(t => t.Identifier)
.Select(g => g
.OrderByDescending(t => t.UseDate)
.First());
For EF Core 5.0 the grouping result set inside the query must be emulated:
var query = db.MyTable
.GroupBy(t => t.Identifier)
.Select(g => db.MyTable
.Where(t => t.Identifier == g.Key)
.OrderByDescending(t => t.UseDate)
.First());

Linq: GroupBy not show result for the .Include clause

Why in the result "Titolare" is null?
If I don't use GroupBy, "Titolare" has value.
Thank you.
var ben = context.Benefici.Include("Titolare").Include("Titolare.ComuneDomicilio")
.Where(b => !b.Titolare.SD1_DAT_DECESSO.HasValue)
.OrderByDescending(b => b.SDB_DAT_INIZIO)
.GroupBy(b => b.SDB_CODDIS)
.ToList()
.Select(b => b.First())
.ToList();
It is because of GroupBy limitation - you cannot get grouped items using LINQ to Entities. It should be fixed in EF Core 6.
To get first item of the group, you have to rewrite your query. It is mimic of what will be generated by EF Core 6:
var itemsQuery = context.Benefici
.Where(b => !b.Titolare.SD1_DAT_DECESSO.HasValue);
var benQuery =
from u in itemsQuery.Select(b => new { b.SDB_CODDIS }).Distinct()
join b in itemsQuery
.Include(x => x.Titolare.ComuneDomicilio)
.Where(x => x.SDB_CODDIS == u.SDB_CODDIS)
.OrderByDescending(x => x.SDB_DAT_INIZIO)
.Take(1)
select b;
var ben = benQuery.ToList();

Using Linq to query Entity Framework with Where clause and many-to-many relation

I have two tables People and Ordersand a many-to-many relationship between the two using PeopleOrders.
Each order is associated with two people: Client and Salesman.
I have the following query:
var query = db.People
.Where(u => u.Description.Equals("Client"))
.Select(u => new {u.Id, OrderId = u.Orders.Select(p => p.Id))
})
.ToList();
This returns a json like this:
[{"Id":1,"OrderId":[2]},{"Id":9,"OrderId":[10,11,12,13]},{"Id":14,"OrderId":[14,15]}]
The ClientID and an array of orders.
I need to invert. Orders can't be an array.
So I need OrderID associated with the ClientID. Something like this:
[{"OrderId":2,"Id":1},{"OrderId":10,"Id":9},{"OrderId":11,"Id":9},{"OrderId":12,"Id":9},{"OrderId":13,"Id":9}]
The query would be something like:
var query = db.Orders
But I need to subquery the People table, so it return only Client; otherwise, it will return a array of People like:
{"OrderId":2,"Id":[1,10]}
Thank you in advance.
Use SelectMany:
var query = db.People
.Where(u => u.Description.Equals("Client"))
.SelectMany(u => u.Orders.Select(p => new {u.Id, p.OrderId}))
.ToList();
You could try something like this (using SelectMany, in order you flatten the projection of your data):
var query = db.People
.Where(person => person.Description.Equals("Client"))
.Select(person => new
{
PersonOrders = person.Orders
.Select(order => new
{
PersonId = person.Id,
OrderId = order.Id))
})
})
.SelectMany(x=>x.PersonOrders)
.ToList();

Left join with group by linq lambda expression c#?

I want to write left join using linq lambda expression. I have tried query using join but now I want to create using left join so any one can help me how can do.
Here this is my query:
var UserList = db.UserInfo
.Join(db.Course, u => u.id, c => c.userid, (u, c) =>
new { u, c }).GroupBy(r => r.u.id)
.Select(g => g.OrderByDescending(r => r.c.datetime)
.FirstOrDefault()).OrderByDescending(a => a.u.datetime).ToList();
Using this query, I don't want user data those who are not in course table, so I want to this data also in course table in userid in or not.
you can use
var qry = Foo.GroupJoin(
Bar,
foo => foo.Foo_Id,
bar => bar.Foo_Id,
(x,y) => new { Foo = x, Bars = y })
.SelectMany(
x => x.Bars.DefaultIfEmpty(),
(x,y) => new { Foo=x.Foo, Bar=y});
ref: How do you perform a left outer join using linq extension methods

EF include with where clause

I've Resource and ResourceDetail.
MemberPoint with memberId and ResourceId.
I would like to get Resources Details for a member.
In SQL,
Select d.* From ResourceDetails d Inner Join
Resource on r d.ResourceId = r.Id Inner Join
MemberPoint mp on r.id = mp.ResourceId
where mp.memberId = 1
In EF,
var query = _context.ResourceDetails
.Include(d => d.Resource)
.Include(r => r.Resource.Memberpoints)
.Where(e => e.Resource.Memberpoints.Where(m => m.MemberId))
I got error when I write above EF query.
Error: unknown method 'Where(?)'of System.Linq.IQueryable
You can try using include this way:
var query = _context.MemberPoint.Include("Resource.ResourceDetails")
.Where(m => m.MemberId == 111111);
Or try joining on resourceId and selecting an anonymous type with the data you need:
var query = (from m in _context.MemberPoint
join rd in _context.ResourceDetails on m.ResourceId equals rd.ResourceId
where m.MemberId == 11111
select new
{
Member = m,
ResourceDetail = rd
})
You are using EF completely incorrectly.
What you want is actually
If ResourceDetails has one Resource and each reasource has one member (unlikely).
var query = _context.ResourceDetails
.Include(d => d.Resource)
.Include(r => r.Resource.Memberpoints)
.Where(d => d.Resource.Memberpoints.MemberId == 1);
If ResourceDetails has one Resource and each resource can have multiple Members.
var query = _context.ResourceDetails
.Include(d => d.Resource)
.Include(r => r.Resource.Memberpoints)
.Where(d => d.Resource.Memberpoints.Any(m => m.MemberId == 1));
If ResourceDetails has multiple Resources (unlikely) and each resource can have multiple Members.
var query = _context.ResourceDetails
.Include(d => d.Resource)
.Include(r => r.Resource.Memberpoints)
.Where(d => d.Resource.Any(r => r.Memberpoints.Any(m => m.MemberId == 1)));
Okay. So what about the join you wanted? Well that is the job of the ORM. The ORM mapping already knows how ResourceDetails are linked to Members.
So what was that error you got?
Well, the sig of IQueryable.Where() takes a Func<T, bool> and returns an IQueryable<T>.
So in your example, the inner Where is wrong because you are giving it a Func<T, int>. The outter Where is wrong because you are passing a IQueryable<T> to it (although the compiler doesn't know that because its all sorts of wrong already).
TL:DR
In general, don't join with EntityFramework/Linq. EF should have the associations in the mappings and already knows how to join entities together.
Assuming MemberId is unique as per your query example. Try this
var query = _context.ResourceDetails
.Include(d => d.Resource)
.Include(r => r.Resource.Memberpoints)
.Where(e => e.ResourceId == e.Resource.Memberpoints.Where(m => m.MemberId == 1))

Categories

Resources