How do you select multiple aggregates and a non aggregate in LINQ - c#

I have this TSQL statement which seems simple enough
select qGroup, AVG(score), COUNT(score)
from [Scores]
where [year] = 2014 and charIndex('s', qGroup, 0) <> 1
group by qGroup
However, I just cannot figure out how to express this in LINQ (dot notation)
Here is my failed stab at it
List<qGroupModel> query = context.Scores.Where(p => (p.schoolID == schoolID) && (p.Year == year) && !(p.qGroup.StartsWith("S"))).Select(p => new { p.Average(p2 => p2.Score), p.qGroup }).GroupBy(p => p.qGroup).ToList<qGroupModel>();
The error I get from the above is as follows
'Models.Score' does not contain a definition for 'Average' and no
extension method 'Average' accepting a first argument of type
'Models.Score' could be found (are you missing a using directive or an
assembly reference?)
My LINQ acumen is not so great.. but still.. this seems harder than it should.
Please help

You can try this way :
List<qGroupModel> query =
context.Scores
.Where(p => (p.schoolID == schoolID) && (p.Year == year) && !(p.qGroup.StartsWith("S")))
.GroupBy(p => p.qGroup)
.Select(p => new qGroupModel { p.Average(p2 => p2.Score), p.qGroup })
.ToList();

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

How can I add a .Where into a .Select into an object using LINQ?

I have a LINQ Query that I am using. What I want to do is to order the data in Start and then select data for either the AndroidSpeeds or IOSSpeeds depending on if the row retrieved has IsAndroid or IsIos set to true.
Here is the query that I have so far:
List<LogStart> Start1 = Start
.Where(x => x.IsPhysical == true)
.OrderBy(g => g.DateYYMMDD)
.Select(g => new Start2
{
AndroidDBSpeed = (int?)g.Where(gx => gx.IsAndroid).Select(gx => (int?)gx.DBSpeed).DefaultIfEmpty(),
AndroidCPUSpeed = (int?)g.Where(gx => gx.IsAndroid).Select(gx => (int?)gx.CPUSpeed).DefaultIfEmpty(),
IOSDBSpeed = (int?)g.Where(gx => gx.IsIos).Select(gx => (int?)gx.DBSpeed).DefaultIfEmpty(),
IOSCPUSpeed = (int?)g.Where(gx => gx.IsIos).Select(gx => (int?)gx.CPUSpeed).DefaultIfEmpty(),
})
.ToList();
However, there is a problem. It's pointing to. Where and saying that
Program.cs(46,46): Error CS1061: 'LogStart' does not contain a
definition for 'Where' and no accessible extension method 'Where'
accepting a first argument of type 'LogStart' could be found (are you
missing a using directive or an assembly reference?) (CS1061) (Cosmos)
Does anyone have any ideas what might be wrong and how it could be fixed?
For reference, the following statement in the same file works okay:
List<LogStart> Start2 = Start
.Where(x => x.IsPhysical == true)
.GroupBy(x => x.DateYYMMDD)
.OrderBy(g => g.Key)
.Select(g => new Start2
{
DateYYMMDD = g.Key,
Devices = g.Count(),
AndroidDBSpeed = (int?) g.Where(gx => gx.IsAndroid).Select(gx => (int?) gx.DBSpeed).DefaultIfEmpty().Average(),
AndroidCPUSpeed = (int?)g.Where(gx => gx.IsAndroid).Select(gx => (int?)gx.CPUSpeed).DefaultIfEmpty().Average(),
IOSDBSpeed = (int?)g.Where(gx => gx.IsIos).Select(gx => (int?)gx.DBSpeed).DefaultIfEmpty().Average(),
IOSCPUSpeed = (int?)g.Where(gx => gx.IsIos).Select(gx => (int?)gx.CPUSpeed).DefaultIfEmpty().Average(),
})
.ToList();
The grouping is what makes the second work.
If you don't want grouping then I suspect you are looking for something like
var Start1 = Start
.Where(x => x.IsPhysical == true)
.OrderBy(g => g.DateYYMMDD)
.Select(g => new Start2
{
AndroidDBSpeed = g.IsAndroid ? (int?) g.DBSpeed : (int?) null,
AndroidCPUSpeed = g.IsAndroid ? (int?) g.CPUSpeed : (int?) null,
IOSDBSpeed = gx.IsIos ? (int?) g.DBSpeed : (int?) null,
IOSCPUSpeed = gx.IsIos ? (int?) g.CPUSpeed : (int?) null,
})
.ToList();
It is as simple as it gets. .Where is an extension method. Check your using statements - unless you are using the proper namespace (iirc System.Linq) you will not be able to get that method.
And obviously Start must be of a type supported by LINQ.
using System.Linq //Add this to the namespace, if you have not added
In C# LINQ provides the ability to filter the data using Where, Select, GroupBy etc and these are called Extension methods.
In order to use these extension methods you need to add System.Linq namespace which is part of System.Core assembly that provides access to classes that support queries that use Language-Integrated Query
Program.cs(46,46): Error CS1061: 'LogStart' does not contain a
definition for 'Where' and no accessible extension method 'Where'
accepting a first argument of type 'LogStart' could be found (are you
missing a using directive or an assembly reference?) (CS1061) (Cosmos)
EDIT: From the subsequent updates
To use the Where clause you need to ensure that the type implements IEnumerable and at this line g.Where(gx => gx.IsAndroid). g is of type LogStart and does not implement IEnumerable therefore you cannot use Where extension on it
Please note my comments that the very next c# statement in the same
file works just fine.
That works because your are doing a GroupBy on the Collection which means that (int?) g.Where(gx => gx.IsAndroid) g is of type
IGrouping<TKey,TElement> which is an IEnumerable that additionally has a key
To solve, with your original code you can try to Cast<IEnumerable> then use .Where like below
List<LogStart> Start1 = Start
.Where(x => x.IsPhysical == true)
.OrderBy(g => g.DateYYMMDD).Cast<IEnumerable<LogStart>>()
List<LogStart> Start1 = Start
.Where(x => x.IsPhysical == true)
.OrderBy(g => g.DateYYMMDD).Cast<IEnumerable<LogStart>>()
.Select(g => new Start2
{
AndroidDBSpeed = (int?)g.Where(gx => gx.IsAndroid).Select(gx => (int?)gx.DBSpeed).DefaultIfEmpty(),
AndroidCPUSpeed = (int?)g.Where(gx => gx.IsAndroid).Select(gx => (int?)gx.CPUSpeed).DefaultIfEmpty(),
IOSDBSpeed = (int?)g.Where(gx => gx.IsIos).Select(gx => (int?)gx.DBSpeed).DefaultIfEmpty(),
IOSCPUSpeed = (int?)g.Where(gx => gx.IsIos).Select(gx => (int?)gx.CPUSpeed).DefaultIfEmpty(),
})
.ToList();

LINQ to Entities does not recognize the method 'System.Linq.IQueryable in nested select

I wrote an extension method to get only approved absences out of a list of absences:
public static IQueryable<tblAbwesenheit> OnlyApprovedAbsences(this IQueryable<tblAbwesenheit> source)
{
return source.Where(a =>
(a.tblAbwesenheitsantraggenehmigungs.Any() && a.tblAbwesenheitsantraggenehmigungs.All(g => g.AbwesenheitsgenehmigungsstatusID == AbsenceStatusIds.Approved))
&& (!a.tblAbwesenheitsstornierunggenehmigungs.Any() || a.tblAbwesenheitsstornierunggenehmigungs.Any(g => g.AbwesenheitsgenehmigungsstatusID != AbsenceStatusIds.Approved)));
}
When I'm using this method with a "normal" Select, everything is fine:
context.tblAbwesenheits.OnlyApprovedAbsences().ToList()
However when I'm using it inside a Select statement, I get an error:
context.tblMitarbeiters.Select(m => new
{
Employee = m,
AbsencesForEmployee = m.tblAbwesenheits.OnlyApprovedAbsences()
})
.ToList();
LINQ to Entities does not recognize the method
'System.Linq.IQueryable1[Data.tblAbwesenheit]
OnlyApprovedAbsences(System.Linq.IQueryable1[Data.tblAbwesenheit])'
method, and this method cannot be translated into a store expression.
I have searched quite a lot, but could not find a way to teach Entity Framework to recognize my Method without expanding the query to
context.tblMitarbeiters.Select(m => new
{
Employee = m,
AbsencesForEmployee = m.tblAbwesenheits
.Where(a =>
(a.tblAbwesenheitsantraggenehmigungs.Any() && a.tblAbwesenheitsantraggenehmigungs.All(g => g.AbwesenheitsgenehmigungsstatusID == AbsenceStatusIds.Approved))
&& (!a.tblAbwesenheitsstornierunggenehmigungs.Any() || a.tblAbwesenheitsstornierunggenehmigungs.Any(g => g.AbwesenheitsgenehmigungsstatusID != AbsenceStatusIds.Approved)))
})
.ToList();
Is there a way to get EF to recognize my Method?
EF is trying to look for a SQL equivalent of your method and not finding one. It can find an equivalent of the expanded query, which is why that works.
You might be able to create an expression rather than a method
var OnlyApprovedAbsencesExpression = (a =>
(a.tblAbwesenheitsantraggenehmigungs.Any() && a.tblAbwesenheitsantraggenehmigungs.All(g => g.AbwesenheitsgenehmigungsstatusID == AbsenceStatusIds.Approved))
&& (!a.tblAbwesenheitsstornierunggenehmigungs.Any() || a.tblAbwesenheitsstornierunggenehmigungs.Any(g => g.AbwesenheitsgenehmigungsstatusID != AbsenceStatusIds.Approved)))
and then write something like
AbsencesForEmployee = m.tblAbwesenheits.Where(OnlyApprovedAbsencesExpression)

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.

Linq-To-SQL Marc Gravell's InRange Extension

I'm tring to use the InRange extension Marc Gravell wrote here:
LINQ Expression to return Property value?
This is my code so far:
// Fetch list of visit Ids and distinct Ips that fall into the date range
var q = (from c in db.tblTrackerVisits where c.Date >= MinDate select new { c.ID, c.IPID });
List<int> VisitIDs = q.Select(c => c.ID).ToList();
List<int> DistinctIPs = q.Select(c => c.IPID).Distinct().ToList();
// List of all campaigns that have visitors
var Campaigns = db.tblTrackerVariables
.Where(c =>
c.TypeID == Settings.CampaignTrackerVariableTypeID
//&& db.tblTrackerVisitVariables.Any(d=>VisitIDs.Contains(d.VisitID) && d.VariableID == c.ID)
&& db.tblTrackerVisitVariables.InRange(x => x.VisitID, 1500, VisitIDs)
)
.Select(c => new { c.ID, c.Name }).OrderBy(c=>c.Name);
However this throws:
Cannot implicitly convert type 'System.Collections.Generic.IEnumerable<tblTrackerVisitVariable>' to 'bool'
I'm not sure if I'm using it correctly, can anyone give me some pointers? The commented out && above it:
//&& db.tblTrackerVisitVariables.Any(d=>VisitIDs.Contains(d.VisitID) && d.VariableID == c.ID)
Is the old working code (but it throws the too many params error so I have to resort to this extension method).
Mark's extension method returns an IEnumerable, not a bool, and therefore cannot be included in a logical expression. I you want to check if the result is non-empty try the Any method:
db.tblTrackerVisitVariables.InRange(x => x.VisitID, 1500, VisitIDs).Any()

Categories

Resources