LINQ - how to get next three business days excluding dates in table? - linq

I need to find the next three available business days for a scheduling application. What is available depends upon excluded dates in a table. So...as long as the date is not in my table and not a Saturday or Sunday, I want the next three. I'd like to find an efficient way of doing this.
I need to return a List<DateTime>. The table is simple - ExcludedDates has an ID and a DateTime with the excluded date.
I'd like to have a single LINQ query expression but can't figure it out...thanks to all in advance and I apologize if this is trivial or obvious - it isn't to me.

Try this...
DateTime start = DateTime.Now.Date;
var result = Enumerable.Range(1, 10) // make this '10' higher if necessary (I assume you only exclude non-workingdays like Christmas and Easter)
.Select(offset => start.AddDays(offset))
.Where(date => !( date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek== DayOfWeek.Sunday))
.Where(d=> !exceptionTable.Any(date => date == d))
.Take(3).ToList();

List<DateTime> result = (from i in Enumerable.Range(1, excludeTable.Rows.Count + 6)
let date = inputDate.AddDays(i)
where date.DayOfWeek != DayOfWeek.Saturday &&
date.DayOfWeek != DayOfWeek.Sunday &&
!excludeTable.Rows.Cast<DataRow>().Select(r => (DateTime) r["ExcludeDate"]).Contains(date)
select date).Take(3).ToList();
excludeTable.Rows.Count + 6 is to cover the worst case where you skip over every thing in the excludeTable and then you have to skip over another weekend.

This assumes that a month will be reasonable depending on your excluded dates.
DateTime date = DateTime.Today;
// first generate all dates in the month of 'date'
var dates = Enumerable.Range(1, DateTime.DaysInMonth(date.Year, date.Month)).Select(n => new DateTime(date.Year, date.Month, n));
// then filter the only the start of weeks
var results = (from d in dates
where d.DayOfWeek != DayOfWeek.Saturday && d.DayOfWeek != DayOfWeek.Saturday && !excludes.Any(i => i.DateTime.Date == d.Date) && date < d
select d).Take(3);

var excludedList = new List<long>() { DateTime.Parse("2011-07-27").Ticks };
var week = new List<long>(){
DateTime.Now.Date.Ticks,
DateTime.Now.Date.AddDays(1).Ticks,
DateTime.Now.Date.AddDays(2).Ticks,
DateTime.Now.Date.AddDays(3).Ticks,
DateTime.Now.Date.AddDays(4).Ticks,
DateTime.Now.Date.AddDays(5).Ticks,
DateTime.Now.Date.AddDays(6).Ticks,
DateTime.Now.Date.AddDays(7).Ticks,
DateTime.Now.Date.AddDays(8).Ticks
};
var available = (from d in week.Except(excludedList)
where new DateTime(d).DayOfWeek != DayOfWeek.Saturday && new DateTime(d).DayOfWeek != DayOfWeek.Sunday
select new DateTime(d)).Take(3);
foreach (var a in available)
Console.WriteLine(a.ToString());

Related

How can i optimize my entity framework query for better performance and more readability

There is a heavy page in my site called HotelDetail, that several things related to hotel will be load in this page. hotel, hotel's services,destinations of places to hotel,user's comments,hotel's room with their Services, prices and capacity.
All of this will be load in one time with below code, now I want to optimize this code, what's solution to optimize this for better performance and more readability ? is there a way to not load all of then in one time?
public PlaceViewModel GetPlaceDetail(string languageKey,string catKey , string placeKey, SearchViewModel searchOptions)
{
DateTime startDate = searchOptions.CheckIn.ToMiladiDateTime();
DateTime endDate = searchOptions.CheckOut.ToMiladiDateTime();
int nights = (int)endDate.Subtract(startDate).TotalDays;
placeViewModel.NightCount = nights;
var query = (from r in _unitOfWork.RoomServiceRepository.Get()
join
a in _unitOfWork.InventoryRepository.Get()
on r equals a.RoomService
where r.Room.Place.Id == place.Id && !r.SoftDelete && !r.Room.SoftDelete &&
a.Date >= startDate && a.Date < endDate && (a.CertainAvailability + a.FloatAvailability) > 0
select new { a, MaxCapacity = r.Room.Capacity + r.ExtraCapacity, r.RoomId });
placeViewModel.HasAvailability = query.ToList().Count != 0;
var grp = query.GroupBy(y => y.a.RoomServiceId)
.Select(z => new {
key = z.Key,
count = z.Count(),
minAvail = z.Min(ax => ax.a.CertainAvailability + ax.a.FloatAvailability),
minDate = z.Min(y => y.a.Date),
minPrice = z.Min(ax=>ax.a.Price),
price = z.Average(ax=>ax.a.Price) });
var tmp = query.Where(x => grp.Any(q =>
q.key == x.a.RoomServiceId &&
q.count == nights &&
q.minDate == x.a.Date)).ToList();
foreach (var item in tmp)
{
var roomViewModel = placeViewModel.Rooms.FirstOrDefault(x => x.Id == item.RoomId);
var avail = new AvailabilityViewModel(item.a, item.MaxCapacity);
avail.Availability = grp.FirstOrDefault(x => x.key == item.a.RoomServiceId).minAvail;
var otherInfos = grp.SingleOrDefault(x => x.key == item.a.RoomServiceId);
avail.JabamaPrice = otherInfos.price;
avail.JabamaMinPriceInPeriod = otherInfos.minPrice;
roomViewModel.Availabilites.Add(avail);
if (maxDiscount == null || (maxDiscount != null &&(maxDiscount.BoardPrice - maxDiscount.JabamaPrice) < (avail.BoardPrice - avail.JabamaPrice)))
maxDiscount = avail;
if (minPrice == null || (minPrice != null && avail.JabamaPrice > 0 && minPrice.JabamaPrice > avail.JabamaPrice))
minPrice = avail;
}
var discountQuery = tmp.Where(x => x.a.RoomService.ExtraCapacity == 0 && x.a.Date == minPrice.Date);
try
{
if (discountQuery.Any())
maxDiscountOfMinPercentageDay = tmp != null && minPrice != null ? discountQuery.Max(x => (x.a.BoardPrice - x.a.Price) / x.a.BoardPrice * 100) : 0;
else
maxDiscountOfMinPercentageDay = 0;
}
catch (Exception)
{
maxDiscountOfMinPercentageDay = 0;
}
foreach (var roomVM in placeViewModel.Rooms)
{
if (roomVM.Availabilites.Count() == 0)
roomVM.Availabilites.Add(new AvailabilityViewModel(-1));
}
}
I'm far from familiar with Linq but I do know some of the pitfalls of SQL. One of the things I notice is that you (and many other people!) use .Count != 0 to figure out if there might or might not be a matching record. In SQL this is a very inefficient approach as the system will effectively go over the entire table to find and count the records that match the prerequisite. In such cases I'd advice to use a WHERE EXISTS() construction; I believe Linq has something along the lines of .Any() that works similarly.
This is also what you do in real life: when someone asks you if there is any cheese left in the fridge, you will not start counting all types of cheese you can find in there but rather stop after the first type of cheese you see and answer 'Yes there is'; there is no added value to going through the entire fridge, it would only cost extra time.
PS: This probably also is true for roomVM.Availabilites.Count() == 0 which might be better handled with inversed logic...
Disclaimer: it could be that the EntityFwk is smart enough to optimize this for you in the background... I don't know; I very much doubt it but like I said, I'm no specialist. I'd suggest to start by first figuring out how long each part of your current code takes and then optimize the slowest ones first. Having a baseline also gives you a better idea if your changes are having any effect, be it positive or negative.

multiple group by using linq

I need return just 2 lines in my query. One line with a string Today and a number of cases closed today, on my second line I need a string Last Week and a number of cases closed on the last week.
How I group with a range date?
Sum Name
----------- ----------
12 Today
33 Last Weeb
How about this:
var caseCounts = Cases
.Where(c => c.Date == today || (c.Date >= startOfLastWeek && c.Date <= endOfLastWeek))
.GroupBy(c => c.Date == today ? "Today" : "Last Week")
.Select(g => new {
Name = g.Key, Sum = g.Count()
});
You would need to define the 3 dates (today, startOfLastWeek, endOfLastWeek) before hand, but it gives you the results you are after.
GROUP BY YEARWEEK(date) should work. Depending on your dbms, you might be able to use another function, or program your own.
http://www.tutorialspoint.com/sql/sql-date-functions.htm#function_yearweek

Restrict records returned from nested LINQ statement

how would I restrict the "Charges" returned in this linq query, to a specified date range:
var dte = DateTime.Parse("2012-01-01");
var dte2 = DateTime.Parse("2012-02-01");
var meetingrooms = tblMeetingRoom
.Where(r => r.building_id==1)
.GroupBy(p => p.tblType)
.Select(g => new
{
TypeName = g.Key.room_type,
TypeID = g.Key.type_id,
TypeCount = g.Count(),
charges =
from rt in charges
where (rt.type_id == g.Key.type_id)
select new {
rt.chargedate,
rt.people,
rt.charge
}
});
meetingrooms.Dump();
I think it needs to go inbetween here somehwhere:
from rt in charges
where (rt.type_id == g.Key.type_id) (EG) && rt.chargedate>=dte and rt.chargedate <dte2
select new {
Thanks for any help,
Mark
from rt in charges
where rt.type_id == g.Key.type_id && (rt.chargedate >= dte && rt.chargedate <= dte2)
select new {
.....
#Maarten - I've been trying this for ages - LinqPad kept giving errors, so I was trying all different ways I could think of - for whatever reason, I must have made some typos, that I didn't make above!
Sorry for wasting everyone's time - the above works "as-is"!
Mark

How to get all the birthdays of today?

Does anyone know how to make a Linq query that gets all the birthdays of today? The code below doesn't work :
var getBirthdays =
orgContext.CreateQuery<Contact>()
.Where(c => c.BirthDate != null
&& c.BirthDate.Value.Month == DateTime.Now.Month).ToList();
I get an error like this:
"Invalid 'where' condition. An entity member is invoking an invalid
property or method."
Thanks in advance!
Anytime a vendor writes a four part blog series on how to do something as simple as finding a birthday (as Microsoft did in 2007), you have to know this won't be simple. So far as I can tell, this hasn't updated since then.
Find contacts with upcoming birthdays
Find contacts with upcoming birthdays - Part 2
Find contacts with upcoming birthdays - Parts 3 and 4
So you have limited options:
Make new fields called something like new_birthmonth and new_birthday that's updated every time a contact is created or updated via a plugin, and then query on those int fields.
Using Dynamic Linq, construct an OR clause in your WHERE clause that checks to see if the birthday falls in a reasonable range of years (say, 140 for the long-livers) (code below).
List<string> birthdays = new List<string>(); //will contain list of OR clauses
//makes sure no CRM unsupported dates are passed (less than 1/1/1900)
for (int i = Math.Min(140, DateTime.Today.Year - 1900); i > -1; i--)
{
//adds a different date per year
birthdays.Add
(
string.Format
(
//DateTimes are stored in UTC
"BirthDate = DateTime.Parse(\"{0}\")",
DateTime.Today.ToUniversalTime().AddYears(-i)
)
);
}
//completes the correct dynamic linq OR clause
string birthdayList = string.Join(" OR ", birthdays);
var getBirthdays = orgContext.CreateQuery<Xrm.Contact>()
.Where(c => c.BirthDate != null)
.Where(birthdayList)
.ToList();
I solved my problem based on the example of "Peter Majeed" and using "LinqKit"!
var predicate = PredicateBuilder.False<Contact>();
for (int i = Math.Min(140, DateTime.Today.Year - 1900); i > -1; i--)
{
DateTime cleanDateTime = new DateTime(DateTime.Today.AddYears(-i).Year, DateTime.Today.AddYears(-1).Month, DateTime.Today.AddYears(-i).Day);
predicate = predicate.Or(p => p.BirthDate == cleanDateTime.ToUniversalTime());
}
var getBirthdays = (from c in orgContext.CreateQuery<Contact>().AsExpandable().Where(predicate)
select c).ToList();
The above query gave me the correct result! Thx to all who helped me!
If c.BirthDate is nullable, you have to convert it to a datetime first:
var getBirthdays = orgContext.CreateQuery<Contact>()
.Where(c => c.BirthDate != null &&
(Convert.ToDateTime(c.BirthDate).Month ==
DateTime.Now.Month) &&
Convert.ToDateTime(c.BirthDate).Day ==
DateTime.Now.Day))
.ToList();
You could fetch this info with a Query, if that is possible in your situation?
//set up the condition + filter
var ce = new Microsoft.Xrm.Sdk.Query.ConditionExpression();
ce.Operator = Microsoft.Xrm.Sdk.Query.ConditionOperator.LastXDays;
ce.AttributeName = "birthdate";
ce.Values.Add(30);
var fe = new Microsoft.Xrm.Sdk.Query.FilterExpression();
fe.AddCondition(ce);
//build query
var query = new Microsoft.Xrm.Sdk.Query.QueryExpression();
query.EntityName = "contact";
query.Criteria.AddFilter(fe);
//get results
var results = CrmHelperV5.OrgProxy.RetrieveMultiple(query);
//if you want early bound entities, convert here.
var contacts = new List<Contact>();
foreach(var result in results.Entities)
{
contacts.Add(result.ToEntity<Contact>());
}
You may want to investigate the other operators for the filters + conditions
You can use QueryExpression (it works for Microsoft CRM Plugin)
public EntityCollection getBirthdateList(IOrganizationService orgsService)
{
List<string> birthdays = new List<string>();
//makes sure no CRM unsupported dates are passed (less than 1/1/1900)
for (int i = Math.Min(140, DateTime.Today.Year - 1930); i > -1; i--)
{
//adds a different date per year
birthdays.Add
(
DateTime.Now.AddYears(-i).ToString("yyyy-MM-dd")
);
}
// Instantiate QueryExpression
var query = new QueryExpression("contact");
// Define filter QEquote.Criteria
var queryfilter = new FilterExpression();
query.Criteria.AddFilter(queryfilter);
// Define filter
queryfilter.FilterOperator = LogicalOperator.Or;
queryfilter.AddCondition("birthdate",ConditionOperator.In,birthdays.ToArray());
return orgsService.RetrieveMultiple(query); ;
}

In Linq2SQL, how do I get a record plus the previous and next in the sequence in a single query?

Given a date, what is the most efficient way to query the last record before that date, any record that equals that date, and the next one after that date.
It should be functionally equivalent to a query like this:
from asset in Assets
where asset.Id == assetId
select new {
Previous = (from a in a.Orders where a.Date < myDate orderby a.Date descending select a).FirstOrDefault(),
Current = (from a in a.Orders where a.Date == myDate select a).SingleOrDefault(),
Next = (from a in a.Orders where a.Date > myDate orderby a.Date select a).FirstOrDefault()
}
As is, this query runs three queries, and presumably has to sort the dataset by myDate three times to do it.
Some similar questions:
How do I get 5 records before AND after a record with a specific ID? (just uses two queries)
How do I get records before and after given one? Not in Linq, and therefore hard for me to take advantage of (my team will get annoyed).
To provide the "most efficient" query depends on what you mean by efficient.
If you want a single query to the database, a single sort of orders by date and finally fast look-ups by date then I suggest the following might be the most efficient. :-)
var orders =
(from a in Assets
where a.Id == assetId
from o in a.Orders
orderby o.Date
select o).ToArray();
var previous = orders.LastOrDefault(o => o.Date < myDate);
var current = orders.SingleOrDefault(o => o.Date == myDate);
var next = orders.FirstOrDefault(o => o.Date > myDate);
This should query the database once for the orders associated with the required asset Id, sort them by date, and return them as an array in memory. Since this is in memory it is now blindingly fast to look for the current, previous & next records for the specified date.
Does your Orders table have a sequential ID field? If so, you might be able to do it with:
from asset in Assets
where asset.Id == assetID
let current = asset.Orders.Where(x => x.Date == myDate).FirstOrDefault()
where current != null
let previous = asset.Orders.Where(x => x.id == current.id - 1).FirstOrDefault()
let next = asset.Orders.Where(x => x.id == current.id + 1).FirstOrDefault()
select new {
Previous = previous,
Current = current,
Next = next
};
If it doesn't, then it'd be a bit more code:
from asset in Assets
where asset.Id == assetID
let current = asset.Orders.Where(x => x.Date == myDate).FirstOrDefault()
where current != null
let previous = asset.Orders.Where(x => x.Date < current.Date).OrderByDescending(x => x.Date).FirstOrDefault()
let next = asset.Orders.Where(x => x.Date > current.Date).OrderBy(x => x.Date).FirstOrDefault()
select new {
Previous = previous,
Current = current,
Next = next
};
That should get compiled into a single SQL query that utilizes sub-queries. IE: the database server will execute multiple queries, but your client program is only submitting one.
Edit One other idea that would work if your Order table had sequential IDs:
var sample = (from asset in Assets
where asset.Id == assetID
let current = asset.Orders.Where(x => x.Date == myDate).FirstOrDefault()
where current != null
from order in asset.Orders
where order.Id == current.id - 1
select order)
.Take(3)
.ToArray();
var Previous = sample[0];
var Current = sample[1];
var Next = sample[2];
Other Answers, for example, SkipWhile etc. very very slow. Good luck ^^
//Current Record
var query
= (from item in db.Employee
where item.UserName.Equals(_username)
select item).SingleOrDefault();
//Next Record
var query
= (from item in db.Employee
where item.UserName.CompareTo(_username) > 0
select item).FirstOrDefault();
//Previous Record
var query
= (from item in db.Employee
where item.UserName.CompareTo(_username) < 0
orderby item.UserName Descending
select item).FirstOrDefault();
Almost the same, but the SQL query plan might be different.
var q =
from asset in Assets
where asset.Id == assetID
select new
{
Previous = asset.Orders.where(a => a.Date == asset.Orders.Where(x => x.Date < myDate).Max(x => x.Date)).FirstOrDefault(),
Current = asset.Orders.Where(x => x.Date == myDate).FirstOrDefault(),
Next = asset.Orders.where(a => a.Date == asset.Orders.Where(x => x.Date > myDate).Min(x => x.Date)).FirstOrDefault()
};

Resources