Compare 2 lists with LINQ - c#

I want to get all date values from dateRange where each datetime is the DayOfWeek.Friday,..Monday,..Sunday.
The Intersect did not work because I guess those 2 lists are not real intersections... and they have a different type: DateTime vs. Enum.DayOfWeek
This gives me only all fridays but I also want the Mondays,Sundays... without using the OR operator.
var dateRange = _dateService.GetDateRange(startDate, endDate).Where(d => visibleWeekDays.Any(v => d.DayOfWeek == v)).ToList();
These are both lists I have to compare somehow:
IEnumerable<DateTime> dateRange = _dateService.GetDateRange(startDate, endDate);
IEnumerable<DayOfWeek> visibleWeekDays = new List<DayOfWeek>
{
DayOfWeek.Friday,
DayOfWeek.Monday,
DayOfWeek.Sunday,
};
Please DO not write the full ling query as solution.
Just write the linq extension methods in random order I have to use to solve the riddle.
Just for the funs and learning sake :)
UPDATE
See my input datetime values and the output I want:
BUT be aware, the visibleWeekDays list is not static. There can be a dynamic number of values in this collection. Therefore I can and do not want to use the && or || operator.

When you find yourself in a position of wanting an intersection, but where the types of the two collections aren't the same, it usually means you want a Join. An intersection, by definition, is a join in which the two collections are of the same type, where the key selector is "itself", and where the result selector just picks one of the items at random (since they must be equal, by the definition of intersection). Since not all of these restrictions apply to you all you need to do is step out to the more general Join.
Just as a demonstration of this, here is an implementation of Intersect using just Join:
public static IEnumerable<T> Intersect<T>(this IEnumerable<T> first
, IEnumerable<T> second, IEqualityComparer<T> comparer)
{
return first.Join(second, x => x, x => x, (a, b) => a, comparer);
}
The DayOfWeek can just select itself as a key, and then you just need a method of getting a DayOfWeek object out of a DateTime for the key selector. For your result selector you only need to grab the DateTime object; you shouldn't need the DayOfWeek object anymore.
Whenever you see yourself writing a LINQ solution that has a Where(x => collection.Any(... or Contains or some other search operation inside of a Where odds are you should be using a Join instead, if applicable (you should at least ask yourself if you should be using a Join).
Since you don't want a full implementation I'll put it below in a spoiler tag. Don't look at it if you want to write the code yourself:
public static IEnumerable FilterDaysOfWeek(IEnumerable dates
, IEnumerable daysOfWeek)
{
return dates.Join(daysOfWeek
, date => date.DayOfWeek
, day => day
, (date, day) => date);
}

If you convert DayOfWeek to an integer, you can do a simple > and < comparison to get valid values, so just a .Where query should be good.

You could use Contains on the visibleWeekDays in your Where clause.

If you want to get all the dates in GetDateRange that have a DayOfWeek that matches those in visibleWeekdays you could use the following Linq statement:
_dateService.GetDateRange(startDate, endDate)
.Where(d=> visibleWeekdays.Contains(d.DayOfWeek));
Below is a full test of this in action:
class Test
{
static void Main(string[] args)
{
var weekdays = new[] { DayOfWeek.Friday, DayOfWeek.Saturday, DayOfWeek.Sunday };
var result = GetDateRange(DateTime.Today, DateTime.Today.AddDays(14))
.Where(d => weekdays.Contains(d.DayOfWeek));
}
public static IEnumerable<DateTime> GetDateRange(DateTime start, DateTime end)
{
DateTime date = start;
do
{
yield return date;
date = date.AddDays(1);
}
while (date < end);
}
}

Related

Finding duplicate items then selecting one with closest date to currentDate

Note: Using Windows Mobile 6.5 Compact Framework.
I have a collection of the following object.
public class RFileModel
{
public List<string> RequiredFilesForR = new List<string>();
public string Date { get; set; }
public string RouteId { get; set; }
}
var ListOfRFileModels = new List<RFileModel>();
There is the chance that the same RouteId will be in multiple instances of RFileModel but with a different Date.
I'm trying to identify the duplicates and select only one, the one closest to the current date.
I have the following LINQ so far:
var query = ListOfRFileModels.GroupBy(route => route.RouteId)
.OrderBy(newGroup => newGroup.Key)
.Select(newGroup => newGroup).ToList();
But I don't think this is what I need, since it still returns all elements. I was expecting a list of non unique RouteId, that way I can iterate each non-unique id and compare dates to see which one to keep.
How can I accomplish this with LINQ or just plain ole foreach?
Your expression sorts groups, not group elements. Here is how to fix it:
DateTime currentDate = ...
var query = ListOfRFileModels
.GroupBy(route => route.RouteId)
.Select(g => g.OrderBy(fm => currentDate-fm.Date).First())
.ToList();
currentDate-fm.Date expression produces the difference between the current date and the date of the RFileModel object. The object with the smallest difference would end up in the first position of the ordered sequence. The call First() picks it up from the group to produce the final result.
Assuming you want ONLY the members with duplicates, take #dasblinkenlight's answer and add a Where clause: .Where(grp => grp.Count()>1):
DateTime currentDate = DateTime.Now;
var query = ListOfRFileModels
.GroupBy(route => route.RouteId)
.Where(grp => grp.Count()>1)
.Select(g => g.OrderBy(fm => currentDate-fm.Date).First())
.ToList();

Return Timespan ticks difference in LINQ queries

I'm querying a list of objects, and I want to return a TimeSpan's ticks list of the difference between the time registered in the object, and now.
I wanted all in one expression, so:
var list = (from r in res where r.Site == "CNIS"
select Math.Abs((r.Timestamp.Value - DateTime.Now).Ticks)).ToList();
But I get the following error:
Exception Details: DbArithmeticExpression arguments must have a numeric common type
I already managed to do a workaround. For example, my code looks like this now:
var list = new List<long>();
foreach(var item in (from r in res where r.Site == "CNIS" select r))
list.Add(Math.Abs((item.Timestamp.Value - DateTime.Now).Ticks));
But what I really wanted to know is if it is possible to get the Timespan diff from a DateTime value to now, in a single LINQ query
It seems the error is relevant to the translation of your select statement into SQL.If fecthing the results form DB is not a problem you can do it using AsEnumerable and then project the items:
var now = DateTime.Now;
var list = res.Where(r => r.Site == "CNIS")
.AsEnumerable()
.Select(x => Math.Abs((x.Timestamp.Value - now).Ticks))
.ToList();
And since the value of DateTime.Now changes you should probably store the it into a variable and use it in the calculation.

Process List Using LINQ?

In my c# MVC4 application, I have a list of strings. Each odd element in the list is a datetime. The even element before each of them is a unique identifier.
For instance:
[0] is A7M0066
[1] is 2007-01-06 06:24:00.000
I want to process the list and add the top 5 most recent pairs based on datetime in the odd elements to another list of strings. Im not sure where to begin but Im assuming it will require LINQ.
Using Keith's answer below and testing I realized that what I actually need has changed. Using his approach, I get 5 results that are most recent but 3 of them have the same id. I need the end result to all have a unique id. Basically I need to keep 1 of the 3 entries that are the same and continue processing until all 5 are unique.
var items =
list.Where((x, i) => i%2 == 0)
.Zip(list.Where((x, i) => i%2 == 1), (f, s) => new {Id = f, Date = s})
.OrderByDescending(x => x.Date)
.DistinctBy(x => x.Id, null) // see note later in this answer....
.Take(5)
.ToList();
this will zip the even elements with the odd elements and turn them into an Object with Id and Date as fields. Then sorts by the date, and takes the 5 latest
so you can then iterate over each object and do as you wish.
ie, an example use to print out the 5 to the console...
items.ForEach(x => Console.WriteLine("{0} {1}", x.Id, x.Date));
for doing unique IDs with the greatest date..... refer LINQ: Distinct values
and use the extension method shown by Mr Skeet ... except I've improved it a bit, my version is :-
public static class DistinctLinq
{
public static IEnumerable<TSource> DistinctBy<TSource, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
IEqualityComparer<TKey> comparer)
{
var knownKeys = new HashSet<TKey>(comparer);
return source.Where(element => knownKeys.Add(keySelector(element)));
}
}
I think you are going about this all wrong. Since these "pairs" are related, lets make a class for them.
public class DateTimePairs(){
public string Id {get;set;}
public DateTime Date {get;set;}
}
Now, lets make a list of those:
var MyList = new List<DateTimePairs>();
MyList = 'Get your values for the list
Finally, returning what you want is going to be really really simple
return MyList.OrderByDescending(x=>x.Date).Take(5);
You can do this:
var results =
(from i in Enumerable.Range(0, myList.Length / 2)
let Guid = Guid.Parse(myList[i * 2])
let Date = DateTime.Parse(myList[i * 2 + 1])
orderby Date descending
return new { Guid, Date })
.Take(5);
Expanding upon Keith's answer, consider the following method, which parses the Date itself. I add this not because you need it for simple ordering (your date format will allow that) - but because you might need it if you start doing other kinds of comparison based on Dates.
var dt = DateTime.MinValue;
var items = list.Where((x, i) => i % 2 == 0)
.Zip(list.Where((x, i) => i % 2 == 1), (f, s) => new {
Id = f,
Date = DateTime.TryParse(s, out dt) ? (DateTime?)dt : null
})
.OrderByDescending(x => x.Date);
Because of the .TryParse() ternary, if the expected odd element does not parse into a valid DateTime object, you get back null. If it does, you get back the parsed DateTime. The resultant list can now be used type-safely to compare for bad data (look for nulls) or do other kinds of DateTime comparison / sorting.
Similar to the Zip solution, I'd recommend creating and using the more generally useful "partition" extension method (see Split List into Sublists with LINQ). With a "Partition" extension method returning IEnumerable<List<T>>, for example, implementation is pretty self documenting as:
var items = list.Partition(2).Take(5).ToList();
Obviously, it would be better to then transform this into a stronger type, but even as is, you'd be able to extract the values (assuming 0 <= n <= items.Count) using:
var id = items[n].FirstOrDefault();
var date = items[n].ElementAtOrDefault(1);

Create grouping that includes missing keys

I have an IEnumerable<T> that has a Created field, which is a date.
There can be multiple T's per date and sometimes there are no T's for a given date.
Currently I'm grouping these by the date, which gives me all the dates that have at least one T, and the T's under them.
What I want though, is something I can use as part of a query that will get me all dates within a range, regardless of whether there are any T's with the given date.
Current Code:
var adjustments = DAL.GetAdjustmentsInDateRange(Start, End);
from adjustment in adjustments
group adjustment by adjustment.Created.Date into adjustmentsByDay
orderby adjustmentsByDay.Key descending
select ....
Here, adjustmentsByDay doesn't have all dates between Start and End. What I want is for it to include them, with no elements.
How can I do that?
You could left join adjustments with a list of all dates before grouping, like so:
var adjustments = DAL.GetAdjustmentsInDateRange(Start, End);
// Get all unique dates in time span
IEnumerable<DateTime> dates = GetAllDates(Start, End);
var query = (from date in dates
join adjustment in adjustments
on date.Date equals adjustment.Created.Date into a
from adjustment in a.DefaultIfEmpty()
select new {date.Date, adjustment}
).GroupBy(i=>i.Date).OrderBy(g=>g.Key);
I've put together a general-purpose LINQ-to-objects extension method to insert missing things into a sequence:
public static IEnumerable<T> InsertMissing<T, U>(this IEnumerable<T> source,
Func<T, U> key, Func<U, U> increment, Func<U, T> create)
{
bool first = true;
U last = default(U);
foreach (var ig in source)
{
U current = key(ig);
if (first)
{
first = false;
last = current;
yield return ig;
continue;
}
while (!(last = increment(last)).Equals(current))
{
yield return create(last);
}
yield return ig;
}
}
You'll also need a custom implementation of IGrouping:
class EmptyGrouping<K, E> : IGrouping<K, E> {
public K Key { get; set; }
public IEnumerator<E> GetEnumerator() {
return Enumerable.Empty<E>().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() {
return this.GetEnumerator();
}
}
Then you'll need to end your query after the orderby, follow it with this call, and then put your select afterwards:
var allGroups = query.InsertMissing(
// Key selector
g => g.Key,
// Get next desired key from current key
d => d.AddDays(-1),
// Create item for missing key
d => new EmptyGrouping<DateTime,YourAdjustmentType>{ Key = d });
This will go haywire if your keys aren't ordered or if one of them doesn't fall in the correct place (e.g. in your case, isn't on midnight).
This has the advantage of not needing multiple queries on the original source to determine the min/max values in order to generate a list of keys, and then a further query to join and get the data.

How to compare only date components from DateTime in EF?

I am having two date values, one already stored in the database and the other selected by the user using DatePicker. The use case is to search for a particular date from the database.
The value previously entered in the database always has time component of 12:00:00, where as the date entered from picker has different time component.
I am interested in only the date components and would like to ignore the time component.
What are the ways to do this comparison in C#?
Also, how to do this in LINQ?
UPDATE:
On LINQ to Entities, the following works fine.
e => DateTime.Compare(e.FirstDate.Value, SecondDate) >= 0
Use the class EntityFunctions for trimming the time portion.
using System.Data.Objects;
var bla = (from log in context.Contacts
where EntityFunctions.TruncateTime(log.ModifiedDate) == EntityFunctions.TruncateTime(today.Date)
select log).FirstOrDefault();
Source: http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/84d4e18b-7545-419b-9826-53ff1a0e2a62/
UPDATE
As of EF 6.0 and later EntityFunctions is replaced by DbFunctions.
NOTE: at the time of writing this answer, the EF-relation was unclear (that was edited into the question after this was written). For correct approach with EF, check Mandeeps answer.
You can use the DateTime.Date property to perform a date-only comparison.
DateTime a = GetFirstDate();
DateTime b = GetSecondDate();
if (a.Date.Equals(b.Date))
{
// the dates are equal
}
I think this could help you.
I made an extension since I have to compare dates in repositories filled with EF data and so .Date was not an option since it is not implemented in LinqToEntities translation.
Here is the code:
/// <summary>
/// Check if two dates are same
/// </summary>
/// <typeparam name="TElement">Type</typeparam>
/// <param name="valueSelector">date field</param>
/// <param name="value">date compared</param>
/// <returns>bool</returns>
public Expression<Func<TElement, bool>> IsSameDate<TElement>(Expression<Func<TElement, DateTime>> valueSelector, DateTime value)
{
ParameterExpression p = valueSelector.Parameters.Single();
var antes = Expression.GreaterThanOrEqual(valueSelector.Body, Expression.Constant(value.Date, typeof(DateTime)));
var despues = Expression.LessThan(valueSelector.Body, Expression.Constant(value.AddDays(1).Date, typeof(DateTime)));
Expression body = Expression.And(antes, despues);
return Expression.Lambda<Func<TElement, bool>>(body, p);
}
then you can use it in this way.
var today = DateTime.Now;
var todayPosts = from t in turnos.Where(IsSameDate<Turno>(t => t.MyDate, today))
select t);
If you use the Date property for DB Entities you will get exception:
"The specified type member 'Date' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported."
You can use something like this:
DateTime date = DateTime.Now.Date;
var result = from client in context.clients
where client.BirthDate >= date
&& client.BirthDate < date.AddDays(1)
select client;
To do it in LINQ to Entities, you have to use supported methods:
var year = someDate.Year;
var month = ...
var q = from r in Context.Records
where Microsoft.VisualBasic.DateAndTime.Year(r.SomeDate) == year
&& // month and day
Ugly, but it works, and it's done on the DB server.
Here's a different way to do it, but it's only useful if SecondDate is a variable you're passing in:
DateTime startDate = SecondDate.Date;
DateTime endDate = startDate.AddDays(1).AddTicks(-1);
...
e => e.FirstDate.Value >= startDate && e.FirstDate.Value <= endDate
I think that should work
You can also use this:
DbFunctions.DiffDays(date1, date2) == 0
you can use DbFunctions.TruncateTime() method for this.
e => DbFunctions.TruncateTime(e.FirstDate.Value) == DbFunctions.TruncateTime(SecondDate);
Just always compare the Date property of DateTime, instead of the full date time.
When you make your LINQ query, use date.Date in the query, ie:
var results = from c in collection
where c.Date == myDateTime.Date
select c;
This is how I do this.
DateTime date_time_to_compare = DateTime.Now;
//Compare only date parts
context.YourObject.FirstOrDefault(r =>
EntityFunctions.TruncateTime(r.date) == EntityFunctions.TruncateTime(date_to_compare));
//Note for Linq Users/Coders
This should give you the exact comparison for checking if a date falls within range when working with input from a user - date picker for example:
((DateTime)ri.RequestX.DateSatisfied).Date >= startdate.Date &&
((DateTime)ri.RequestX.DateSatisfied).Date <= enddate.Date
where startdate and enddate are values from a date picker.
Without time than try like this:
TimeSpan ts = new TimeSpan(23, 59, 59);
toDate = toDate.Add(ts);
List<AuditLog> resultLogs =
_dbContext.AuditLogs
.Where(al => al.Log_Date >= fromDate && al.Log_Date <= toDate)
.ToList();
return resultLogs;
You can user below link to compare 2 dates without time :
private bool DateGreaterOrEqual(DateTime dt1, DateTime dt2)
{
return DateTime.Compare(dt1.Date, dt2.Date) >= 0;
}
private bool DateLessOrEqual(DateTime dt1, DateTime dt2)
{
return DateTime.Compare(dt1.Date, dt2.Date) <= 0;
}
the Compare function return 3 different values: -1 0 1 which means dt1>dt2, dt1=dt2, dt1
Try this... It works fine to compare Date properties between two DateTimes type:
PS. It is a stopgap solution and a really bad practice, should never be used when you know that the database can bring thousands of records...
query = query.ToList()
.Where(x => x.FirstDate.Date == SecondDate.Date)
.AsQueryable();
I have resolved error using EfCore FromSqlRaw method.
var sql =
$"select * from \"ProgressBooks\" where date(\"Date\") = date('{today.Date.ToString("yyyy-MM-dd")}') and \"GroupId\" = {groupId}";
var todayProgressBook = _context.ProgressBooks.FromSqlRaw(sql).FirstOrDefault();

Categories

Resources