I have the following method which returns jobs for a certain date range and status. The problem is that when I'm using IEnumerable.Any, it returns all jobs even those that do not have the specified status. Is there a way to fine-tune it so that it only returns jobs with a particular status? Here's the code:
public List<JobDTO> GetJobList(int domainID, DateTime? dateFrom, DateTime? dateTo, PlannedTravelStopStatusTypeEnum status)
{
var context = new WebSchedulerContext();
var list = new List<JobDTO>();
if (dateFrom == null)
{
dateFrom = DateTime.MinValue;
}
if (dateTo == null)
{
dateTo = DateTime.MaxValue;
}
var jobs = context.Job.Include(j => j.PlannedJobStopDetails
.Select(jsd => jsd.PlannedTravelStop)
)
.Where(j => j.MinimumStartDate >= dateFrom && j.MaximumEndDate <= dateTo &&
**j.PlannedJobStopDetails.Any**(
jsd => jsd.PlannedTravelStop.PlannedTravelStopStatus == status
));
foreach (var job in jobs)
{
list.Add(new JobDTO(job));
}
return list;
}
The code fragment in question is :
j.PlannedJobStopDetails.Any(jsd =>
jsd.PlannedTravelStop.PlannedTravelStopStatus == status)
As far as I can see there is a 1 : n relation between a Job and PlannedJobStopDetails.
And in your statement you say: Give me a job, if any of its PlannedJobStopDetails (this is a collection) has the "status" to look for.
Perhaps you want to say that each of the PlannedJobStopDetails should have the same status?
Then you need to change from "Any" to "All":
var jobs = context.Job.Include(j => j.PlannedJobStopDetails
.Select(jsd => jsd.PlannedTravelStop)
)
.Where(j => j.MinimumStartDate >= dateFrom && j.MaximumEndDate <= dateTo &&
j.PlannedJobStopDetails.**All**(
jsd => jsd.PlannedTravelStop.PlannedTravelStopStatus == status
));
j.PlannedJobStopDetails.Any(jsd => jsd.PlannedTravelStop.PlannedTravelStopStatus == status) returns true if any of the PlannedJobStopDetails have a PlannedTravelStopStatus matching status.
If you want to only return the jobs where all of the PlannedTravelStopStatus match your given status, use All().
Edit: On second thought, are you sure that PlannedJobStopDetails is actually a foreign key relationship that EF understands?
Related
I am a little confused about how I can use the date as an optional condition.
if there is a date then <= of date, if the date is null then don't filter based on date.
My code is like this
DateTime date= DateTime.UtcNow.AddDays(-10);
foreach (var camEvent in dbContext
.CAM_EVENTS
.Where(c => c.USER_ID == userID &&
c.CAM_ID == cam.CAM_ID &&
c.EVENT_DATE >= date) // I want to change this like
.OrderByDescending(c => c.DATE))
{...}
I want that line to look something like this
(date && c.EVENT_DATE >= date)
so it only filter when date is not null, but this is not working.
I'd do the following logic:
(date==null || (c.EVENT_DATE >= date))
You can do something like this:
DateTime date = DateTime.UtcNow.AddDays(-10);
var filteredContext = dbContext
.CAM_EVENTS
.Where(c => c.USER_ID == userID &&
c.CAM_ID == cam.CAM_ID)
.OrderByDescending(c => c.DATE);
if (date != null) {
filteredContext = filteredContext.Where(c.EVENT_DATE >= date);
}
foreach (var camEvent in filteredContext) {
...
}
You can use a ternary operator, also known as a conditional operator.
foreach (var camEvent in dbContext
.CAM_EVENTS
.Where(c => c.USER_ID == userID &&
c.CAM_ID == cam.CAM_ID &&
// if date is not null, it will return bool c.EVENT_DATE >= date, otherwise just true
(date != null ? c.EVENT_DATE >= date : true))
.OrderByDescending(c => c.DATE))
I'm trying to use the entity framework to filter my GET method... Using the if the condition it's working
public async Task<List<TimeEntryViewModel>> Get(TimeEntryFilter filter)
{
var result = await _readRepository
.FindByCondition(x => x.IsApproved == true)
.Include(x => x.Task)
.Include(x => x.Account)
.Select(x => new TimeEntryViewModel
{
AccountName = x.Account.Name,
Date = x.Date,
StartTime = x.StartTime,
FinishTime = x.FinishTime,
InternalId = x.InternalId,
TaskId = x.TaskId,
TaskName = x.Task.Name,
CreatedBy = x.CreatedBy,
CustomerName = x.Task.Project.ServiceOrder.Customer.Name
}).ToListAsync().ConfigureAwait(false);
if (filter.AccountName != null ||
(filter.StartDate.HasValue && filter.FinishDate.HasValue && (filter.StartDate <= filter.FinishDate)) ||
(filter.TaskName != null) ||
(filter.CustomerName != null) ||
(filter.InternalId != null))
result = result.Where(x =>
(x.AccountName.ToString().Contains(filter.AccountName)) &&
(x.Date >= filter.StartDate && x.Date <= filter.FinishDate) &&
(x.CustomerName.ToString().Contains(filter.CustomerName)) &&
(x.TaskName.ToString().Contains(filter.TaskName)) &&
(x.InternalId.ToString().Contains(filter.InternalId.ToString()))).ToList();
return result;
}
But I imagine that there is a way to improve this method without using if
I'd had already tried to do that (Just filter account name to show)
But returned Internal Server Error
var result = await _readRepository
.FindByCondition(x => x.IsApproved == true)
.Where(x => string.IsNullOrEmpty(filter.AccountName) || x.Account.Name.ToString().Contains(filter.AccountName))
.Include(x => x.Task)
.Include(x => x.Account)
.Select(x => new TimeEntryViewModel
{
Id = x.Id,
AccountName = x.Account.Name,
Date = x.Date,
Description = x.Description,
StartTime = x.StartTime,
FinishTime = x.FinishTime,
InternalId = x.InternalId,
OnSite = x.OnSite,
IsApproved = x.IsApproved,
IsBillable = x.IsBillable,
TaskId = x.TaskId,
TaskName = x.Task.Name,
CreatedBy = x.CreatedBy,
CustomerName = x.Task.Project.ServiceOrder.Customer.Name
}).ToListAsync().ConfigureAwait(false);
Okay Guys EDIT1: In my second sample, using InternalId instead of Account.Name
.Where(x => (string.IsNullOrEmpty(filter.InternalId.ToString())) || x.InternalId.ToString().Contains(filter.InternalId.ToString()))
it worked.... I believe that I'm having trouble to work with something that is a foreign key in my table...
There is a very interesting way to mass multiple bool values (contains or not etc) with only one value. It's called bit bitwise operations. Here: Most common C# bitwise operations on enums
you can find different approachs to this "technique".
It returned null because you don't check if Account property is null.
AccountName = x.Account.Name
So the above will throw a Null Reference Exception.
A way to avoid this, it is to use the null conditional operator:
AccountName = x.Account?.Name
You can follow the same approach also for properties like:
Task.Name
Task.Project.ServiceOrder.Customer.Name
And access them safely, by using the same operator, like below:
Task?.Name
Task?.Project?.ServiceOrder?.Customer?.Name
Based on the above you can follow the way you have already followed for AccountName property, but you have also at the same time to use the null conditional operator.
It looks like you need && operator:
.Where(x => string.IsNullOrEmpty(filter?.AccountName)
&& x?.Account?.Name.ToString().Contains(filter.AccountName))
because when you use || operator, then you can get NullReferenceException in the second part of your expression in Where:
x?.Account?.Name.ToString().Contains(filter.AccountName))
Okay, apparently, my problem was .Where(x => string.IsNullOrEmpty(filter.AccountName) || x.Account.Name.ToString().Contains(filter.AccountName)) after x.Account.Name I can't use ToString()... I don't know exactly why, but now works without the if condition
public async Task<List<TimeEntryViewModel>> GetApproved(TimeEntryFilter filter)
{
var result = await _readRepository
.FindByCondition(x => x.IsApproved == true)
.Include(x => x.Task)
.Include(x => x.Account)
.Where(x =>
(string.IsNullOrEmpty(filter.InternalId.ToString()) || x.InternalId.ToString().Contains(filter.InternalId.ToString())) &&
(string.IsNullOrEmpty(filter.AccountName) || x.Account.Name.Contains(filter.AccountName)) &&
(string.IsNullOrEmpty(filter.CustomerName) || x.Task.Project.ServiceOrder.Customer.Name.Contains(filter.CustomerName)) &&
(string.IsNullOrEmpty(filter.TaskName) || x.Task.Name.Contains(filter.TaskName)))
.Select(x => new TimeEntryViewModel
{
Id = x.Id,
AccountName = x.Account.Name,
Date = x.Date,
Description = x.Description,
StartTime = x.StartTime,
FinishTime = x.FinishTime,
InternalId = x.InternalId,
OnSite = x.OnSite,
IsApproved = x.IsApproved,
IsBillable = x.IsBillable,
TaskId = x.TaskId,
TaskName = x.Task.Name,
CreatedBy = x.CreatedBy,
CustomerName = x.Task.Project.ServiceOrder.Customer.Name
}).ToListAsync().ConfigureAwait(false);
return result;
}
If anyone knows why it's working, I'll be thankful
I have a linq query that I would like to return results with user input data. However, if this function gets called and there is zero data from user, OR user just wants to search via data, OR just one of the other parameters, how can I efficiently write the linq to accommodate for this? Here is the Linq and function:
public static List<Objects.Logs.GenericLog> GetLogs(int entityId, int logLevelId,
DateTime startDate, DateTime endDate)
{
var logsList = new List<Objects.Logs.GenericLog>();
using(var db = CORAContext.GetCORAContext())
{
logsList = (from i in db.GenericLog select new Objects.Logs.GenericLog()
{
EntityId = i.FkEntityId,
LogSourceCode = i.FkLogSourceCode,
LogLevelId = i.FkLogLevelId,
LogDateTime = i.LogDateTime,
LogId = i.PkLogId,
Message = i.Message
})
.Where(i => i.LogDateTime >= startDate && i.LogDateTime <= endDate)
.Where(i => i.EntityId == entityId || i.EntityId == null)
.Where(i => i.LogLevelId == logLevelId || i.EntityId == null)
.ToList();
}
return logsList;
}
For example, in the second and third Where(), I have || i.EntityId == null... thinking this would accomodate for is user input for Entity is null?
Will this work?
Also, how can I do this for date ranges? Can I also do the same?
Finally, is there a BETTER way to do this?
Split creating a query and generating a final result by .ToList()
When you generate a query, you can add where statements on demand, like this:
public static List<Objects.Logs.GenericLog> GetLogs(int entityId, int logLevelId, DateTime startDate, DateTime endDate)
{
var logsList = new List<Objects.Logs.GenericLog>();
using(var db = CORAContext.GetCORAContext())
{
var query = (from i in db.GenericLog select new Objects.Logs.GenericLog()
{
EntityId = i.FkEntityId,
LogSourceCode = i.FkLogSourceCode,
LogLevelId = i.FkLogLevelId,
LogDateTime = i.LogDateTime,
LogId = i.PkLogId,
Message = i.Message
});
if(someCondition) {
query = query.Where(i => i.LogDateTime >= startDate && i.LogDateTime <= endDate)
}
query = query.Where(i => i.EntityId == entityId || i.EntityId == null)
query = query.Where(i => i.LogLevelId == logLevelId || i.EntityId == null)
logsList = query.ToList();
}
return logsList;
}
If I understand you correctly, you have a method that gets a filtered set of data based on the values of the parameters passed in. But you want to make the parameters optional, so if the user wants data for all entities, they wouldn't pass in an entityId.
If that's the case, then you can make the arguments optional by providing a default value for them in the method signature. We can then check if the argument has the default value, and if it does, don't apply that filter; otherwise apply it.
We can do this by doing .Where(x => argHasDefaultValue || someFilter). This works because if the argument has the default value, then the second part of the || is ignored.
For example:
public static List<Objects.Logs.GenericLog> GetLogs(int entityId = int.MinValue,
int logLevelId = int.MinValue, DateTime startDate = default(DateTime),
DateTime endDate = default(DateTime))
{
using(var db = CORAContext.GetCORAContext())
{
return db.GenericLog
.Where(i => startDate == default(DateTime) || i.LogDateTime >= startDate)
.Where(i => endDate == default(DateTime) || i.LogDateTime <= endDate)
.Where(i => entityId == int.MinValue || i.EntityId == entityId)
.Where(i => logLevelId == int.MinValue || i.LogLevelId == logLevelId)
.Select(i => new Objects.Logs.GenericLog
{
EntityId = i.FkEntityId,
LogSourceCode = i.FkLogSourceCode,
LogLevelId = i.FkLogLevelId,
LogDateTime = i.LogDateTime,
LogId = i.PkLogId,
Message = i.Message
}).ToList();
}
}
Is there anyways I can stop from having to stay Convert twice?
allObjects.Where(x =>
Convert.ToDateTime(x.MyDate).DayOfWeek != DayOfWeek.Saturday &&
Convert.ToDateTime(x.MyDate).DayOfWeek != DayOfWeek.Sunday).ToList()
Assume I can't change the type of "MyDate" to datetime to stop from having to do the convert in the first place
For these sorts of cases it is often easier to use the query comprehension form.
var q = from obj in allObjects
let day = Convert.ToDateTime(obj.MyDate).DayOfWeek
where day != DayOfWeek.Saturday
where day != DayOfWeek.Sunday
select obj;
var result = q.ToList();
Note that the answer given by Tim Schmelter is the fluent form of this query; I find the comprehension form much easier to read and reason about.
Assume I can't change the type of "MyDate" to datetime to stop from
having to do the convert in the first place
Why you assume that? That would be the best option
If you can't do that you could store the result in an anonymous type:
var objectList = allObjects
.Select(x => new { Object = x, Date = Convert.ToDateTime(x.MyDate) })
.Where(x => x.Date.DayOfWeek != DayOfWeek.Saturday && x.Date.DayOfWeek != DayOfWeek.Sunday)
.Select(x => x.Object)
.ToList();
Making a helper extension method will make your query more readable:
static class DayOfWeekExtensions {
public static bool IsWeekEnd(DayOfWeek dow) {
return dow == DayOfWeek.Saturday || dow == DayOfWeek.Sunday;
}
}
Now your query could be rewritten as follows:
var notOnWeekEnds = allObjects
.Where(x => !Convert.ToDateTime(x.MyDate).DayOfWeek.IsWeekEnd())
.ToList()
You can do this:
allObject.Where(x => { var dt = Convert.ToDateTime(x.MyDate);
return dt.DayOfWeek != DayOfWeek.Saturday && dt.DayOfWeek != DayOfWeek.Sunday;
}).ToList();
Yes -
allObjects.Where(x => {
var dateConvered = Convert.ToDateTime(x.MyDate);
return dateConvered .DayOfWeek != DayOfWeek.Saturday && dateConvered.DayOfWeek != DayOfWeek.Sunday; }).ToList();
I would probably go this route. I hate multiple OR logic.
var allObjects = new List<Foo>
{
new Foo { MyDate = "3/10/18" },
new Foo { MyDate = "3/11/18" },
new Foo { MyDate = "3/12/18" },
new Foo { MyDate = "3/13/18" },
new Foo { MyDate = "3/14/18" }
};
var valuesToRemove = new DayOfWeek[]
{
DayOfWeek.Saturday, DayOfWeek.Sunday
};
var list = allObjects.Where //<-- This is the part that will interest you
(
x => Array.IndexOf
(
valuesToRemove,
Convert.ToDateTime(x.MyDate).DayOfWeek
) == -1
);
Console.WriteLine(string.Join("\r\n", list));
Output:
3/12/18
3/13/18
3/14/18
Code on DotNetFiddle
This might not help with regard to reusing calculated values in LINQ expressions, but in your specific case, you can rewrite it as:
var weekendDays = new [] { DayOfWeek.Saturday, DayOfWeek.Sunday };
var weekdayObjects = allObjects.Where(x =>
!weekendDays.Contains(Convert.ToDateTime(x.MyDate).DayOfWeek)
).ToList();
Operator should be ‘AND’ and not a ‘OR’.
I am trying to refactor the following code and i understood the following way of writing linq query may not be the correct way. Can somone advice me how to combine the following into one query.
AllCompany.Where(itm => itm != null).Distinct().ToList();
if (AllCompany.Count > 0)
{
//COMPANY NAME
if (isfldCompanyName)
{
AllCompany = AllCompany.Where(company => company["Company Name"].StartsWith(fldCompanyName)).ToList();
}
//SECTOR
if (isfldSector)
{
AllCompany = AllCompany.Where(company => fldSector.Intersect(company["Sectors"].Split('|')).Any()).ToList();
}
//LOCATION
if (isfldLocation)
{
AllCompany = AllCompany.Where(company => fldLocation.Intersect(company["Location"].Split('|')).Any()).ToList();
}
//CREATED DATE
if (isfldcreatedDate)
{
AllCompany = AllCompany.Where(company => company.Statistics.Created >= createdDate).ToList();
}
//LAST UPDATED DATE
if (isfldUpdatedDate)
{
AllCompany = AllCompany.Where(company => company.Statistics.Updated >= updatedDate).ToList();
}
//Allow Placements
if (isfldEmployerLevel)
{
fldEmployerLevel = (fldEmployerLevel == "Yes") ? "1" : "";
AllCompany = AllCompany.Where(company => company["Allow Placements"].ToString() == fldEmployerLevel).ToList();
}
Firstly, unless AllCompany is of some magic custom type, the first line gives you nothing.
Also I have a doubt that Distinctworks the way You want it to. I don't know the type of AllCompany but I would guess it gives you only reference distinction.
Either way here'w what I think You want:
fldEmployerLevel = (fldEmployerLevel == "Yes") ? "1" : "";
var result = AllCompany.Where(itm => itm != null)
.Where(company => !isfldCompanyName || company["Company Name"].StartsWith(fldCompanyName))
.Where(company => !isfldSector|| fldSector.Intersect(company["Sectors"].Split('|')).Any())
.Where(company => !isfldLocation|| fldLocation.Intersect(company["Location"].Split('|')).Any())
.Where(company => !isfldcreatedDate|| company.Statistics.Created >= createdDate)
.Where(company => !isfldUpdatedDate|| company.Statistics.Updated >= updatedDate)
.Where(company => !isfldEmployerLevel|| company["Allow Placements"].ToString() == fldEmployerLevel)
.Distinct()
.ToList();
Edit:
I moved Distinct to the end of the query to optimize the processing.
How about trying like this;
AllCompany = AllCompany .Where(company => (company => company.Statistics.Created >= createdDate)) && (company.Statistics.Updated >= updatedDate));
If every part of query is optional (like created date, last update date..) then you can build linq query string.
Here's a sneaky trick. If you define the following extension method in its own static class:
public virtual IEnumerable<T> WhereAll(params Expression<Predicate<T> filters)
{
return filters.Aggregate(dbSet, (acc, element) => acc.Where(element));
}
then you can write
var result = AllCompany.WhereAll(itm => itm != null,
company => !isfldCompanyName || company["Company Name"].StartsWith(fldCompanyName),
company => !isfldSectorn || fldSector.Intersect(company["Sectors"].Split('|')).Any(),
company => !isfldLocation || fldLocation.Intersect(company["Location"].Split('|')).Any(),
company => !isfldcreatedDate || company.Statistics.Created >= createdDate,
company => !isfldUpdatedDate || company.Statistics.Updated >= updatedDate,
company => !isfldEmployerLevel || company["Allow Placements"].ToString() == fldEmployerLevel)
.Distinct()
.ToList();