Using T-SQL DATEFROMPARTS() in C# Linq - c#

I have the following T-SQL query which I'm trying to convert into C# code:
SELECT
*, DATEFROMPARTS(Jaar, Maand, 1)
FROM
EAN_Budget_Meetdata_Factuur
WHERE
EID = 1 AND
Bron = 'B' AND
DATEFROMPARTS(Jaar, Maand, 1) BETWEEN '20161101' AND '20170101'
The table contains a column Year and Month, and I'm querying the table based on two known DateTimes (a period).
The query is meant to retrieve the table rows that apply to this period based on the years and months between the two DateTimes.
What is the best way to convert this query into C#? I've so far come up with the following, without success:
var query = from b in CT2.EAN_Budget_Meetdata_Factuur
let JaarTest = b.Jaar + "-" + b.Maand + "-01"
where
b.EID == 1 &&
b.Bron == "B" &&
JaarTest == "something"
select new
{
JaarTest = b.Jaar + "-" + b.Maand + "-01"
};

If you're using DATEFROMPARTS on table columns you've already lost any opportunity for indexes to be useful on the database side - so I'd instead recommend working in total months.1
(year * 12) + months is a simple calculation that you can perform both with the table columns and whatever start/end points you want to query for and you then can perform simple int range checks.
1In the alternative, if you hadn't realised that this was going to deny you any indexing ability, you might want to consider adding a computed column on which you apply an index - in which case the column definition would use the function (either the total months calculation I give here or the original DATEFROMPARTS one) and you'd then query that column easily.

Try following :
class Program
{
static void Main(string[] args)
{
var query = EAN_Budget_Meetdata_Factuur.factuurs
.Where(x => (x.eid == 1) && (x.bron == "B") && (x.date >= new DateTime(2016, 11, 1)) && (x.date <= new DateTime(2017, 1, 1)))
.Select(x => new { eid = x.eid, bron = x.bron, date = x.date })
.ToList();
}
}
public class EAN_Budget_Meetdata_Factuur
{
public static List<EAN_Budget_Meetdata_Factuur> factuurs = new List<EAN_Budget_Meetdata_Factuur>();
public int eid { get; set; }
public string bron { get; set; }
public DateTime date { get; set; }
}

Related

where to put orderby in linq statement

Here is my code and i want to order by via date_added column. i tried all the possibilities but still the date_added column sorted via month instead of a year. Please guide where i need to put orderby statement.further the date_added return result in string datatype.
{
var records = (from r in db2.documents
select new
{
r.show_in_portal,
r.buyer_id,
r.advertiser_id,
r.contract_id,
r.campaign_id,
date_added = Dates.FormatDateToExt(r.date_added),
id = r.document_id,
name = r.filename,
location = r.filename,
r.publisher_id,
affiliate_id = (r.contract != null ? r.contract.publisher_id : -1),
document_type = r.document_type.type_name
});
if (campaign_id > 0)
records = records.Where(v => v.campaign_id == campaign_id);
//if (creativeid > 0)
// records = records.Where(v => v.id == creativeid);
if (affid > 0)
records = records.Where(v => v.publisher_id == affid);
if (contid > 0)
records = records.Where(v => v.contract_id == contid);
if (advertiserid > 0)
records = records.Where(v => v.advertiser_id == advertiserid);
if (buyerid > 0)
records = records.Where(v => v.buyer_id == buyerid);
GridOut(context, records.ToArray());
}
public static string FormatDateToExt(DateTime? input)
{
return FormatDateToExt(input, 0);
}
public static string FormatDateToExt(DateTime? input, int time_offset = 0)
{
return input != null ? input.Value.AddHours(-1 * time_offset).ToString("MM/dd/yyy h:mm:ss tt") : "";
}
The result of your query is a sequence of some anonymous type. Date_Added is one of the properties of this anonymous type, so after you created your query you can order by Date_Added.
The type of Date_Added is the returned type of Dates.FormatDateToExt(...). Alas you forgot to inform us about this type. Is it a DateTime? Is it a string? If you order by this type do you get the sorting order that you want?
If so, just add the OrderBy at the end:
var records = db2.documents.Select(document => new
{
Id = document.document_id,
Portal = document.Show,
BuyerId = document.buyer_id,
AdvertiserId = document.advertiser_id,
...
DateAdded = Dates.FormatDateToExt(document.date_added),
});
if (campaign_id > 0)
records = records.Where(record => record.campaign_id == campaign_id);
if (affid > 0)
records = records.Where(record => record.publisher_id == affid);
... // etc. other ifs and other wheres
records = records.OrderBy(record => record.DateAdded);
It is a good thing to do the Sorting at the end, because this means that you will have to sort fewer records. All records that don't pass all Wheres, won't have to be sorted.
Finally a small hint: did you see, that if you use proper identifiers, that your queries will be easier to read? It is good practice to use plural nouns for collections of items, and singular nouns for elements of the collection:
var novels = dbContext.Books.Where(book => book.Type == BookType.Novel)
Consider making your dates uniform by using ISO 8601 ones (convert them on the fly in your Linq query), as they're made to be sortable.
You can put the orderby clause after the from.
Read:
https://www.c-sharpcorner.com/UploadFile/mahesh/working-with-datetime-using-C-Sharp/
https://dev.to/adnauseum/sorting-iso-8601-timestamps-5am2

Linq Overlapped date range checking in single collection

Class TimeRange{
private DateTime StartDate{get; set;}
private DateTime EndDate{get; set;}
}
List<TimeRange> TimeRangeList = new List<TimeRange>(){
new TimeRange(){StartDate = new DateTime(2050, 1, 1),
EndDate = new DateTime(2050, 1, 10)},
new TimeRange(){StartDate = new DateTime(2050, 2, 1),
EndDate = new DateTime(2050, 2, 10)},
//This item will triggered the overlap validation failed
new TimeRange(){StartDate = new DateTime(2050, 1, 5),
EndDate = new DateTime(2050, 1, 9)},
},
}
so after I checked out the similar topic, I still can't figured out the algorithm of checking the overlapped date range.
This is quite simple in SQL, according to Checking for date overlap across multiple date range objects
I just need to compare two date range like this
SELECT COUNT(*)
FROM Table1
WHERE Table1.StartDate < 'endCheckDate'
AND Table1.EndDate > 'startCheckDate'
I found it is difficult to do in Linq, how do we compare all items in one collection within? of cause we can use foreach in just loop the collection just like comparing two list, but how is it work in select?
actually I'm doing something like this
for (int i = 0; i < TimeRangeList .Count(); ++i)
{
var item = TimeRangeList[i];
for (int y = i + 1; y < TimeRangeList.Count(); ++y)
{
var item2 = TimeRangeList[y];
if (IsOverLapped(item, item2))
{
// this is overlapped
};
}
}
private bool IsOverLapped(dynamic firstObj, dynamic secondObj)
{
return secondObj.StartDate <= firstObj.EndDate && firstObj.StartDate <= secondObj.EndDate;
}
Is there a more elegant way to do without looping?
so my questions is how do we compare one single list for each items itself by linq?
A simple brute force idea:
bool overlap = TimeRangeList
.Any(r => TimeRangeList
.Where(q => q != r)
.Any(q => q.EndDate >= r.StartDate && q.StartDate <= r.EndDate) );
If I look at your SQLcode, it seems that you have a Table1 object which is a sequence of similar objects, let's say of class Table1Row. Every Table1Row has at least two DateTime properties, a StartDate and an EndDate. Furthermore you have two DateTime objects: startCheckDate and endCheckDate.
You want to count all elements in your Table1 that have a StartDate smaller than startCheckDate and an EndDate larger than endCheckDate
Written as an extension function of IQueryable:
public static int CountOverlapping(this IQueryable<Table1Row> table1,
DateTime startCheckDate,
DateTime endCheckDate)
{
return table1
.Where (row => row.StartDate < startCheckDate && row.EndDate > endCheckDate)
.Count();
}
Usage:
DateTime startCheckDate = ...
DateTime endCheckDate = ...
IQueryable<Table1Row> table1 = ...
int nrOfOverlapping = table1.CountOverlapping(startCheckDate, endCheckDate);
Simple comme bonjour?

Finding lowest price for overlapping date ranges - C# algorithm

There are prices set for certain time periods... I'm having trouble coming up with an algorithm to determine the lowest price for a specific time period.
I'm doing this with a list of objects, where the object has properties DateTime StartDate, DateTime EndDate, decimal Price.
For example, two price sets and their active date ranges:
A. 09/26/16 - 12/31/17 at $20.00
B. 12/01/16 - 12/31/16 at $18.00
You can see that B is inside the A time period and is lower.
I need that converted to this:
A. 09/26/16 - 11/30/16 at $20.00
B. 12/01/16 - 12/31/16 at $18.00
C. 01/01/17 - 12/31/17 at $20.00
It has to work for any number of date ranges and combinations. Has anyone come across anything I can manipulate to get the result I need? Or any suggestions?
Edit: My data structure:
public class PromoResult
{
public int ItemId { get; set; }
public decimal PromoPrice { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public int PromoType { get; set; } // can ignore this...
}
This is a great case for using Linq. Assuming your price range object is called PriceRecord...
You will need to create a list of all dates and then filter down to price records that are between two consecutive dates. An implementation might look something like this:
public static IEnumerable<PriceRecord> ReduceOverlaps(IEnumerable<PriceRecord> source)
{
// Get a list of all edges of date ranges
// edit, added OrderBy (!)
var edges = source.SelectMany(record => new[] { record.StartDate, record.EndDate }).OrderBy(d => d).ToArray();
// iterate over pairs of edges (i and i-1)
for (int i = 1; i < edges.Length; i++)
{
// select min price for range i-1, i
var price = source.Where(r => r.StartDate <= edges[i - 1] && r.EndDate >= edges[i]).Select(r => r.Price).Min();
// return a new record from i-1, i with price
yield return new PriceRecord() { StartDate = edges[i - 1], EndDate = edges[i], Price = price };
}
}
I haven't tested this and you may need to tinker with the comparison operators, but it may be a good starting point.
I have now tested the code, the example here works with the data in the question.
Feel free to propose edits to improve this example.
I will use 2 functions DateRange and GroupSequenceWhile
List<PromoResult> promoResult = new List<PromoResult>()
{
new PromoResult() { PromoPrice=20, StartDate = new DateTime(2016, 9, 26),EndDate=new DateTime(2017, 12, 31)},
new PromoResult() { PromoPrice=18, StartDate = new DateTime(2016, 12, 1),EndDate=new DateTime(2016, 12, 31)}
};
var result = promoResult.SelectMany(x => DateRange(x.StartDate, x.EndDate, TimeSpan.FromDays(1))
.Select(y => new { promo = x, date = y }))
.GroupBy(x => x.date).Select(x => x.OrderBy(y => y.promo.PromoPrice).First())
.OrderBy(x=>x.date)
.ToList();
var final = result.GroupSequenceWhile((x, y) => x.promo.PromoPrice == y.promo.PromoPrice)
.Select(g => new { start = g.First().date, end = g.Last().date, price = g.First().promo.PromoPrice })
.ToList();
foreach (var r in final)
{
Console.WriteLine(r.price + "$ " + r.start.ToString("MM/dd/yy", CultureInfo.InvariantCulture) + " " + r.end.ToString("MM/dd/yy", CultureInfo.InvariantCulture));
}
OUTPUT:
20$ 09/26/16 11/30/16
18$ 12/01/16 12/31/16
20$ 01/01/17 12/31/17
Algorithm:
1- create a <day,price> tuple for each item in promoResult list
2- group this tuples by day and select min price
3- order this tuples by date
4- select the starting and ending day when there is a change in price in consecutive days
IEnumerable<DateTime> DateRange(DateTime start, DateTime end, TimeSpan period)
{
for (var dt = start; dt <= end; dt = dt.Add(period))
{
yield return dt;
}
}
public static IEnumerable<IEnumerable<T>> GroupSequenceWhile<T>(this IEnumerable<T> seq, Func<T, T, bool> condition)
{
List<T> list = new List<T>();
using (var en = seq.GetEnumerator())
{
if (en.MoveNext())
{
var prev = en.Current;
list.Add(en.Current);
while (en.MoveNext())
{
if (condition(prev, en.Current))
{
list.Add(en.Current);
}
else
{
yield return list;
list = new List<T>();
list.Add(en.Current);
}
prev = en.Current;
}
if (list.Any())
yield return list;
}
}
}
Doesn't directly answer your question, but here is some SQL that I used to solve a similar problem I had (simplified down a bit, as I was also dealing with multiple locations and different price types):
SELECT RI.ItemNmbr, RI.UnitPrice, RI.CasePrice
, RP.ProgramID
, Row_Number() OVER (PARTITION BY RI.ItemNmbr,
ORDER BY CASE WHEN RI.UnitPrice > 0
THEN RI.UnitPrice
ELSE 1000000 END ASC
, CASE WHEN RI.CasePrice > 0
THEN RI.CasePrice
ELSE 1000000 END ASC
, RP.EndDate DESC
, RP.BeginDate ASC
, RP.ProgramID ASC) AS RowNumBtl
, Row_Number() OVER (PARTITION BY RI.UnitPrice,
ORDER BY CASE WHEN RI.CasePrice > 0
THEN RI.CasePrice
ELSE 1000000 END ASC
, CASE WHEN RI.UnitPrice > 0
THEN RI.UnitPrice
ELSE 1000000 END ASC
, RP.EndDate DESC
, RP.BeginDate ASC
, RP.ProgramID ASC) AS RowNumCase
FROM RetailPriceProgramItem AS RI
INNER JOIN RetailPriceMaster AS RP
ON RP.ProgramType = RI.ProgramType AND RP.ProgramID = RI.ProgramID
WHERE RP.ProgramType='S'
AND RP.BeginDate <= #date AND RP.EndDate >= #date
AND RI.Active=1
I select from that where RowNumBtl=1 for the UnitPrice and RowNumCase=1 for the CasePrice. If you then create a table of dates (which you can do using a CTE), you can cross apply on each date. This is a bit inefficient, since you only need to test at border conditions between date ranges, so... good luck with that.
I would start with the ranges in date order based on starting date, add the first entry as a range in its entirety so:
09/26/16 - 12/31/17 at $20.00
TBD:
12/01/16 - 12/31/16 at $18.00
Next grab the next range you have, if it overlaps with the previous one, split the overlap (there are few kinds of overlaps, make sure to handle them all) taking the minimum value for the overlapped region:
09/26/16 - 11/30/16 at $20.00
12/01/16 - 12/31/16 at $18.00
TBD:
01/01/17 - 12/31/17 at $20.00
Note that you don't have the last one yet as you would take any splits that occur after and put them back into your sorted list of "yet to be compared" items.
Try this
lets say we have:
public class DatePrice
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public decimal Price { get; set; }
}
and
IList<DatePrice> list = new List<DatePrice>(); // populate your data from the source..
var lowestPriceItem = list.OrderBy(item => item.Price).First();
should give you the lowest price item.

Spliting up long linq queries for improved maintainability

I have a bunch of these Tasks that are all based on LINQ queries. I am looking for good way to refactor them and make them easier to read and allow me to change the queries depending on language/region etc.
var mailTaskOne = CreateTask(() => myService.Mail.Where(p => p.ProjectName == "Delta"
&& (p.MailLang== (int)MailLanguage.EU || p.MailLang == (int)MailLanguage.RU)
&& (p.DateEntered >= startDate && p.DateEntered <= endDate)
&& p.MailPriority == (int)MailPriority.High).Count());
One of the ways I thought would be convenient would be to split the query up into something like this.
var results = myService.Mail.Where(x => x.ProjectName == "Delta");
results = results.Where(p => p.MailLang== (int)MailLanguage.EU);
results = results.Where(p => p.DateModified >= startDate && p.DateModified <= endDate);
This would allow me to do this without having to repeat the whole query for each region.
if (MailLanguage == "English")
results = results.Where(p => p.MailLang== (int)MailLanguage.EU);
else
results = results.Where(p => p.MailLang== (int)MailLanguage.RU);
Is there anyone that knows a better solution for this? I end up having huge functions as I need to do maybe 20 of these queries depending on the requirements; such as Region, Project name etc.
Edit:
Due to some limitations I did not know of with the back-end (web service/api) I could unfortunately not use some of the awesome answers mentioned in this question.
For example this does not get translated properly, but in no ways because the answer incorrect, simply does not work with the API I am working against -- possibly because it is poorly implemented.
public bool IsValid(Type x)
{
return (x.a == b) && (x.c ==d) && (x.d == e);
}
Anyway, anyone looking for similar solutions all of these are valid answers, but in the end I ended up going with something similar to the solution snurre provided.
I would go with just splitting up the query onto different lines like you suggested, it means you can put comments per line to describe what it is doing. You are still only making 1 trip to the database so you aren't losing anything in terms of performance but gaining better readability.
Why not simply have a method for the purpose?
public static IQueryable<Mail> Count(this IQueryable<Mail> mails,
string projectName,
MailLanguage mailLanguage,
DateTime startDate,
DateTime endDate) {
return mails.Count(p=>
p.ProjectName == projectName
&& p.MailLang == mailLanguage
&& p.DateEntered >= startDate
&& p.DateEntered <= endDate
&& p.MailPriority == (int)MailPriority.High);
}
then you can simply use it like this
CreateTask(() => myService.Mail.Count("Delta",MailLanguage.EU,startDate,endDate));
You could turn project name, data modified, mail language and any other criteria into variables and guive them the value you want based on any condition. Then your query would use the variables not the literal values.
var projectName="Delta";
var mailLanguage=(int)MailLanguage.RU;
var results=myService.Mail.Where(x => x.ProjectName == projectName)
&& (p.MailLang== mailLanguage);
That way you can put most of the complexity in giving the values to the variables and the linq query would be easier to read and mantain.
You could create a parameter class like:
public class MailParameters
{
public DateTime EndTime { get; private set; }
public IEnumerable<int> Languages { get; private set; }
public int Priority { get; private set; }
public string ProjectName { get; private set; }
public DateTime StartTime { get; private set; }
public MailParameters(string projectName, DateTime startTime, DateTime endTime, MailLang language, Priority priority)
: this(projectName, startTime, endTime, new[] { language }, priority)
public MailParameters(string projectName, DateTime startTime, DateTime endTime, IEnumerable<MailLang> languages, Priority priority)
{
ProjectName = projectName;
StartTime = startTime;
EndTime = endTime;
Languages = languages.Cast<int>();
Priority = (int)priority;
}
}
Then add these extension methods:
public static int Count(this IQueryable<Mail> mails, MailCountParameter p)
{
return mails.Count(m =>
m.ProjectName == p.ProjectName &&
p.Languages.Contains(m.MailLang) &&
m.EnteredBetween(p.StartTime, p.EndTime) &&
m.Priority == p.Priority);
}
public static bool EnteredBetween(this Mail mail, DateTime startTime, DateTime endTime)
{
return mail.DateEntered >= startTime && mail.DateEntered <= endTime;
}
The usage would then be:
var mailParametersOne = new MailParameters("Delta", startDate, endDate, new[] { MailLang.EU, MailLang.RU }, MailPriority.High);
var mailTaskOne = CreateTask(() => myService.Mail.Count(mailParametersOne));
Consider moving the complex comparisons into a function. For exanple, instead of
Results.Where(x => (x.a == b) && (x.c == d) && (x.d == e))
consider
Results.Where(x => IsValid(x))
...
public bool IsValid(Type x)
{
return (x.a == b) && (x.c ==d) && (x.d == e);
}
The code becomes more readable and IsValid is easy to test using an automated testing framework.
My final solution is based on an article by ScottGu.
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
I build the LINQ query like this.
var linqStatements = new List<String>();
linqStatements.Add(parser.StringToLinqQuery<Project>("ProjectId", report.Project));
linqStatements.Add(parser.StringToLinqQuery<Region>("RegionId", report.Region));
linqStatements.Add(parser.StringToLinqQuery<Status>("Status", report.Status));
linqStatements.Add(parser.StringToLinqQuery<Priority>("Priority", report.Priority));
linqStatements.Add(parser.StringToLinqQuery<Category>("CategoryId", report.Category));
linqStatements.Add(AccountIdsToLinqQuery(report.PrimaryAssignment));
string baseQuery = String.Join(" AND ", linqStatements.Where(s => !String.IsNullOrWhiteSpace(s)));
var linqQuery = service.Mail.Where(baseQuery).Cast<Mail>();
The StringToLinqQuery looks something like this (simplified version).
public string StringToLinqQuery<TEnum>(string field, string value) where TEnum : struct
{
if (String.IsNullOrWhiteSpace(value))
return String.Empty;
var valueArray = value.Split('|');
var query = new StringBuilder();
for (int i = 0; i < valueArray.Count(); i++)
{
TEnum result;
if (Enum.TryParse<TEnum>(valueArray[i].ToLower(), true, out result))
{
if (i > 0)
query.Append(" OR ");
query.AppendFormat("{0} == {1}", field, Convert.ToInt32(result));
}
else
{
throw new DynoException("Item '" + valueArray[i] + "' not found. (" + type of (TEnum) + ")",
query.ToString());
}
}
// Wrap field == value with parentheses ()
query.Insert(0, "(");
query.Insert(query.Length, ")");
return query.ToString();
}
And the end result would look something like this.
service.Mail.Where("(ProjectId == 5) AND (RegionId == 6 OR RegionId == 7) AND (Status == 5) and (Priority == 5)")
In my project I store the values in an XML file, and then feed them into the above LINQ query. If an field is empty it will be ignored. It also support multiple values using the | sign, e.g. EU|US would translate to (Region == 5 OR Region == 6).

LINQ to SQL Conditional where clause

I have the following controller code that returns a Json list object to my view that draws a pie chart.
There are 4 input parameters and i have it working with 3 of them.
However, the fist parameter entitled 'SiteTypeId' needs to be included in the where.
My problem is how to include this neatly in the code, i'd like to avoid an override of the function.
The required additional logic is:
if SiteTypeId = -1 (then this means show all so nothing is to be changed)
if SiteTypeId = 0 (then i.SiteTypeId == 0 needs to be added)
if SiteTypeId = 1 (then i.SiteTypeId == 1 needs to be added)
If 2 and 3 above were all that was required it would be easy I guess. I'm thinking there must be a neat expression for this or a neat way of splitting the LINQ into 2 with a condition perhaps.
I'm new to LINQ - can anyone advise me, here is the controller code i need to modify:
public JsonResult GetChartData_IncidentsBySiteStatus(string SiteTypeId, string searchTextSite, string StartDate, string EndDate)
{
if (searchTextSite == null)
searchTextSite = "";
DateTime startDate = DateTime.Parse(StartDate);
DateTime endDate = DateTime.Parse(EndDate);
var qry = from s in _db.Sites
join i in _db.Incidents on s.SiteId equals i.SiteId
where s.SiteDescription.Contains(searchTextSite)
&& (i.Entered >= startDate && i.Entered <= endDate)
group s by s.SiteStatus.SiteStatusDescription + "[" + s.SiteTypeId.ToString() + "]"
into grp
select new
{
Site = grp.Key,
Count = grp.Count()
};
return Json(qry.ToList() , JsonRequestBehavior.AllowGet);
}
Sounds like you could use LINQKit and its PredicateBuilder. You use it to build dynamic conditional WHERE clauses. It's also used in LinqPad, and it's free.
Try this:
public JsonResult GetChartData_IncidentsBySiteStatus(string SiteTypeId, string searchTextSite, string StartDate, string EndDate)
{
if (searchTextSite == null)
searchTextSite = "";
DateTime startDate = DateTime.Parse(StartDate);
DateTime endDate = DateTime.Parse(EndDate);
var incidentsQry = _db.Incidents;
if(SiteTypeId > -1)
{
incidentsQry = incidentsQry.Where(a=>a.SiteTypeId == SiteTypeId);
}
var qry = from s in _db.Sites
join i in incidentsQry on s.SiteId equals i.SiteId
where s.SiteDescription.Contains(searchTextSite)
&& (i.Entered >= startDate && i.Entered <= endDate)
group s by s.SiteStatus.SiteStatusDescription + "[" + s.SiteTypeId.ToString() + "]"
into grp
select new
{
Site = grp.Key,
Count = grp.Count()
};
return Json(qry.ToList() , JsonRequestBehavior.AllowGet);
}
Simply add the following to your where clause
(SiteTypeId == -1 || i.SiteTypeId == SiteTypeId)

Categories

Resources