Why do the 2 LINQ queries get evaluated differently - c#

Query 1
var resulty = db.QIS
.Where(w=>w.QSA.Any(a => a.QSID != w.QSID))
.Select(s => s.QSID).ToList();
Query 2
var resultz = db.QIS
.Where(w=>w.QSA.Where(h => h.QSID == w.QSID).Select(s => s.QSID).FirstOrDefault() != w.QSID)
.Select(s => s.QSID).ToList();
Table QIS and QSA are related Many:Many. The idea here is to find how many QIS.ID's are not Found in QSA where QIS.QID == QSA.QID.
Query 1 returns 0
Query 2 on the other hand gives me what I expected to see ( the list off all non matching QIS records.)
Why will the any not work? - i found myself running into the same situation a couple of times now in seperate scenarios... thanks for any help / thoughts.
PS: Prefer method syntax.

In the filtering in your second version, will only be true if the inner Where clause returns no elements, so that FirstOrDefault() returns null or 0 (depending on if the type is nullable or not).
w=>w.QSA.Where(h => h.QSID == w.QSID)
.Select(s => s.QSID).FirstOrDefault() != w.QSID
Which is equivalent to (now assuming QSID is a non nullable numeric type, if it is nullable, use null instead of zero):
w=>w.QSA.Where(h => h.QSID == w.QSID)
.Select(s => s.QSID).FirstOrDefault() == 0
which can be rewritten to:
w=>w.QSA.Where(h => h.QSID == w.QSID).FirstOrDefault() == null
which can be rewritten to:
w=>!w.QSA.Any(h => h.QSID == w.QSID)
which is nearly the same as your initial version, but not exactly. You still want to check for equivalence inside the Any() filter, but then negate the result.

Related

The LINQ expression 'Expression' could not be translated. Either rewrite the query in a form that can be translated

I have looked at a lot of similar questions but none could give me a solution so I am thinking if anyone can help me with this problem. I have a hierarchy of entities as Clients have multiple ClientRateDeals and then I am trying to fetch only those clients that have a list of client rate deals that all pass some condition. Here's my LINQ query that generating an error :
var query = _context.Client.Where(c=>c.Disabled==false)
.GroupJoin(_context.ClientRateDeal.Where(crd=>crd.Disabled==false),
c => c.Id,
crd => crd.ClientId,
(c, crd) => new
{
c,
crd = crd.Where(cr => cr.DateEnd == null || cr.DateEnd > DateTime.Today)
})
.Where(res => res.crd.Count() == 0)
.Select(cl => cl.c).AsNoTracking().ToList();
as you can see in the result selector argument I have kept that condition and then a where clause on the result selector to fetch only those whose client rate deal whose count is 0. However due to some reason I am getting the exception that the LINQ cant be translated. Can anyone help me with this ?
For unknown reason (it has nothing in similar with GroupBy), LINQ GroupJoin operator is not supported in EF Core 3.x, 5.x.
You have to use one of the available alternatives - (1) collection navigation property (preferred) or (2) correlated subquery.
e.g.
(1) In Client class define
public ICollection<ClientRateDeal> ClientRateDeals { get; set; }
and use it inside the query
var query = _context.Client
.Where(c => c.Disabled==false)
// vvv
.Where(c => !c.ClientRateDeals.Any(
crd => crd.Disabled == false &&
(crd.DateEnd == null || crd.DateEnd > DateTime.Today)))
.AsNoTracking().ToList();
or (2)
var query = _context.Client
.Where(c => c.Disabled==false)
// vvv
.Where(c => !_context.ClientRateDeal.Any(crd =>
c.Id == crd.ClientId &&
crd.Disabled == false &&
cr.DateEnd == null || cr.DateEnd > DateTime.Today))
.AsNoTracking().ToList();
In general, instead of
db.As.GroupJoin(db.Bs, a => a.Id, b => b.AId, (a, Bs) => new { a, Bs })
use
db.As.Select(a => new { a, Bs = db.Bs.Where(b => a.Id == b.AId) })
Related github issue (please go vote in order to have a chance to get that implemented):
Query with GroupBy or GroupJoin throws exception #17068
Query: Support GroupJoin when it is final query operator #19930
even though the second is not exactly what we need (we want just GroupJoin to be translated as it was written in correlated subquery syntax shown above).

The binary operator Greater Than is not defined for the types 'System.Collections.Generic.IEnumerable'

The following code throws the The binary operator Greater Than is not defined for the types 'System.Collections.Generic.IEnumerable' error, although s.Assessments is null from the previous code. But, then I do not understand why the binary operator is reacheable even though I control with s.Assessments == null. Also, why does it produce System.Collections.Generic.IEnumerable to compare with? Any ideas?
baseQuery = baseQuery.Where(s => s.Assessments == null ? false : s.Assessments
.SelectMany(a => a.AssessmentItems)
.Where(ai => ai.RubricItemId == rubricItemId)
.DefaultIfEmpty()
.Average(d => d == null ? 0 : d.CurrentScore) > averageScore.GetValueOrDefault());
When boiled down to SQL, Assessments will not be null, it will be empty. With Linq2EF it is generally a good idea to move your conditional/defaults outside of the expression, then let EF work with values. The risk with having functions and such inside expressions is that EF may not be able to resolve them and raise errors or result in a query being executed to objects early.
decimal average = averageScore.GetValueOrDefault();
baseQuery = baseQuery.Where(s => s.Assessments
.SelectMany(a => a.AssessmentItems)
.Where(ai => ai.RubricItemId == rubricItemId)
.Average(d => d.CurrentScore) > average);
You shouldn't need all the DefaultIfEmpty and null checks. EF will work through and handle the situations where records don't exist. If the query has no Assessments, that element will not be returned or factored into the average score check.

Ef core: Sequence contains no element when doing MaxAsync

I'm using ef core in my asp core API project.
I have to find the highest order index.
Example:
Data table: Id, ForeignId, OrderIndex
So I'm doing:
var highestOrderIndex = await _context
.ExampleDbSet
.Where(x =>
x.ForeignId == foreignId)
.MaxAsync(x =>
x.OrderIndex);
The problem is when the example db set is containing 0 elements. This will throw an exception: Sequence contains no element.
Is there an elegant way to do this? Because I don't want to get all the elements from the database. And it should be async.
Thanks
Actually there is quite elegant (and more performant compared to the suggested in the other answer because it's executing just a single database query) way by utilizing the fact that aggregate methods like Min, Max throw Sequence contains no element exception only when used with non nullable overloads, but nullable overloads simply return null instead.
So all you need is to promote the non nullable property type to the corresponding nullable type. For instance, if the OrderIndex type is int, the only change to your query could be
.MaxAsync(x => (int?)x.OrderIndex);
Note that this will also change the type of the receiving variable highestOrderIndex to int?. You can check for null and react accordingly, or you can simply combine the aggregate function call with ?? operator and provide some default value, for instance
var highestOrderIndex = (await _context.ExampleDbSet
.Where(x => x.ForeignId == foreignId)
.MaxAsync(x => (int?)x.OrderIndex)) ?? -1; // or whatever "magic" number works for you
Doing an AnyAsync and then a MaxAsync will result in two separate database calls. You can condense it into one by making sure the sequence contains a "default" minimum value. This is a useful trick anywhere you use the Linq Max/Min methods, not just in database code:
context.ExampleDbSet
.Where(w => w.ForeignId == foreignId)
.Select(s => s.OrderIndex)
.Concat(new[] { 0 })
.MaxAsync();
Another way with a bit better perfomance than the MaxAsync, if the default value is the one you want to get, if there are no results:
var highestOrderIndex = await _context.ExampleDbSet
.Where(x => x.ForeignId == foreignId)
.OrderByDescending(x => x.OrderIndex)
.Select(x => x.OrderIndex)
.FirstOrDefaultAsync();
TOP is faster than the aggregate functions, look at the execution plan in your SQL Server.
You can find if any records exist and if they do, then to find the max. Something like this:
var query = _context.ExampleDbSet
.Where(x => x.ForeignId == foreignId);
var itemsExist = await query.AnyAsync();
int maxOrderIndex = 0;
if(itemsExist)
{
maxOrderIndex = await query.MaxAsync(x => x.OrderIndex);
}
Here you won't have to retrieve all of the items from the database, only check if a record exists which is much much faster and you can also keep the method async.
You can use DefaultIfEmpty and Select before MaxAsync.
var highestOrderIndex = await _context
.ExampleDbSet
.Where(x =>
x.ForeignId == foreignId)
.Select(x => x.OrderIndex)
.DefaultIfEmpty() // default is 0 if OrderIndex is int or long
// .DefaultIfEmpty(-1) // default is -1
.MaxAsync();

Select nested property values in LINQ

I am fairly new to Linq and I am starting to try and filter lists and IEnumerable content without using foreach loops. In this case I want to retreive a nested property if it meets a given criteria.
Here is what I have so far:
IEnumerable<GroupTourData> test =
web_service_item.TourInstances
.Where(x => x.Availability.Where(a => a.Price > 0 && a.Grade == string.Empty)
.Count() > 0);
What I want to achieve here is an IEnumerable list of all of the prices in the matching criteria. I though this would be obtainable by adding .SelectMany(a.Price) to the end of this statement but that seems to error out in Visual Studio.
Any help or pointers would be greatly appreciated.
ok seems as this solves the issue:
if you just want the prices? then you just had your SelectMany in the wrong position:
web_service_item
.TourInstances
.SelectMany(ti => ti.Availability)
.Where(a => a.Price > 0 && a.Grade == string.Empty)
.Select(a => a.Price);
Depending on your data schema and tables... Have you tried including your sub-entities using Include?
IEnumerable<GroupTourData> test = web_service_item
.TourInstances
.Include("Availability")
.Where(x => x
.Availability
.Any(a => a.Price > 0 && a.Grade == string.Empty)
);

Fluent nHibernate Query => QueryOver, Join, Distinct

I try to change that query to QueryOver<> to be able to do the Distinct operation yet inside the (generated sql) query
var result = (from x in Session.Query<Events>()
join o in Session.Query<Receivers>() on x.ID equals o.ID
where x.Owner.ID == 1 //the user is the owner of that Event (not null)
||
x.EVType.ID == 123 //(not null)
||
x.Receivers.Count(y => y.User.ID == 1) > 0 //the user is one of the Event Receivers
select x.StartDate)
.Distinct();
I tried something like that
Events x = null;
List<Receivers> t = null;
var result = Session.QueryOver<Events>(() => x)
.JoinAlias(() => x.Receivers, () => t)
.Where(() => x.Owner.ID == 1
||
x.EVType.ID == 123
||
t.Count(y => y.User.ID == 1) > 0)
.TransformUsing(Transformers.DistinctRootEntity)
.Select(a => a.StartDate)
.List();
but then I got the Value can not be null. Parameter name: source exception. Any ideas how can I fix that query ?
edit
thanks to the xanatos' answer, the final SQL query is correct (I used his 2nd approach):
SELECT distinct this_.StartDate as y0_
FROM Events this_
WHERE
(
this_.UserID = ?
or
this_.EventTypeID = ?
or
exists (SELECT this_0_.ID as y0_
FROM Receivers this_0_
WHERE this_0_.UserID = ?)
)
"In QueryOver, aliases are assigned using an empty variable. The variable can be declared anywhere (but should be empty/default at runtime). The compiler can then check the syntax against the variable is used correctly, but at runtime the variable is not evaluated (it's just used as a placeholder for the alias)." http://nhibernate.info/blog/2009/12/17/queryover-in-nh-3-0.html
Setting List<Receivers> t to empty collection as you did (as you have mentioned in comments) means that you check is event id in local empty collection - doesn't have sense at all.
You can try do your query with subquery (should work but i'm not sure, I wrote it without testing, "by hand"):
Receivers receiversSubQueryAlias = null;
var subquery = session.QueryOver<Events>()
.JoinQueryOver<Receivers>(x => x.Receivers, () => receiversSubqueryAlias, JoinType.Inner)
.Where(()=> receiversSubQueryAlias.UserId == 1)
.Select(x => x.Id)
.TransformUsing(Transformers.DistinctRootEntity);
Events eventsAlias = null;
var mainQueryResults = session.QueryOver<Events>(() => eventsAilas)
.Where(Restrictions.Disjunction()
.Add(() => eventAlias.OwnerId == 1)
.Add(() => eventAlias.EVType.Id == 123)
.Add(Subqueries.WhereProperty<Events>(() => eventAlias.Id).In(subquery))
).Select(x => x.StartDate)
.TransformUsing(Transformers.DistinctRootEntity)
.List();
As written by #fex, you can't simply do a new List<Receivers>. The problem is that you can't mix QueryOver with "LINQ" (the t.Count(...) part). The QueryOver "parser" tries to execute "locally" the t.Count(...) instead of executing it in SQL.
As written by someone else, TransformUsing(Transformers.DistinctRootEntity) is client-side. If you want to do a DISTINCT server-side you have to use Projections.Distinct .
You have to make an explicit subquery. Here there are two variants of the query. the first one is more similar to the LINQ query, the second one doesn't use the Count but uses the Exist (in LINQ you could have done the same by changing the Count(...) > 0 with a Any(...)
Note that when you use a .Select() you normally have to explicitly tell the NHibernate the type of the .List<something>()
Events x = null;
Receivers t = null;
// Similar to LINQ, with COUNT
var subquery2 = QueryOver.Of<Receivers>(() => t)
.Where(() => t.SOMETHING == x.SOMETHING) // The JOIN clause between Receivers and Events
.ToRowCountQuery();
var result2 = Session.QueryOver<Events>(() => x)
.Where(Restrictions.Disjunction()
.Add(() => x.Owner.ID == 1)
.Add(() => x.EVType.ID == 123)
.Add(Subqueries.WhereValue(0).Lt(subquery2))
)
.Select(Projections.Distinct(Projections.Property(() => x.StartDate)))
.List<DateTime>();
// With EXIST
var subquery = QueryOver.Of<Receivers>(() => t)
.Where(() => t.SOMETHING == x.SOMETHING) // The JOIN clause between Receivers and Events
.Select(t1 => t1.ID);
var result = Session.QueryOver<Events>(() => x)
.Where(Restrictions.Disjunction()
.Add(() => x.Owner.ID == 1)
.Add(() => x.EVType.ID == 123)
.Add(Subqueries.WhereExists(subquery))
)
.Select(Projections.Distinct(Projections.Property(() => x.StartDate)))
.List<DateTime>();
Note that you'll have to set "manually" the JOIN condition in the subquery.
Hopefully this answer can help others. This error was being caused by declaring
List<Receivers> t = null;
followed by the query expression
t.Count(y => y.User.ID == 1) > 0
The QueryOver documentation states "The variable can be declared anywhere (but should be empty/default at runtime)." Since in this case, the place holder is a List, you must initialize it as an empty list.
List<Receivers> t = new List<Receivers>();
Otherwise, when you try to reference the Count method, or any other method on the placeholder object, the source (t) will be null.
This however still leaves a problem as #fex and #xanatos, in which it makes no sense to reference Count() from the alias List t, as it won't convert into SQL. Instead you should be creating a subquery. See their answers for more comprehensive answer.

Categories

Resources