EF include with where clause - c#

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

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());

Suggestions on why this linq ef core query wont execute

Good morning,
I have the following query in EF Core which works as required if I do not add into the the where clause a condition for the createdtimestamp. If I add other conditions clauses to filter down the data as shown below the query will execute without issue.
var q = (
from trckhead in DbContext.TrackingBatchHeader
from userdets in DbContext.UserDetails
.Where(u => u.UserId == trckhead.ClosedByUserId).DefaultIfEmpty()
from trckkeys in DbContext.TrackingBatchesItemKey
.Where(t => t.TrackingNo == trckhead.TrackingNo).DefaultIfEmpty()
from trcklink1 in DbContext.TrackingBatchesLink
.Where(x => x.TrackingNo == trckhead.TrackingNo && x.TrackingType == "I").DefaultIfEmpty()
from trcklink2 in DbContext.TrackingBatchesLink
.Where(x => x.TrackingNo == trckhead.TrackingNo && x.TrackingType == "T").DefaultIfEmpty()
join trckref in DbContext.TrackingBatchesReference on trckhead.TrackingBatchType equals trckref
.TrackingBatchType
join mthsite in DbContext.SiteDetail on trckhead.SiteCode equals mthsite.SiteCode
join userdets2 in DbContext.UserDetails on trckhead.CreatedByUserId equals userdets2.UserId
where
(string.IsNullOrEmpty(searchRequest.Status) || trckhead.Status == searchRequest.Status) &&
trckhead.CreatedTimestamp >= DateTime.Now.AddMonths(-11)
select new TrackingBatchSearchResultDto
{
TrackingBatchId = trckhead.TrackingNo,
SiteCode = trckhead.SiteCode,
TrackingBatchType = trckhead.TrackingBatchType,
Status = trckhead.Status,
Created = trckhead.CreatedTimestamp,
CreatedById = trckhead.CreatedByUserId,
ClosedById = trckhead.ClosedByUserId,
UserDescription = trckhead.UsersDescription,
TrackingBatchDescription = trckref.TrackingBatchTypeDescription,
SiteName = mthsite.Title,
CreatedByName = userdets2.FullUserName ?? string.Empty,
ClosedByName = userdets.FullUserName ?? string.Empty,
Link1 = trcklink1.TrackingData ?? string.Empty,
Link2 = trcklink2.TrackingData ?? string.Empty
}).Distinct();
return await q.ToListAsync();
If I keep the date clause in the query the exception that is raised shows the following. I would have thought the server evaluation wouldnt be happy if I was passing a function into the query but checking against a simple date has thrown me. I could filter the results by date after obtaining the whole data set but the number of records returned can be massive so I would rather filter it down from a database query. Any help would be appreciated.
The LINQ expression 'DbSet<TrackingBatchHeader>
.SelectMany(
source: t => DbSet<UserDetail>
.Where(u => u.UserId == t.ClosedByUserId)
.DefaultIfEmpty(),
collectionSelector: (t, c) => new TransparentIdentifier<TrackingBatchHeader, UserDetail>(
Outer = t,
Inner = c
))
.SelectMany(
source: ti => DbSet<TrackingBatchesItemKey>
.Where(t0 => t0.TrackingNo == ti.Outer.TrackingNo)
.DefaultIfEmpty(),
collectionSelector: (ti, c) => new TransparentIdentifier<TransparentIdentifier<TrackingBatchHeader, UserDetail>, TrackingBatchesItemKey>(
Outer = ti,
Inner = c
))
.SelectMany(
source: ti0 => DbSet<TrackingBatchesLink>
.Where(t1 => t1.TrackingNo == ti0.Outer.Outer.TrackingNo && t1.TrackingType == "I")
.DefaultIfEmpty(),
collectionSelector: (ti0, c) => new TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TrackingBatchHeader, UserDetail>, TrackingBatchesItemKey>, TrackingBatchesLink>(
Outer = ti0,
Inner = c
))
.SelectMany(
source: ti1 => DbSet<TrackingBatchesLink>
.Where(t2 => t2.TrackingNo == ti1.Outer.Outer.Outer.TrackingNo && t2.TrackingType == "T")
.DefaultIfEmpty(),
collectionSelector: (ti1, c) => new TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TrackingBatchHeader, UserDetail>, TrackingBatchesItemKey>, TrackingBatchesLink>, TrackingBatchesLink>(
Outer = ti1,
Inner = c
))
.Join(
outer: DbSet<TrackingBatchesReference>,
inner: ti2 => ti2.Outer.Outer.Outer.Outer.TrackingBatchType,
outerKeySelector: t3 => t3.TrackingBatchType,
innerKeySelector: (ti2, t3) => new TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TrackingBatchHeader, UserDetail>, TrackingBatchesItemKey>, TrackingBatchesLink>, TrackingBatchesLink>, TrackingBatchesReference>(
Outer = ti2,
Inner = t3
))
.Join(
outer: DbSet<SiteDetail>,
inner: ti3 => ti3.Outer.Outer.Outer.Outer.Outer.SiteCode,
outerKeySelector: s => s.SiteCode,
innerKeySelector: (ti3, s) => new TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TrackingBatchHeader, UserDetail>, TrackingBatchesItemKey>, TrackingBatchesLink>, TrackingBatchesLink>, TrackingBatchesReference>, SiteDetail>(
Outer = ti3,
Inner = s
))
.Join(
outer: DbSet<UserDetail>,
inner: ti4 => ti4.Outer.Outer.Outer.Outer.Outer.Outer.CreatedByUserId,
outerKeySelector: u0 => u0.UserId,
innerKeySelector: (ti4, u0) => new TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TransparentIdentifier<TrackingBatchHeader, UserDetail>, TrackingBatchesItemKey>, TrackingBatchesLink>, TrackingBatchesLink>, TrackingBatchesReference>, SiteDetail>, UserDetail>(
Outer = ti4,
Inner = u0
))
.Where(ti5 => ti5.Outer.Outer.Outer.Outer.Outer.Outer.Outer.CreatedTimestamp >= (Nullable<DateTime>)DateTime.Now.AddMonths(-11))' 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 either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
Plainly speaking, neglecting either establish navigation properties for the relationships between entities and instead using manual outer joins using "from" plus then adding the ultimate insult to poorly formed queries with Distinct is trying to use EF like a hammer to pound a square peg through a round hole.
With proper relationships, that query should be trivial.
Your datetime condition error message does not match your query.
** Edit: Removed details on Date comparison - This does not appear to be directly related as I did verify that the Nullable Date/DateTime comparisons do work with .AddMonth() in EF Core.
Expanding out conditional logic can help simplify the queries being executed.
For example:
var query = (/*build your base query*/);
if (!string.IsNullOrEmpty(searchRequest.Status))
query = query.Where(x => x.Status == searchRequest.Status);
if (searchRequest.DateFrom.HasValue)
{
var fromDate = searchRequest.DateFrom.Value.AddMonths(-11);
query = query.Where(x => x.CreatedTimestamp >= fromDate);
}
var results = query.Select(...).Distinct().ToList();
EF can perform some magic to build queries provided you give it enough information to do it properly. Seeing it build something like ti5.Outer.Outer.Outer.Outer.Outer.Outer.Outer.CreatedTimestamp should be sending off alarm bells. There may be something deep in your query lurking that is tripping a Client-side evaluation, or it is simply a case that a combination of joins and such has tipped over a complexity limit or exposed a bug in EF Core. The starting point would be to look to simplify that query expression by leveraging navigation properties rather than joins, and externalizing the conditional logic where you can.
If you have no recourse but to try and query across relationships that you are not prepared to map out properly with navigation properties, I would recommend exploring building your query in SQL as a Stored Procedure and then defining an Entity that can map to the resulting record from that Sproc.

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 conditional statements in linq query c#

I have 2 queries. First gets the full name of a committee head if the committee has more that 1 member.
var result = await db.ExpertCommittees
.Where(f => f.Id == committeeId)
.Where(f => f.ExpertCommitteeMembers.Count > 1)
.Select(f => f.ExpertCommitteeMembers
.Where(m => m.IsCommitteeHead)
.FirstOrDefault().Expert.FullName)
.FirstOrDefaultAsync();
The second one gets the full name of the only committee member if the committee has only 1 member
var result2 = await db.ExpertCommittees
.Where(f => f.Id == committeeId)
.Where(f => f.ExpertCommitteeMembers.Count == 1)
.Select(f => f.ExpertCommitteeMembers
.FirstOrDefault().Expert.FullName)
.FirstOrDefaultAsync();
Is it possible to check how many members does the committee have and then return the correct name all in the same query? Or do I first have to check how many members does the committee have and then run the appropriate query seperatly?
If I understand correctly you can try to let condition into inner linqwhere
var result = await db.ExpertCommittees
.Where(f => f.Id == committeeId)
.Select(f => f.ExpertCommitteeMembers.Where(m =>
(m.IsCommitteeHead &&
f.ExpertCommitteeMembers.Count > 1)||f.ExpertCommitteeMembers.Count == 1).FirstOrDefault().Expert.FullName)
.FirstOrDefaultAsync();
you can Create a create anonymous object in linq query select. which will contain FullName and count.
var result = await db.ExpertCommittees
.Where(f => f.Id == committeeId)
.Where(f => f.ExpertCommitteeMembers.Count > 1)
.Select(f => new
{
FullName = f.ExpertCommitteeMembers
.Where(m => m.IsCommitteeHead)
.FirstOrDefault().Expert.FullName,
Count = f.ExpertCommitteeMembers.Count
})
.FirstOrDefaultAsync();
A generated Join, as it is generated from ExpertCommittees to
ExpertCommitteeMembers, with a Navigation-Collection, will always do a Left Join,
what you want is an Inner Join. It will give you only items with entities in both tables.
This will be something like
db.ExpertCommittees.Join(db.ExpertCommittemember, x=>someid, y=>somid,
(comittee, member) => new { comittee, member});
But this will give you one line per member.... with the possibility to filter by "IsComitteeHead" or whatever.
You can append Grouping, or use a GroupJoin directly, to have a List of "Commitees", each with a List of it's members ...and only if there are members.
Without knowing the data structure, ID's, Foreign-Keys and table name, we cannot produce a valid query.

How can i get List along with child list where child list having some condition in NHibernate

I had written a Query in NHibernate as below:
var queryResult = CurrentSession.QueryOver()
.Where(r => r.StatusId == 1)
.JoinQueryOver(a => a.ActorList)
.Where(s=>s.IsActor==1)
.List()
.Distinct()
.ToList();
I am trying to retrieve only Where(s=>s.IsActor==1), But It Is Getting Records
Where(s=>s.IsActor==0) also...
How can I get only IsActor==1 records?
Thanks in Advance
You need to specify a predicate in the join, so that it is applied to the join not the top where:
(will look something like ...LEFT JOIN actor on actor.Id = p.ActorId AND IsActor = 1)
Actor actorAlias = null;
var queryResult = CurrentSession.QueryOver()
.Where(r => r.StatusId == 1)
.Left.JoinQueryOver(r => r.ActorList, () => actorAlias, a => a.IsActor==1)
.List()
.Distinct()
.ToList();

Categories

Resources