Max throws InvalidOperationException - c#

I Have this query
decimal? query = context.History
.Where(x => x.ItemId == Id
&& x.OtherFilter == IdOtherFilter
&& x.Fecha < Fecha)
.Select(x => x.Secuencia)
.Max();
I need to perform this query into Linq:
select max(Secuencia)
from History
where ItemId= 1406
and OtherFilter= 3
and Fecha < '20150922'
group by OtherFilter
I need to get the max Secuencia from a table that match with criteria, it works fine when criteria returns data, but when no data match with criteria, Max() can't materialize throwing an InvalidOperationException,
The cast to value type 'System.Decimal' failed because the materialized value is null. Either the result type's generic parameter or the query must use a nullable type.
how to avoid that ?
There is another Linq that I can use ?
are those sentences equivalent? if not What is wrong in my linq?

Assuming that the type of the Secuencia property is decimal and not decimal?, you should try adding a cast to decimal? so that Max will return null in the case that there are no values.
decimal? query = context.History
.Where(x => x.ItemId == Id
&& x.OtherFilter == IdOtherFilter
&& x.Fecha < Fecha)
.Select(x => (decimal?) x.Secuencia)
.Max();
or
decimal? query = context.History
.Where(x => x.ItemId == Id
&& x.OtherFilter == IdOtherFilter
&& x.Fecha < Fecha)
.Select(x => x.Secuencia)
.Cast<decimal?>()
.Max();

Make following modification to make it work, add DefaultIfEmpty() before Max() call, this will take care of empty list, also you may specify custom default value like 0.0, here it would be null, since the type selected is a nullable value type, check DefaultIfEmpty
decimal? query = context.History
.Where(x => x.ItemId == Id
&& x.OtherFilter == IdOtherFilter
&& x.Fecha < Fecha)
.Select(x => x.Secuencia)
.DefaultIfEmpty()
.Max();

Related

Compare between DateTime from Client and DateTime Saved in Database

I'm trying to make a comparison between two Dates in Date,Hours,Minutes,Seconds but when I invoke
x.CreationTime.TimeOfDay the nullable exception appeared.
I thought this link answer will solve my problem but after I fellow the solution the problem still appread
Here is my query :
public async Task<List<MessageDto>> getMessageHistory(long userId, string code, long HCSId, long Role,DateTime latestMessageDateTime , DateTime messageDateBeforeSeeMore)
{
var result =await _repository.GetAllIncluding(x => x.listOfMessages)
.Where(x => ((x.receiverID == userId|| x.CreatorUserId == userId)
&& x.code== code) && (
x.CreationTime != null
&& x.CreationTime.Date > latestMessageDateTime.Date
&& x.CreationTime.TimeOfDay !=null
&& x.CreationTime.TimeOfDay.Hours > latestMessageDateTime.TimeOfDay.Hours /*<===== this cause the problem if I remove it the query working fine*/
)
&& x.listOfMessages.Any(x => x.HCSId== HCSId)
).OrderBy(message => message.CreationTime).ToListAsync();
return result ;
}
Update:
the Exception details :
- $exception {"Object reference not set to an instance of an object."} System.NullReferenceException
+ Data {System.Collections.ListDictionaryInternal} System.Collections.IDictionary {System.Collections.ListDictionaryInternal}
at MyProject.ChatAppService.MessageAppService.<getMessageHistory>d__19.MoveNext() in D:\WorkSpace\MyProject\aspnet-core\src\MyProject.Application\ChatAppService\MessageAppService.cs:line 346
+ TargetSite {Void MoveNext()} System.Reflection.MethodBase {System.Reflection.RuntimeMethodInfo}
Update 2:
I follow the steps that have been mentioned by #RandRandom
and what I found is below Exception
The LINQ expression 'DbSet<Message>
.Where(m => m.receiverID == __citizenId_0 || m.CreatorUserId == __userId_0 && m.code == __code_1 && DbSet<HCSMessages>
.Where(h => EF.Property<Nullable<long>>(m, "Id") != null && EF.Property<Nullable<long>>(m, "Id") == EF.Property<Nullable<long>>(h, "messageId"))
.Any(h => h.HCsId == __HCSId_2) && m.CreationTime > __messageDateBeforeSeeMore_3)
.Where(m => m.CreationTime.Date > __latestMessageDateTime_Date_4)
.Where(m => m.CreationTime.TimeOfDay.Hours > __latestMessageDateTime_TimeOfDay_Hours_5)' 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.
Question:
The exception didn't have an inner exception with more usefull information?
Attempt of an answer:
A wild guess would be, the only thing that actually can be null at all is x.listOfMessages since it is the only reference type you are using, everything else is a struct.
You can "easily" narrow the error down, by spliting your query into multiple parts and materialize each query seperatly one after the other.
So first rewrite your query like this:
public async Task<List<MessageDto>> getMessageHistory(long userId, string code, long HCSId, long Role,DateTime latestMessageDateTime , DateTime messageDateBeforeSeeMore)
{
IEnumerable<MessageDto> result = await _repository.GetAllIncluding(x => x.listOfMessages);
result = result.Where(x => x.receiverID == userId || x.CreatorUserId == userId);
result = result.Where(x => x.code == code);
//dropped the null checks for DateTime and TimeOfDay, since NULL should be impossible
result = result.Where(x => x.CreationTime.Date > latestMessageDateTime.Date );
result = result.Where(x => x.CreationTime.TimeOfDay.Hours > latestMessageDateTime.TimeOfDay.Hours);
result = result.Where(x => x.listOfMessages.Any(x => x.HCSId== HCSId));
result = result.OrderBy(message => message.CreationTime);
return (List<MessageDto>)result;
}
After this add .ToListAsync() on each line beginning at the top and moving it with each successfull step one place further down.
So on your first test change the first line to this:
IEnumerable<MessageDto> result = await _repository.GetAllIncluding(x => x.listOfMessages).ToListAsync(); //added .ToListAsync()
For the second test remove the ToListAsync() from the first line and add it to the second line, first and second line should than look like this:
IEnumerable<MessageDto> result = await _repository.GetAllIncluding(x => x.listOfMessages); //removed .ToListAsync()
result = result.Where(x => (x.receiverID == userId || x.CreatorUserId == userId)).ToListAsync(); //added .ToListAsync()
With this approach your are materializing each condidition seperatly and can figure out what condition will fail.
Edit:
The error in "Update2" happens because you are doing something that can't be translated into a SQL query.
To fix this in general you have two options
Run the unsupported expression locally, there for skip the necessety to translate the expression to SQL
To do this you have to evaluate all expressions prior to the unsupported, the evalution happens when an enumeration on the IQueryable happens, easy ways to do this are for example to call ToList(), ToArray() or AsEnumerable(), after this your expression will run against an IEnumerable<T> instead of an IQueryable<T>
Use DbFunctions if a suiting one is available - https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.dbfunctions - or write your own functions - https://khalidabuhakmeh.com/add-custom-database-functions-for-entity-framework-core
But in your case I would question your expressions at a whole, why are comparing the date and hours seperatly
result = result.Where(x => x.CreationTime.Date > latestMessageDateTime.Date );
result = result.Where(x => x.CreationTime.TimeOfDay.Hours > latestMessageDateTime.TimeOfDay.Hours);
Instead of just simple comparing the DateTime struct?
result = result.Where(x => x.CreationTime > latestMessageDateTime );
At the beggining I will try to put the query parameters (date from client) outside the query and get concrete values.
For example
int lastMessageHours = latestMessageDateTime.TimeOfDay.Hours;
DateTime lastMessageDate = latestMessageDateTime.Date;
And then pass it to query
var result =await _repository.GetAllIncluding(x => x.listOfMessages)
.Where(x => ((x.receiverID == userId|| x.CreatorUserId == userId)
&& x.code== code) && (
x.CreationTime != null
&& x.CreationTime.Date > lastMessageDate
&& x.CreationTime.TimeOfDay !=null
&& x.CreationTime.TimeOfDay.Hours > latestMessageHours
)
&& x.listOfMessages.Any(x => x.HCSId== HCSId)
).OrderBy(message => message.CreationTime).ToListAsync();
If this does not help CreationTime has value if it is nullable type.
x.CreationTime.HasValue
I also suggest to split this query between multiple methods. Write extension method for this query:
public static IQueryable<Message> NewerThan(this IQueryable<Message> query, DateTime value)
{
int lastMessageHours = latestMessageDateTime.TimeOfDay.Hours;
DateTime lastMessageDate = latestMessageDateTime.Date;
return query.Where(x=> x.CreationTime != null
&& x.CreationTime.Date > lastMessageDate
&& x.CreationTime.TimeOfDay !=null
&& x.CreationTime.TimeOfDay.Hours > latestMessageHours );
}
Query will be more readable.

How to check if column value has "value"

I want to check if value of "Acceptance" is "Accepted" and if it has that value- .Sum else i want to get 0, but have some problms with finding correct syntax. I cant get "Acceptance" value
My method
public decimal Prices2(string term)
{
var kl= db.Order.Where(x => x.Client == term && x.Acceptance == "Accepted");
if (term != null && kl.Acceptance == "Accepted" )
{ ^^//Element does not contain definition"Acceptance"
return kl.Sum(x => x.Price);
}
else
{
return 0;
}
}
how I get "term"
public IEnumerable<string> GetClient(string term)
{
return db.Order.Where(x => (term != null && x.Client.Contains(term)) || (term == null)).OrderBy(c => c.Client).Take(10).Select(a => a.Client).Distinct();
}
When i try this:
return db.Order.Where(x => x.Client == term && x.Acceptance == "Accepted").sum(x=>x.Price)
I get error when there is no "Accepted" order for client
System.InvalidOperationException: „The cast to value type 'System.Decimal' failed because the materialized value is null. Either the result type's generic parameter or the query must use a nullable type.”
How can i check first if there is Accepted order for client and return 0 if there istn?
First Assign the query to one variable,
var orders = db.Order.Where(x => x.Client == term && x.Acceptance == "Accepted");
var result= orders.Count() > 0 ? orders.Sum(x=>x.Price) : 0.0M;
return result;
You're return type of method should be decimal
.
First of all, your code will not work because variable kl is of type IEnumerable<Order> which doesn't have a property Acceptance.
The code mentioned by #uk2k05 in the comments section will solve your problem:
return db.Order
.Where(x => x.Client == term && x.Acceptance == "Accepted")
.Sum(x => x.Price);
But another problem will occur, that's because Sum method returns Nullable<Decimal>, while your method Prices2 returns decimal. Click to see docs
To solve this problem you can use the following code instead:
return db.Order
.Where(x => x.Client == term && x.Acceptance == "Accepted")
.Sum(x => x.Price) ?? 0;
That will return the value returned by Sum or 0 if the returned value is null.

Using linq to find MIN(date) within linq query

I am using linq to extract data. This data contains a date and some other values. The thing i that these dates can occur more then once, because the dates can have the same value but a different timestamp. I want to extract the anonymous type with the earliest timestamp. How can i do this in linq ?
this is my code:
var result = (from a in UnitOfWork.ActivityLessonParticipantService.Query
.Where(a => a.ActivityLesson.Activity.Id == activityId)
.Where(a => a.ActivityLesson.From >= startDate && (a.ActivityLesson.To == startDate || a.ActivityLesson.To <= endDate)).OrderBy(d => d.ActivityLesson.From)
where !a.ActivityLesson.IsDeleted && !a.ActivityLesson.Activity.IsDeleted && a.Appeared == true
select new
{
CPR = a.User.UserName,
FullName = a.User.FullName,
ActivityFromDate = a.ActivityLesson.From,
}).OrderBy(c => c.CPR).ToList();
thanks
You can GroupBy the Date property of DateTime and then order this group by DateTime and use First to pick only the first record/object:
var query = from a in UnitOfWork.ActivityLessonParticipantService.Query
where a.ActivityLesson.Activity.Id == activityId
&& a.ActivityLesson.From >= startDate
&& (a.ActivityLesson.To == startDate || a.ActivityLesson.To <= endDate)
&& !a.ActivityLesson.IsDeleted
&& !a.ActivityLesson.Activity.IsDeleted
&& a.Appeared
select a;
var firstByDate = query.GroupBy(a => a.ActivityLesson.From.Date)
.Select(grp => grp.OrderBy(a => a.ActivityLesson.From).First())
.OrderBy(a => a.User.UserName)
.Select(a => new
{
CPR = a.User.UserName,
FullName = a.User.FullName,
ActivityFromDate = a.ActivityLesson.From,
}).ToList();
Due to LINQ's deferred execution this is actually a single query that gets executed at the final ToList. I'm mixing query and method syntax because i prefer method syntax when it comes to GroupBy but it's a matter of taste.

how to correct the following linq?

I have the following LINQ statement:
var inspectorAllocSummary = context
.UserDetails
.Where(ud => ud.PerformAudit == true && ud.Valid == true)
.OrderBy(ud => ud.Firstname)
.Select(ud
=> new InspectorWorkStatusModel
{
InspectorName = ud.Firstname,
ToBeCompleted = context
.InspectorWorkAllocations
.Where(x => x.UploadCOESDetails.AuditMonth
== criteria.AuditMonth
&& x.InspectorId == ud.Id)
.Sum(x=> x.ToBeAudited) ?? 0,
Completed = context
.COESDetails
.Where(x => x.UploadCOESDetails.AuditMonth
== criteria.AuditMonth
&& x.InspectorId == ud.Id
&& x.AuditType != null)
.Count()
});
The ToBeCompleted is an integer, it gets data from the database, but if it is null I want to make sure it is set to 0. I tried the following:
ToBeCompleted = context
.InspectorWorkAllocations
.Where(x => x.UploadCOESDetails.AuditMonth == criteria.AuditMonth
&& x.InspectorId == ud.Id)
.Sum(x=> x.ToBeAudited) ?? 0
but I get the following error:
Operator '??' cannot be applied to operands of type 'int' and 'int'
How can I ensure that if the return data is null, that it will be set to zero?
If ToBeAudited is an int then Sum can never return null, it always returns an int. If the collection is empty it will return 0. just take off ?? 0.
However, Linq-to-SQL and Linq-to-Entities can throw an error if the resultant SQL does not return any records. For Linq-to-SQL one "fix" is to cast the underlying field to a nullable type:
.Sum(x=> (int?)x.ToBeAudited) ?? 0
For Linq-to-Entities a fix is to call .DefaultIfEmpty():
.Select(x=> x.ToBeAudited)
.DefaultIfEmpty(0)
.Sum()
First, you need to change the type of ToBeAudited to int?. The problem comes from the fact that int is a value type and it cannot contain null. That's why in your first approach you get the error:
The cast to value type 'Int32' failed because the materialized value is null.
There is a Sum overload which accepts nullable values. So after changin ToBeAudited to nullable this will suffice:
ToBeCompleted = context
.InspectorWorkAllocations
.Where(x => x.UploadCOESDetails.AuditMonth == criteria.AuditMonth
&& x.InspectorId == ud.Id)
.Sum(x => x.ToBeAudited).Value;
You don't need the ?? operator, as null values are being automatically ignored (and for the empty sequence Sum will return 0).

Order by child record value(s) that can be empty

I've tried some combinations but I just don't understand how to do the following:
Lets say I have tables Requests and RequestActivities. I need to get all request sorted by RequestActivity.TimeOfCreation in descending order but RequestActivity may be null.
List<DA.GeneralRequest> ongoingGeneralRequests = db.GeneralRequests
.Where(t => t.GeneralRequestStatusID != 3 && (t.SupervisorID == currentUserId || t.CreatorID == currentUserId || t.AssignedUsers.Any(au => au.UserID == currentUserId)))
.OrderByDescending(x => x.GeneralRequestActivities.OrderBy(ga => ga.GeneralRequestActivityDate).Last().GeneralRequestActivityDate) //gives exeption
.ThenBy(a => a.Deadline).ToList();
I'm not really familiar with LINQ-To-SQL but doesn't work MAX in this case?
.OrderByDescending(x => x.GeneralRequestActivities
.Max(ga => ga.GeneralRequestActivityDate))
.ThenBy(a => a.Deadline)
.ToList();
You need to first cache the ordering value, and then order by the date if it is not null, else by some default date you want:
List<DA.GeneralRequest> ongoingGeneralRequests = db.GeneralRequests
.Where(t => t.GeneralRequestStatusID != 3 && (t.SupervisorID == currentUserId || t.CreatorID == currentUserId || t.AssignedUsers.Any(au => au.UserID == currentUserId)))
.Select(x => new {
Value = x,
OrderByValue = x.GeneralRequestActivities
.OrderBy(ga => ga.GeneralRequestActivityDate)
.LastOrDefault()) // cache value
.OrderByDescending(x => x.OrderByValue != null ?
OrderByValue.GeneralRequestActivityDate
: some default value)
.ThenBy(a => a.Value.Deadline)
.Select(a => a.Value)
.ToList();
Note that you can't use Last() extension method on empty IEnumerable. This is why you get the exception:
InvalidOperationException : The source sequence is empty.
In this line:
x.GeneralRequestActivities.OrderBy(ga => ga.GeneralRequestActivityDate).Last()
x.GeneralRequestActivities is empty, so calling Last() on it result on the exception.
Instead, use the LastOrDefault() extension method, which return null if the IEnumerable is empty.
Return Value Type: TSource default (TSource) if the source sequence is
empty; otherwise, the last element in the IEnumerable.

Categories

Resources