Linq Statement when you need to do a Convert.To? - c#

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();

Related

How to efficiently grab results from Linq with or without user input params

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();
}
}

List.OrderBy define order in a variable

I have a list containing objects :
class MyType
{
String Name;
String Address;
DateTime BirthDay;
}
List<MyType> myList;
I want to write a function working like this :
public void SortList(int value)
{
// orderValue => this is the variable I need
if (value == 0)
orderValue = MyType.Name; // Sort list by Name
else if (value == 1)
orderValue = MyType.Address; // Sort list by Address
else if (value == 1)
orderValue = MyType.BirthDay; // Sort list by BirthDay
if (orderValue != null)
{
List<MyType> sortedList = myList.OrderBy(orderValue).ToList();
if (Enumerable.SequenceEqual(myList, sortedList))
sortedList = myList.OrderByDescending(orderValue).ToList();
myList = sortedList;
}
}
How can I write my orderValue to make this code working ?
This is just a synthesis of my code, I can provide more details if needed.
OrderBy can accept Func and you can have variable like this
Func<Person, string> orderFunc = x => x.Name;
if (value == 0)
orderFunc = x => x.Name;
else if (value == 1)
orderFunc = x => x.Address;
myList = myList.OrderBy(orderFunc).ToList();
You have several ways of doing it. For example, you can add OrderBy clause inside your conditional statement, like this:
IEnumerable<MyType> data = myList;
if (value == 0)
data = data.OrderBy(x => x.Name); // Sort list by Name
else if (value == 1)
data = data.OrderBy(x => x.Address); // Sort list by Address
res = data.ToList();
You can also do it in a single statement:
res = myList
.OrderBy(x => value == 0 ? x.Name : "")
.ThenBy(x => value == 1 ? x.Address : "")
.ToList();
This is somewhat harder to read, but it will do the work on RDBMS side in EF or LINQ to SQL.
You can try
myList.OrderBy((x) => (value == 0 ? x.Name : x.Address));

In C#, how can I sort a collection of objects by multiple fields of that object?

I have a list of project objects and I want to sort the collection by using a few different fields in the project object.
IEnumerable<Project> list = GetProjectList();
public class Project
{
public DateTime? Date;
public string ImportanceLevel;
}
I first want to
Sort by the date field (sooner dates should be at the top of the list versus dates farther out in the future) and those should show up before any item without a date.
For the items that don't have Dates set them i want to sort by ImportanceLevel (which can be one of 3 strings (Low, Medium or High) where High would go first and then Medium and then Low
I appreciate this would be better if these were integer values but unfortunately they are stored as strings right now).
One option is
var sorted = list.OrderBy(l => l.Date.HasValue ? l.Date : DateTime.MaxValue)
.ThenBy(l => l.ImportanceLevel == "High" ? 1 :
(l.ImportanceLevel == "Medium" ? 2 : 3));
Here, it will do what you want, also it'll sort the projects with same date, by importance.
Or,
var sorted2 = list.OrderBy(l => l.Date.HasValue ?
int.Parse(l.Date.Value.ToString("yyyyMMdd")) :
(l.ImportanceLevel == "High" ?
100000001 :
(l.ImportanceLevel == "Medium" ? 100000002 : 100000003)));
Where, it'll not sort the projects which have date, by importance.
//Order by the projects with Date
var projectsWithDate = list.Where(d => d.Date != null)
.OrderBy(s => s.Date).ToList();
// Projects without Dates
var withoutdate = list.Where(d => d.Date == null)
.OrderBy(g =>
{
if (g.ImportanceLevel == "High")
return 1;
if (g.ImportanceLevel == "Medium")
return 2;
if (g.ImportanceLevel == "Low")
return 3;
return 0;
})
.ToList();
//Add the second list to first.
projectsWithDate.AddRange(withoutdate);
// Now you can use projectsWithDate collection.
This will work
var orderedList = list
.OrderBy(p => p.Date)
.ThenByDescending(p =>
{
if (p.ImportanceLevel == "Low")
return 1;
if (p.ImportanceLevel == "Medium")
return 2;
if (p.ImportanceLevel == "Hight")
return 3;
return 0;
});
public class Project
{
public DateTime? Date;
public string ImportanceLevel;
}
var sortedProejcts =
projects.OrderByDescending(p => p.Date.HasValue)
.ThenBy(p => Math.Abs(DateTime.Now.CompareTo(p.Date ?? DateTime.MaxValue)))
.ThenByDescending(
p =>
{
switch (p.ImportanceLevel)
{
case "Low":
return 1;
case "Medium":
return 2;
case "High":
return 3;
default:
return 0;
}
});

IEnumerable.Any

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?

How to combine the multiple part linq into one query?

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();

Categories