Compare between DateTime from Client and DateTime Saved in Database - c#

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.

Related

Conditioning LINQ Query

Currently I have a LINQ query like this:
var policy = snapshotDate == null
? await _dbContext.VPolicies
.Where(p => p.PolicyNumber.Trim().Equals(policyNumber))
.OrderByDescending(d => d.PolicyEffectiveDate)
.FirstOrDefaultAsync()
: await _dbContext.VPolicies
.Where(p => p.PolicyNumber.Trim().Equals(policyNumber)
&& (p.PolicyEffectiveDate <= snapshotDate && p.PolicyExpirationDate > snapshotDate))
.OrderByDescending(d => d.PolicyEffectiveDate)
.FirstOrDefaultAsync();
I would like to know if I can shorten it like this:
var policy = await _dbContext.VPolicies
.Where(p => p.PolicyNumber.Trim().Equals(policyNumber))
.Where(p => snapshotDate != null && (p.PolicyEffectiveDate <= snapshotDate && p.PolicyExpirationDate > snapshotDate))
.OrderByDescending(d => d.PolicyEffectiveDate)
.FirstOrDefaultAsync();
But this doesn't work and I would like to know how I can condition this LINQ query in a proper way and shorten them without using the terenary operator.
Thanks in advance.
Try this:
var policy = await _dbContext.VPolicies
.Where(p => p.PolicyNumber.Trim().Equals(policyNumber)
&& (snapshotDate == null ||
(p.PolicyEffectiveDate <= snapshotDate &&
p.PolicyExpirationDate > snapshotDate)))
.OrderByDescending(d => d.PolicyEffectiveDate)
.FirstOrDefaultAsync();
This query will apply conditions of PolicyEffectiveDate and PolicyExpirationDate if snapshotDate is not null otherwise only PolicyNumber condition will apply.
Seems like you want your condition to evaluate
snapshotDate == null || (p.PolicyEffectiveDate <= snapshotDate && p.PolicyExpirationDate > snapshotDate)
Which says, give all dates when snapshotDate is null, or within when it's not.
Note : about multiple where clauses. In a memory based collection you are will get a performance increase combining them with an && (due to the extra delegate invocation, and potential multiple enumerations), however with most LINQ to Query providers it will evaluate in the exact same SQL. Though in general it's best combining these if you can.

C# Linq no working with "Greater than" operators

Every time I try to run the following sentence in my function:
var comission = await _context.Comissions.FirstOrDefaultAsync(c =>
c.CountryReceiverId == receiverCountryId &&
c.CountrySenderId == senderCountryId);
var comissionRange = await _context.ComissionRanges
.Where(c => c.MinAmount <= amount && c.MaxAmount >= amount)
.FirstOrDefaultAsync(c => c.ComissionId == comission.Id);
it gives me the following error:
The LINQ expression 'DbSet<ComissionRange> .Where(c => c.MinAmount <= __amount_0 && c.MaxAmount >= __amount_0)' 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().
The error happens at the comissionRange query specifically, and I noticed the following, like if the problem it is with the >= or <= operator, for example if I modify such query like this:
var comissionRange = await _context.ComissionRanges
.Where(c => c.MinAmount == 501 && c.MaxAmount == 1000)
.FirstOrDefaultAsync(c => c.ComissionId == comission.Id);
it works perfectly! It's like if the problem it's because I am using the >= and <= operators, what will be the right way to rewrite this query and make it work?

Linq To Entities Internal Conversion

I must to execute this expression:
var CAP = modelCap.AZCPC00F.Where(x => x.CPCVER == CPCVER && x.CPCNAR == CPCNAR && x.CPCCAP == CPCCAP)
.Where(x => XXXXKG == 0 ? true : Convert.ToInt64(Convert.ToDouble(x.CPCLKG)) < XXXXKG)
.Where(x => XXXXMC == 0 ? true : Convert.ToInt64(Convert.ToDouble(x.CPCLMC)) < XXXXMC)
.Where(x => XXXXFD == "N" ? true : x.CPCZFD == XXXXFD).FirstOrDefault();
When I try to execute this I have an exception on the internal conversion of x.CPCLKG. The exception is:
LINQ to Entities does not recognize the method 'int64' method, and this method cannot be translated into a store expression.
I know that the problem is in the conversion but how can i use this function?
An example of x.CPCLKG is 9.9999 and is an nChar character type.
Thanks to all
You need to have the logic that is not supported in Linq To Entities done in an in-memory query. We can do this by calling ToList() on the parts of the query that are supported. This will execute those parts, and return the results as a list. We can then execute whatever Linq supports on that list, in memory.
var CAP = modelCap.AZCPC00F.Where(x => x.CPCVER == CPCVER && x.CPCNAR == CPCNAR && x.CPCCAP == CPCCAP)
.Where(x => XXXXFD == "N" ? true : x.CPCZFD == XXXXFD)
.ToList();
var CAP2 = CAP.Where(x => XXXXKG == 0 ? true : Convert.ToInt64(Convert.ToDouble(x.CPCLKG)) < XXXXKG)
.Where(x => XXXXMC == 0 ? true : Convert.ToInt64(Convert.ToDouble(x.CPCLMC)) < XXXXMC).FirstOrDefault()

multiple where in linq based on parameters value [duplicate]

This question already has answers here:
Linq to SQL multiple conditional where clauses
(3 answers)
Closed 7 years ago.
I want to have multiple where clauses in linq but out of them only one should execute, i was trying something like this:
public JsonResult GetPost(int? id, int? tagid, DateTime? date)
{
var ret = from data in db.Posts.Include(x => x.Tags)
.Include(x => x.Neighbourhood)
.Where((x => x.NeighbourhoodId == id) || (y => y.PostedDate == date) || third condition).ToList()
but i was unable to put second and third condition there becoz after putting dot after y, i cant see any options.
Now, out of these three, only one parameter would have value and other two would have null so, it should checks for parameter only with value.
should i write query like this, is it correct way:
if (id != null)
{
//whole query here
}
else if (tagid != null)
{
//whole query here
}
else (date != null)
{
//whole query here
}
Is it the best way to do this or something else is possible. many many thnks in advance for any suggestion.
Something like this?
var ret = from data in db.Posts.Include(x => x.Tags)
.Include(x => x.Neighbourhood)
.Where(x => x.NeighbourhoodId == (id ?? x.NeighbourhoodId) &&
x.<condition> == (tagid ?? x.<condition>) &&
x.PostedDate == (date ?? x.PostedDate).ToList();
Or like this:
var ret = from data in db.Posts.Include(x => x.Tags)
.Include(x => x.Neighbourhood)
.Where(x => id.HasValue ? x.NeighbourhoodId == id :
tagid.HasValue ? x.<condition> == tagid :
x.PostedDate == date).ToList();
Another option is to build your query more dynamically. I think this also makes your code more human readable, and your conditions can be more complex if needed (for example, build your query inside a loop or something). And you can use this with any other operator, like Include etc. Once your query is built, you can call ToList().
var ret = db.Posts.Include(x => x.Tags).Include(x => x.Neighbourhood);
if (id != null)
{
ret = ret.Where((x => x.NeighbourhoodId == id);
}
else
{
...
}
var result = ret.ToList();
You could use the following:
var ret = from data in db.Posts.Include(x => x.Tags)
.Include(x => x.Neighbourhood)
.Where(x => id == null || x.NeighbourhoodId == id)
.Where(x => date == null || y.PostedDate == date)
.ToList();
If the paramter is null, the where-clauses returns every element of the sequence. If its not null it only returns the elements which matches.

Entity Framework: Unable to create a constant value of type 'System.Collections.Generic.IList`1'

This has caused me no end of problems today. I have this simple query
var result =
DataContext.Accommodations.Where(a =>
(criteria.MinPrice == null || a.AccommodationRates.Any(r => r.From >= criteria.MinPrice)) &&
(criteria.MaxPrice == null || a.AccommodationRates.Any(r => r.To <= criteria.MaxPrice)) &&
(criteria.Locations == null || criteria.Locations.Count == 0 || a.AccommodationPlaceJoins.Any(j => criteria.Locations.Contains(j.Place.PlaceName)))
);
The last line of this query is causing me problems
(criteria.Locations == null ||
criteria.Locations.Count == 0 ||
a.AccommodationPlaceJoins.Any(j => criteria.Locations.Contains(j.Place.PlaceName)))
The error it gives is
Unable to create a constant value of type
'System.Collections.Generic.IList`1'. Only primitive types ('such as
Int32, String, and Guid') are supported in this context.
I'm not even trying to create a list. All I'm trying to do here is bring back accommodations which are associated to a place (where the place name in the Place table which is linked to the Accommodation table via the AccommodationPlaceJoin table) is equal to any one of the place names in criteria.Locations (which is of type IList).
I've tried changing this line to this, but it didn't work.
(criteria.Locations == null ||
criteria.Locations.Count == 0 ||
a.AccommodationPlaceJoins.Any(j => criteria.Locations.Any(l => l == j.Place.PlaceName)))
The constant value EF can't create is null for the comparison criteria.Locations == null. You need to split the query into two cases and do the check for the empty list outside of the query, for example like so:
var result = DataContext.Accommodations.Where(a =>
(criteria.MinPrice == null ||
a.AccommodationRates.Any(r => r.From >= criteria.MinPrice)) &&
(criteria.MaxPrice == null ||
a.AccommodationRates.Any(r => r.To <= criteria.MaxPrice)));
if (criteria.Locations != null && criteria.Locations.Count > 0)
{
result = result.Where(a => a.AccommodationPlaceJoins
.Any(j => criteria.Locations.Contains(j.Place.PlaceName)));
}
Edit
BTW: Composing the whole query would make it better readable in my opinion and will simplify the SQL that has to be sent to the database:
IQueryable<Accommodation> result = DataContext.Accommodations;
if (criteria.MinPrice != null)
result = result.Where(a => a.AccommodationRates
.Any(r => r.From >= criteria.MinPrice));
if (criteria.MaxPrice != null)
result = result.Where(a => a.AccommodationRates
.Any(r => r.To <= criteria.MaxPrice));
if (criteria.Locations != null && criteria.Locations.Count > 0)
result = result.Where(a => a.AccommodationPlaceJoins
.Any(j => criteria.Locations.Contains(j.Place.PlaceName)));
I had this same issue when trying to pass a list of strings for comparison into an EF core query recently and found that it resolves the issue to assign an empty array or empty list to the value in case it is null rather than doing the null check right in the EF Core query.
This allows you to query the EF Core database without the null error and you don't need a bunch of conditional branching for separate queries - just one query with a bit of null coalescence.
So your code fixed this way would look like this:
var locations = criteria.Locations == null ? new List<Location>() : criteria.Locations;
var result =
DataContext.Accommodations.Where(a =>
(criteria.MinPrice == null || a.AccommodationRates.Any(r => r.From >= criteria.MinPrice)) &&
(criteria.MaxPrice == null || a.AccommodationRates.Any(r => r.To <= criteria.MaxPrice)) &&
(locations.Any() ? a.AccommodationPlaceJoins.Any(j => criteria.Locations.Contains(j.Place.PlaceName))) : true);
Keep in mind I am not sure of the type for locations or accomodationplacejoins so I cannot be sure the above code will exactly work but the general idea is we are doing a .Any() instead of checking for null. This way we can keep our query a little simpler. We are then using a ternary expression within the last block of our where clause so if the locations list turns out to be empty, we skip the condition entirely and just return true which coalesces the empty list if it is empty.

Categories

Resources