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)
Related
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
I have the following SQL query to be translated to LINQ
string qWhere;
DateTime startDate = DateTime.Parse("2018-02-01");
DateTime endDate = DateTime.Parse("2018-02-03");
if(manual == true)
{
qWhere = " deliveryDate>=" + startDate + " and deliveryDate<=" + endDate;
}
else
{
qWhere = "deliveryDate>=" + DateTime.Now;
}
string sqlQuery = "select * from LoadingOrder where " + qWhere;
can anyone help me to translate this query to LINQ, table LoadingOrder have million rows.
Many thanks
If it was just the one condition then you could have two separate queries instead of one query. I think that's a lot easier to read and follow then placing the conditional inside the query itself.
var now = DateTime.Now;
if(manual)
result = LoadingOrders.Where(s=> s.deliveryDate >= startDate && s.deliveryDate <= endDate);
else
result = LoadingOrders.Where(s=> s.deliveryDate >= now);
Suppose the query has lots of conditions and the "delivery date" condition is the only one that changes. In that case you probably wouldn't want to have two entire versions of the query with just one difference. In that case, you can create that one condition separately.
To do that you would create a Func<LoadingOrder, bool> - a function that takes a LoadingOrder and returns true or false. And then you would assign whichever condition you want to check for to that function.
Func<LoadingOrder, bool> deliveryDateCondition;
if(manual)
deliveryDateCondition = loadingOrder =>
loadingOrder.deliveryDate >= startDate && loadingOrder.deliveryDate <= endDate;
else
{
var now = DateTime.Now;
deliveryDateCondition = loadingOrder => loadingOrder.deliveryDate >= now;
}
Now deliveryDateCondition is function that takes a LoadingOrder and returns true or false. You can add that function into your LINQ query, and it works regardless of which function was selected.
var result = LoadingOrders.Where(loadingOrder => deliveryDateCondition(loadingOrder)
&& ...some other condition...
&& ...some other condition...);
Something like this:
DateTime startDate = DateTime.Parse("2018-02-01");
DateTime endDate = DateTime.Parse("2018-02-03");
bool manual = ...;
loadingOrders.Where(
o => o.DeliveryDate >= startDate &&
o.DeliveryDate <= endDate &&
manual ||
o.DeliveryDate >= DateTime.Now &&
!manual);
var now = DateTime.Now;
var query = from e in db.LoadingOrder
where (e.deliveryDate >= startDate && e.deliveryDate <= endDate && manual)
|| (e.deliveryDate >= now)
select e;
OR
var query = db.LoadingOrder.Where(x => (x.deliveryDate >= startDate && x.deliveryDate <= endDate && manual) || (x.deliveryDate >= now));
Let's say that I have the following function...
private List<QueryObject> ReturnSQLData (DateTime d1, DateTime d2, string[] arrayOfNames)
{
var results = (from a in _context.Names
where (a.CreatedOn >= d1 && a.CreatedOn <d2)
select new QueryObject
{
FirstName = a.Name
LastName = a.LastName
}).ToList();
return results;
}
How could I continue create a Linq query where I have something line this?
private List<QueryObject> ReturnSQLData (DateTime d1, DateTime d2, string[] arrayOfNames)
{
var results = (from a in _context.Names
where (a.CreatedOn >= d1 && a.CreatedOn <d2)
&& (arrayOfNames[0].Equals("abc") || arrayOfNames[1].Equals("cde")
select new QueryObject
{
FirstName = a.Name
LastName = a.LastName
}).ToList();
return results;
}
Note that I don't really know the size of the string[] until I am actually executing the code. How could I then add this statement on my linq query?
> && (arrayOfNames[0].Equals("abc") || arrayOfNames[1].Equals("cde") ||
> ... || arrayOfNames[n].Equals("cde"))
Would that be possible?
BTW. I am running this on a ASP.NET 5 MVC6 EF7 solution in case it helps :)
Thanks!
can you do:
&& arrayOfNames.Contains("cde")
I have a table contain the payment records of agencies. I want to sum total payment of each agency into 2 columns, first is current day payment and second is the day before payment.
So I try the SQL like this.
select p1.UserName, p1.PaymentAmount, p2.PaymentAmount
from vw_Agency_Payment p1
join vw_Agency_Payment p2 on p1.UserName=p2.UserName
where p1.PaymentDate = '2014-08-07'
and p2.PaymentDate = '2014-08-08'
It is successful and return the data.
But when I convert it to Linq like below:
var yesterday = DateTime.Today.AddDays(-1);
var tomorrow = DateTime.Today.AddDays(1);
var agencyPayment = from y in db2.vw_Agency_Payment
join t in db2.vw_Agency_Payment on y.UserName equals t.UserName
where y.PaymentDate >= yesterday
&& y.PaymentDate < DateTime.Today
&& t.PaymentDate >= DateTime.Today
&& t.PaymentDate < tomorrow
select new AgencyPaymentModel
{
agencyUserCode = y.UserName,
yesterdayPayment = y.PaymentAmount,
todayPayment = t.PaymentAmount,
growth = (t.PaymentAmount - y.PaymentAmount) / y.PaymentAmount * 100
};
return View(agencyPayment.OrderByDescending(c => c.growth).Take(100).ToList());
It return no data.
I don't know what make it wrong!?
Why not the following code (taking the date part of datetime field)?
var yesterday = DateTime.Today.AddDays(-1);
var agencyPayment = from y in db2.vw_Agency_Payment
join t in db2.vw_Agency_Payment on y.UserName equals t.UserName
where y.PaymentDate.Date = yesterday
&& t.PaymentDate.Date = DateTime.Today
select new AgencyPaymentModel
{
agencyUserCode = y.UserName,
yesterdayPayment = y.PaymentAmount,
todayPayment = t.PaymentAmount,
growth = (t.PaymentAmount - y.PaymentAmount) / y.PaymentAmount * 100
};
return View(agencyPayment.OrderByDescending(c => c.growth).Take(100).ToList());
where y.PaymentDate >= yesterday
&& y.PaymentDate < DateTime.Today
&& t.PaymentDate >= DateTime.Today
&& t.PaymentDate < tomorrow
No result will satisfy this condition:
from line 1-2, PaymentDate is limited to yesterday... intersect with line 3 will narrow down to nothing.
Basically you need to draw a reasonable range.
Plus, snippet 2 contains more logic than snippet 1, you should test them under same conditions.
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).