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.
Related
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.
I want to display results that don't have a secondary ID displayed first and then display items that do have a secondary ID. But then I need to Skip and Take.
IQueryable<thing> result;
IQueryable<thing> result2;
result2 = result
.Where(t => !(t.second_id == null || t.second_id.Trim() == string.Empty))
.OrderBy(t => t.second_id);
result = result
.Where(t => (t.second_id== null || t.second_id.Trim() == string.Empty))
.OrderBy(t => t.first_id);
result = result.Concat(result2);
return result
.Select(t => t.primary_key)
.Skip(pageSize * pageNumber)
.Take(pageSize)
.ToList();
The problem is that after Concat the IQueryable is no longer technically ordered so Skip and Take throw an error. Like this:
PagedList error: The method 'OrderBy' must be called before the method 'Skip'
You can do it in one query with the conditional operator:
return result.OrderBy(t => (t.second_id != null && t.second_id.Trim() != String.Empty))
.ThenBy(t => (t.second_id != null && t.second_id.Trim() != String.Empty) ? t.second_id : t.first_id)
.Select(t => t.primary_key)
.Skip(pageSize * pageNumber)
.Take(pageSize)
.ToList();
It would need some adjustment if you need to order duplicate second_id in some way, but your original code doesn't.
PS I folded in the negation operator since I think it reads more clearly.
You can do the job with a single query
result = result
.OrderByDescending(t => (t.second_id== null || t.second_id.Trim() ==
string.Empty))
.ThenBy(t => t.second_id)
.ThenBy(t => t.first_id)
.Select(t => t.primary_key)
.Skip(pageSize * pageNumber)
.Take(pageSize)
.ToList();
I used entity framework with my example. I wanted to filter child entity but I am getting exception 'The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.'
public List<Notification> GetNotificationBySentDate(DateTime? dateTime)
{
if (dateTime == null)
{
return
_dbContext.Notifications.Include(x => x.Attachments.Select(a=>a.Clean==true))
.Where(x =>
x.Sent == null &&
x.FaultCount <= _appSettingsHelper.NotificationsFaultCountLimit &&
DbFunctions.AddSeconds(x.CreatedDate, x.DelaySeconds) < DateTime.UtcNow)
.OrderBy(a => DbFunctions.AddSeconds(a.CreatedDate, a.DelaySeconds))
.Take(_appSettingsHelper.NotificationsBySentStateSelectTop).ToList();
}
return _dbContext.Notifications.Include(x => x.Attachments).Where(x => x.Sent >= dateTime)
.OrderBy(a => a.CreatedDate)
.Take(_appSettingsHelper.NotificationsBySentStateSelectTop).ToList();
}
Any help would be much appreciated. Thanks.
It seems that the problem is with Select statement under the Include. You can try to remove it and add an additional clause to Where, something like && x.Attachements.Clean == true. Thus your code will be
_dbContext.Notifications.Include(x => x.Attachments)
.Where(x =>
x.Sent == null &&
x.FaultCount <= _appSettingsHelper.NotificationsFaultCountLimit &&
DbFunctions.AddSeconds(x.CreatedDate, x.DelaySeconds) < DateTime.UtcNow &&
x.Attachments.Clean == true)
.OrderBy(a => DbFunctions.AddSeconds(a.CreatedDate, a.DelaySeconds))
.Take(_appSettingsHelper.NotificationsBySentStateSelectTop).ToList();
another kinda newbie question I guess. I have EF setup and now I want to select some records based on a filter. I have SomeClass with 4 items (all strings to keep things simple, lets call them string1, string2, and so on). Now, in a post I send the filter in an instance of SomeClass, but maybe not all properties are filled in.
So you might end up with string1="something", string2="bla" and string4="bla2". So string 3 = null. Now, how do I setup the query? If i try something like:
var dataset = entities.mydatabase
.Where(x => x.string1 == someclass.string1 && x.string2 == someclass.string2 && x.string3 == someclass.string3 && x.string4 == someclass.string4)
.Select(x => new { x.string1, x.string2, x.string3, x.string4}).ToList();
... I get no results, because string3=null. I could do something with checking all parameters and see if they're set and create the query based on that, but there must be something more elegant than that.
Anyone?
Thanks!
Ronald
The following will return all rows where the someclass.string is null OR equals to x.string.
var dataset = entities.mydatabase
.Where(x => someclass.string1 == null || x.string1 == someclass.string1)
.Where(x => someclass.string2 == null || x.string2 == someclass.string2)
.Where(x => someclass.string3 == null || x.string3 == someclass.string3)
.Where(x => someclass.string4 == null || x.string4 == someclass.string4)
.Select(x => new { x.string1, x.string2, x.string3, x.string4}).ToList();
I 'm new to NHibernate & LINQ. I have a piece of code which I think can be optimized. Please help me to do so.
foreach (var geography in geographyList.OrderBy(x => x.Name))
{
var introductionDateDetail = environment.IntroductionDateInfo
.IntroductionDateDetails
.OrderByDescending(x => x.ApplicationDate)
.FirstOrDefault(x => x.Geography.Id == geography.Id &&
x.VaccineDetail.Id == vaccineDetail.Id &&
x.MasterForecastInfo.Id == masterforecast.Id &&
x.ViewInfo.Id == viewInfoDetail.ViewInfo.Id);
}
The for loop may iterate to about thousand records.And hence the LINQ statement is also executed that many times. Can we write a piece of code where we can execute the LINQ statement just once?
You can try something like this:
var geographiesId = geographyList.Select(g => g.Id);
var introductionDetails = environment.IntroductionDateInfo
.IntroductionDateDetails
.OrderByDescending(x => x.ApplicationDate)
.FirstOrDefault(x => geographiesId.Contains(x.Geography.Id) &&
x.VaccineDetail.Id == vaccineDetail.Id &&
x.MasterForecastInfo.Id == masterforecast.Id &&
x.ViewInfo.Id == viewInfoDetail