Is it possible that the order by datetime or the get max() datetime is not working in the in-memory database?
var document = dbContext.Documents
.Where(x => x.CompanyGuid == companyGuid && x.PackageDocuments.Where(y => y.PackageGuid == packageGuid).Count() > 0)
.OrderByDescending(x => x.KeepDocumentUntilDateUtc)
.FirstOrDefault();
It always returns me the first value of the list and not the record with the max datetime
Related
I have a table of entities of class Pricing.
Previously the Pricing table had a unique index based on 3 foreign keys: InstitutionId, SubmissionTypeId and FeeTypeId.
Now it needs to have multiple possible Pricing rows per that same index, but I should only use the one with the latest value of a DateTime column ActiveFrom, i.e. I should only use the latest active Pricing for the given composite index.
My repository method needs to return all currently active Pricings matching two foreign key values (InstitutionId and SubmissionTypeId).
I tried doing it like this:
DateTime today = DateTime.Today;
IQueryable<Pricing> filteredActivePricings = Context.Pricings
.Where(pricing =>
pricing.InstitutionId == institutionId
&& pricing.SubmissionTypeId == submissionTypeId
&& pricing.ActiveFrom <= today
)
.GroupBy(pricing => new { pricing.InstitutionId, pricing.SubmissionTypeId, pricing.FeeTypeId })
.Select(pricingGroup => pricingGroup
.OrderByDescending(pricing => pricing.ActiveFrom)
.First()
);
IList<Pricing> result = await filteredActivePricings.ToListAsync();
But EF Core says it can't convert that syntax into a valid SQL query.
I've found a solution using pure SQL code, but I'd like to do it in the EF Core's LINQ method syntax, since the rest of my codebase is written in it.
So, I figured out my problem and a solution.
The problem was, as #ivan-stoev pointed out, I wrongly thought that IGrouping<K, T> (the result of IQueryable<T>.GroupBy) contained a collection of T entities grouped by the key K, and that I had to somehow process that collection to get my single entity T.
So here's my solution which utilizes the IQueryable<T>.GroupBy method properly, to get a collection of key values and dates which are then used in a Join back to the table, to get the full data of the entities I need.
DateTime today = DateTime.Today;
IQueryable<ActivePricingKey> filteredActivePricingKeyQuery = Context.Pricings
.Where(pricing =>
pricing.InstitutionId == institutionId
&& pricing.SubmissionTypeId == submissionTypeId
&& pricing.ActiveFrom <= today
)
.GroupBy<Pricing, PricingKey, ActivePricingKey>(
(Pricing pricing)
=> new PricingKey
{
InstitutionId = pricing.InstitutionId,
SubmissionTypeId = pricing.SubmissionTypeId,
FeeTypeId = pricing.FeeTypeId
},
(PricingKey key, IEnumerable<Pricing> pricings)
=> new ActivePricingKey
{
InstitutionId = key.InstitutionId,
SubmissionTypeId = key.SubmissionTypeId,
FeeTypeId = key.FeeTypeId,
ActiveFrom = pricings.Max(pricing => pricing.ActiveFrom)
}
);
IQueryable<Pricing> filteredActivePricingQuery = filteredActivePricingKeyQuery
.Join(
Context.Pricings,
(ActivePricingKey activePricingKey)
=> new
{
activePricingKey.InstitutionId,
activePricingKey.SubmissionTypeId,
activePricingKey.FeeTypeId,
activePricingKey.ActiveFrom
},
(Pricing pricing)
=> new
{
pricing.InstitutionId,
pricing.SubmissionTypeId,
pricing.FeeTypeId,
pricing.ActiveFrom
},
(ActivePricingKey activePricingKey, Pricing pricing) => pricing
);
ICollection<Pricing> pricings = await filteredActivePricingQuery.ToListAsync();
I am trying to run this query, however I get an error saying that this query could not be translated.
I have tried subtracting seconds and milliseconds but the StartDateTime has some fractions of the millisecond that I couldn't eliminate.
While trying some options of truncating the seconds and milliseconds and ticks, I got the same error of NOT BEING ABLE TO TRANSLATE THE QUERY.
Is there any appropriate way of doing this query?
await _context.AppointmentReminders
.Where(x => x.RemindAt.ToString(#"MM/DD/YYYY h:mm") == request.StartDateTime.ToString(#"MM/DD/YYYY h:mm")
&& x.IsHandled == false)
.Include(a => a.Appointment)
.ThenInclude(a => a.AppointmentInvitations)
.ToListAsync();
Well the solution was to compare the date, hour and minute each separately.
await _context.AppointmentReminders.Where(x => x.RemindAt.Date == request.StartDateTime.Date &&
x.RemindAt.Hour == request.StartDateTime.Hour &&
x.RemindAt.Minute == request.StartDateTime.Minute &&
x.IsHandled == false)
.Include(a => a.Appointment)
.ThenInclude(a => a.AppointmentInvitations)
.ToListAsync();
In this case you should be able to simply rework this as a range query. EG
var d = request.StartDateTime;
var startDate = new DateTime(d.Year, d.Month, d.Day, d.Hour, d.Minute, 0);
var endDate = startDate.AddMinutes(1);
await _context.AppointmentReminders.Where(x => x.RemindAt >= startDate && x.RemindAt < endDate
&& x.IsHandled == false).Include(a => a.Appointment)
.ThenInclude(a => a.AppointmentInvitations
)
.ToListAsync();
Using Entity Framework Core 2.2 I have the following query:
var user = await context.Users.AsNoTracking()
.Include(x => x.Lessons).ThenInclude(x => x.LessonLevel)
.FirstOrDefaultAsync(x => x.Id == userId);
var lessons = context.Lessons.AsNoTracking();
.Where(x => x.LessonLevelId < user.Lessons.Max(y => y.LessonLevelId));
Thus query evaluates locally and I get the message:
The LINQ expression 'Max()' could not be translated and will be evaluated locally.'
How can I make this query evaluate on the server?
Update
Based on DavigG answer I made it work using:
var maxLessonLevelId = user.Lessons.Max(y => y.LessonLevelId););
var lessons = context.Lessons.AsNoTracking();
.Where(x => x.LessonLevelId < maxLessonLevelId);
I know the following evaluates locally but shouldn't evaluate on the server?
var lessons = context.Lessons.AsNoTracking();
.Where(x => x.LessonLevelId <
context.Users.AsNoTracking()
.Where(y => y.Id == userId)
.Select(y => y.Lessons.Max(z => z.LessonLevelId))
.FirstOrDefault());
Is it possible to use a child queries that evaluates on the server?
Get the max value as a separate query, for example:
var maxLessonLevelId = user.Lessons.Max(y => y.LessonLevelId);
Then you can can get the lessons like this:
var lessons = context.Lessons.AsNoTracking()
.Where(x => x.LessonLevelId < maxLessonLevelId);
I have a query that gets data in the form of an IQueryable
var assys = assetrelationshipRepository.GetAll()
.Where(x => x.AssetId == siteAssetId)
.Where(x => x.RelationshipTypeId == (long)AssetRelationshipTypeEnum.Parent)
.Where(x => x.RelatedAsset.AssetTypeId == (long)AssetTypeEnum.Assembly)
.Select(x => x.RelatedAsset.CustomAssetAttributes2);
For every 'assy' that is returned, I'd like to get it's AssetId and use this to get a list of 'subassys', see below. For each 'assy' record, the assyId variable should be substituted for its AssetId.
var subassys = assetrelationshipRepository.GetAll()
.Where(x => x.AssetId == assyId)
.Where(x => x.RelationshipTypeId == (long)AssetRelationshipTypeEnum.Parent)
.Where(x => x.RelatedAsset.AssetTypeId == (long)AssetTypeEnum.SubAssy)
.Select(x => x.RelatedAsset.CustomAssetAttributes2);
I assume I'll need to use ForEach, does anyone know if what I'm trying to do is possible?
Thanks
Applying ForEach would be extremely bad approach to retrieve your desired result. You could apply join and group for that queries instead.
var assysQuery = assetrelationshipRepository.GetAll()
.Where(x => x.AssetId == siteAssetId)
.Where(x => x.RelationshipTypeId == (long)AssetRelationshipTypeEnum.Parent)
.Where(x => x.RelatedAsset.AssetTypeId == (long)AssetTypeEnum.Assembly);
Then apply join and group;
var subAssysQuery =
from assy in assysQuery
join subAssy in assetrelationshipRepository.GetAll() on assy.Id equals subAssy.AssetId
where
subAssy.RelationshipTypeId == (long)AssetRelationshipTypeEnum.Parent &&
subAssy.RelatedAsset.AssetTypeId == (long)AssetTypeEnum.Assembly
group assy by assy.Id into g
select new
{
AssyId = g.Key,
SubAssets = g.Select(x => x.RelatedAsset.CustomAssetAttributes2),
};
This should give you CustomAssetAttributes2 value of subassys for per assy record.
Note : Also, I suggest you to use known type for select clause instead
of anonymous type.
I'm trying to count some records that were updated this week and group them by the day of week (depending when they were last updated). E.g.So Tues:1, Thur:4 Fri:5 etc... I'm not sure how to group by day of week.
var data = repo
.Where(o => o.LastUpdated >= monday)
.GroupBy(o => o.LastUpdated)
.Select(g => new { DayOfWeek = g.Key, Count = g.Count() })
.ToList();
I've tried .GroupBy(o => o.LastUpdated.DayOfWeek but that throws an error :
"The specified type member 'DayOfWeek' is not supported in LINQ to Entities"
If you are targeting only SqlServer database type, you can use SqlFunctions.DatePart canonical function like this
var data = repo
.Where(o => o.LastUpdated >= monday)
.GroupBy(o => SqlFunctions.DatePart("weekday", o.LastUpdated))
.Select(g => new { DayOfWeek = g.Key, Count = g.Count() })
.ToList();
Unfortunately there is no such general canonical function defined in DbFunctions, so if you are targeting another database type (or multiple database types), the only option is to switch to Linq To Objects context as described in another answer.
The message is explicit, Entity Framework doesn't know how to translate "DayOfWeek" to SQL. The simplest solution would be to do the grouping outside of SQL after retrieving the data:
var data = repo
.Where(o => o.LastUpdated >= monday)
.AsEnumerable() // After this everything uses LINQ to Objects and is executed locally, not on your SQL server
.GroupBy(o => o.LastUpdated)
.Select(g => new { DayOfWeek = g.Key, Count = g.Count() })
.ToList();
It should hardly have a performance impact either way as you're not filtering further down so you're not retrieving more data than you need from the server, anything past AsEnumerable is materialized as data, anything before just générâtes a SQL query, so past AsEnumerable (or anything else that would materialize the query like ToArray or ToList) you can use anything you'd normally use in C# without worrying about it being translatable to SQL.
It is only possible to lastupdated column datatype of datetime.
var data = repo.Where(o => o.LastUpdated >= monday).AsEnumerable().GroupBy(o => o.LastUpdated.Value.Day).Select(g => new { DayOfWeek = g.Key, Count = g.Count() }).ToList();