How to create condtional lambda expression for date ranges? - c#

In my ASP.net MVC project, I've a search form with a total of 7 search variables. None of them are mandatory, however, at least one should be selected to perform search operation, as shown in the image.
What is the best method to perform search using lambda expression in this scenario since any combination of search parameters can be selected by the user? For now, I'm using a long list of 'if-else if' structures and writing individual lambda expressions for each. Is there a better/ more efficient way?
Update 1:
This is just half of the conditions, and now I've been told that even the 'Date To' for each of the three date criteria are optional, which will increase the list of checks even more. Is this the standard way of doing it?
IEnumerable<MyRecord> Request = null;
if (!status.Equals(""))
{
if (deliveryDateFrom == null && caseDateFrom == null && pickupDateFrom == null)
{
//only request status
Request = GetRecord("EquipRequest")
.Where(r => r.Status == status);
}
else if (deliveryDateFrom != null && caseDateFrom == null && pickupDateFrom == null)
{
// request status and delivery date
Request = GetRecord("EquipRequest")
.Where(r => r.Status == status && (r.DeliveryDate >= deliveryDateFrom && r.DeliveryDate <= deliveryDateTo));
}
else if (deliveryDateFrom == null && caseDateFrom != null && pickupDateFrom == null)
{
// request status and case date
Request = GetRecord("EquipRequest")
.Where(r => r.Status == status && (r.CaseDate >= caseDateFrom && r.CaseDate <= caseDateTo));
}
else if (deliveryDateFrom == null && caseDateFrom == null && pickupDateFrom != null)
{
// request status and pickup date
Request = GetRecord("EquipRequest")
.Where(r => r.Status == status && (r.PickupDate >= pickupDateFrom && r.PickupDate <= pickupDateTo));
}
else if (deliveryDateFrom != null && caseDateFrom != null && pickupDateFrom == null)
{
// request status, delivery date and case date
Request = GetRecord("EquipRequest")
.Where(r => r.Status == status && (r.DeliveryDate >= deliveryDateFrom && r.DeliveryDate <= deliveryDateTo)
&& (r.CaseDate >= caseDateFrom && r.CaseDate <= caseDateTo));
}
else if (deliveryDateFrom != null && caseDateFrom == null && pickupDateFrom != null)
{
// request status, delivery date and pickup date
Request = GetRecord("EquipRequest")
.Where(r => r.Status == status && (r.DeliveryDate >= deliveryDateFrom && r.DeliveryDate <= deliveryDateTo)
&& (r.PickupDate >= pickupDateFrom && r.PickupDate <= pickupDateTo));
}
else if (deliveryDateFrom == null && caseDateFrom != null && pickupDateFrom != null)
{
// request status, case date and pickup date
Request = GetRecord("EquipRequest")
.Where(r => r.Status == status && (r.CaseDate >= caseDateFrom && r.CaseDate <= caseDateTo)
&& (r.PickupDate >= pickupDateFrom && r.PickupDate <= pickupDateTo));
}
else if (deliveryDateFrom != null && caseDateFrom != null && pickupDateFrom != null)
{
// request status, delivery date, case date and pickup date
Request = GetRecord("EquipRequest")
.Where(r => r.Status == status && (r.DeliveryDate >= deliveryDateFrom && r.DeliveryDate <= deliveryDateTo)
&& (r.CaseDate >= caseDateFrom && r.CaseDate <= caseDateTo)
&& (r.PickupDate >= pickupDateFrom && r.PickupDate <= pickupDateTo));
}
}

What you could do is use a function like the following:
public static IEnumerable<TSource> WhereIf<TSource>(this IEnumerable<TSource> source, bool condition, Func<TSource, bool> predicate)
{
return condition ? source.Where(predicate) : source;
}
What it will do is if the bool is true, it will narrow it down by your lambda expression, if it is false, it will just continue.
And used it like so:
var whatever = GetRecord("EquipRequest")
.Where(r.Status == status)
.WhereIf(deliveryDateFrom != null, r => r.DeliveryDate >= deliveryDateFrom)
.WhereIf(deliveryDateTo != null, r => r.DeliveryDate <= deliveryDateTo)
.WhereIf(caseDateFrom != null, r => r.CaseDate >= caseDateFrom)
.WhereIf(caseDateTo != null, r => r.CaseDate <= caseDateTo)
.WhereIf(pickupDateFrom != null, r => r.PickupDate >= pickupDateFrom)
.WhereIf(pickupDateTo != null, r => r.PickupDate <= pickupDateTo)
So on and so forth

First I recommend you get LinqPad, learn it, if you want to experiment with things like this.
Then write something like this LinqPad script and run it to get your Linq statement correct:
public class SearchViewModel {
public DateTime DeliveryDateFrom {get; set;}
public DateTime DeliveryDateTo {get; set;}
public DateTime CaseDateFrom {get; set;}
public DateTime CaseDateTo {get; set;}
public DateTime PickupDateFrom {get; set;}
public DateTime PickupDateTo {get; set;}
};
public class Data {
public DateTime DeliveryDate {get; set;}
public DateTime CaseDate {get; set;}
public DateTime PickupDate {get; set;}
};
void Main()
{
var model = new SearchViewModel(); // insert your model stuff here
// you can use LinqPad to hook up directly to your project DBContext dll here
var dataBase = new List<Data>() {
new Data() { DeliveryDate = DateTime.Now, CaseDate = DateTime.Now, PickupDate = DateTime.Now }
};
// experiment with Linq statement here
var result = dataBase.Where(x =>
(model.DeliveryDateFrom >= x.DeliveryDate && model.DeliveryDateTo <= x.DeliveryDate) &&
(model.CaseDateFrom >= x.CaseDate && model.CaseDateTo <= x.CaseDate) &&
(model.PickupDateFrom >= x.PickupDate && model.PickupDateTo <= x.PickupDate)
);
result.Dump();
}

You can chain where clause.
IEnumerable<MyRecord> query = GetRecord("EquipRequest");
if (!status.Equals(""))
{
if (deliveryDateFrom.HasValue)
query = query.Where(x => x.DeliveryDate >= deliveryDateFrom);
if (deliveryDateTo.HasValue)
query = query.Where(x => x.DeliveryDate <= deliveryDateTo);
}
IEnumerable<MyRecord> result = query.ToList();

Related

Slow LINQ Query on Join

I'm using EF 6 with .NET Core. I have written the following query to select some data into a custom object. At the moment the main query returns over 25000 records. Then the filters are applied. This query is running very slow. I'm just trying to find out if I'm doing any mistakes here. Why it could be so slow and is the filtering happening on the memory rather than on the database side?
public IQueryable<TicketViewModel> GetTickets(int companyId, string locale, TicketFilterViewModel filters, Guid customerRef, int? take, int currentUser)
{
IQueryable<TicketViewModel> tickets = (from to in _context.Ticket
join co in _context.Company on to.CompanyId equals co.CompanyId
join con in _context.Contact on to.ContactId equals con.ContactId
join site in _context.Site on to.SiteId equals site.SiteId
join country in _context.Country on site.CountryId equals country.CountryId
join cus in _context.Customer on con.CustomerId equals cus.CustomerId
join tic_type in _context.TicketType on to.TicketTypeId equals tic_type.TicketTypeId
join ts in _context.TicketStatus on to.TicketStatusId equals ts.TicketStatusId
join sb in _context.ServiceBoard on to.ServiceBoardId equals sb.ServiceBoardId into ob1
from a in ob1.DefaultIfEmpty()
join u in _context.User on to.TechnitianId equals u.Id into ob2
from b in ob2.DefaultIfEmpty()
join pr in _context.Priority on to.PriorityId equals pr.PriorityId into ob3
from c in ob3.DefaultIfEmpty()
where to.CompanyId == companyId && (customerRef == Guid.Empty || cus.RefNo == customerRef)
&& to.MergedIntoTicketId == null
select new TicketViewModel
{
CreatedOn = Helpers.Custom.UtcToStandardTime(locale, to.AddedOnUtc).ToString("dd/MM/yyyy hh:mm tt"),
CustomerName = cus.CustomerName,
TicketNumber = to.TicketNumber,
TicketTitle = to.TicketTitle,
RefNo = to.RefNo,
CompanyName = co.CompanyName,
ContactName = string.Concat(con.FirstName, " ", con.LastName),
SiteAddress = String.Concat(site.AddressLine1, ", ", site.AddressLine2, ", ", site.Suburb, ", ", site.State, ", ", site.PostCode, ", ", country.Name),
ServiceBoardName = a.BoardName,
TechnicianName = string.Concat(b.FirstName, " ", b.LastName),
PriorityName = c.PriorityName,
TicketStatus = ts.StatusName, //d.StatusName,
ServiceBoardId = to.ServiceBoardId,
TicketStatusId = to.TicketStatusId,
CustomerId = cus.CustomerId,
PriorityId = to.PriorityId,
ContractId = site.ContractId,
TechnitianId = to.TechnitianId,
TicketId = to.TicketId,
StatusCategoryId = ts.CategoryId,//,//d.CategoryId,
DueOnUtc = to.DueOnUtc,
DefaultStatusId = ts.DefaultId,//d.DefaultId,
ClosedOnUtc = to.ClosedOnUtc,
ResolvedOnUtc = to.ResolvedOnUtc,
InitialResponseMade = to.InitialResponseMade,
TicketTypeId = to.TicketTypeId,
TicketTypeName = tic_type.TicketTypeName
}).OrderByDescending(o => o.TicketNumber);
bool isFiltersHit = false;
if (filters != null)
{
if (tickets != null && tickets.Count() > 0 && filters.serviceboard_selectedItems != null && filters.serviceboard_selectedItems.Count > 0)
{
isFiltersHit = true;
tickets = tickets.Where(x => x.ServiceBoardId != null && filters.serviceboard_selectedItems.Select(o => o.serviceBoardId).Contains(x.ServiceBoardId.Value));
}
if (tickets != null && tickets.Count() > 0 && filters.status_selectedItems != null && filters.status_selectedItems.Count > 0)
{
isFiltersHit = true;
tickets = tickets.Where(x => x.TicketStatusId != null && filters.status_selectedItems.Select(o => o.ticketStatusId).Contains(x.TicketStatusId.Value));
}
if (tickets != null && tickets.Count() > 0 && filters.type_selectedItems != null && filters.type_selectedItems.Count > 0)
{
isFiltersHit = true;
tickets = tickets.Where(x => filters.type_selectedItems.Select(o => o.ticketTypeId).Contains(x.TicketTypeId));
}
if (tickets != null && tickets.Count() > 0 && filters.technician_selectedItems != null && filters.technician_selectedItems.Count > 0)
{
isFiltersHit = true;
tickets = tickets.Where(x => x.TechnitianId != null && filters.technician_selectedItems.Select(o => o.id).Contains(x.TechnitianId.Value));
}
if (tickets != null && tickets.Count() > 0 && filters.customer_selectedItems != null && filters.customer_selectedItems.Count > 0)
{
isFiltersHit = true;
tickets = tickets.Where(x => x.CustomerId != 0 && filters.customer_selectedItems.Select(o => o.customerId).Contains(x.CustomerId));
}
if (tickets != null && tickets.Count() > 0 && filters.priority_selectedItems != null && filters.priority_selectedItems.Count > 0)
{
isFiltersHit = true;
tickets = tickets.Where(x => x.PriorityId != null && filters.priority_selectedItems.Select(o => o.priorityId).Contains(x.PriorityId.Value));
}
if (tickets != null && tickets.Count() > 0 && filters.contract_selectedItems != null && filters.contract_selectedItems.Count > 0)
{
isFiltersHit = true;
tickets = tickets.Where(x => x.ContractId != null && filters.contract_selectedItems.Select(o => o.contractId).Contains(x.ContractId.Value));
}
if (tickets != null && tickets.Count() > 0 && filters.source_selectedItems != null && filters.source_selectedItems.Count > 0)
{
isFiltersHit = true;
tickets = tickets.Where(x => x.SourceId != 0 && filters.source_selectedItems.Select(o => o.item_id).Contains(x.SourceId));
}
}
if (take.HasValue)
return tickets.Take(take.Value);
return tickets;
}
If you compose the query based on your conditions from the filters, you will see a big improvement. Meaning, create a base query ( base and minimum conditions ) and then add the conditions one by one, depending on what your filters are. Execute the query after all filters are checked.
P.S: you don't really need to call tickets.Count() on every filter. Do it once, in the beginning.

C# Query when any of the condition is not null

I want to find the data in database when any one of the conditions meet.
Pasted my code so that it will be more clear
[HttpGet]
[Route("")]
public IEnumerable<User> GetUsers(string FirstName = null, string LastName = null, int Year = 0, int Month = 0)
{
var users = _context.Users.AsQueryable();
if (FirstName != null || LastName != null || Year != 0 || Month != 0)
{
users = _context.Users.Where(u => (u.CreatedAt.Year == Year) && (u.CreatedAt.Month == Month));
}
else
{
users = _context.Users;
}
return users.ToList();
}
This code is doing a simple search in database
where year == createdAt.year &&
month == createdAt.month &&
LastName == abc &&
FirstName == abc
However, if one of the condition is 0/null, then the database will return nothing since there is no month/year == 0 or firstname/lastname == null; What I want is, if year/month/lastname/firstname is 0/null, then just ignore it and check other condition.
Any idea?
// first style
users = _context.Users.Where(u =>
(Year != 0 ? u.CreatedAt.Year == Year : true) &&
(Month != 0 ? u.CreatedAt.Month == Month : true) &&
(FirstName != null ? u.FirstName == FirstName : true) &&
(LastName != null ? u.LastName == LastName : true));
// second style
users = _context.Users.Where(u =>
(Year == 0 || u.CreatedAt.Year == Year) &&
(Month == 0 || u.CreatedAt.Month == Month) &&
(FirstName == null || u.FirstName == FirstName) &&
(LastName == null || u.LastName == LastName));
I think you should check each condition separately like this.
For example when Year != 0 and every other para is not set, your original code will return nothing.
You can add your logic to the LINQ query to check conditions.
users = _context.Users.Where(x => x.Id !=0
&& x.FirstName != null
&& x.FirstName != null
&& x.Year != 0
&& x.Month != 0)
.ToList();
Try this users = _context.Users.Where(x =>
&& (x.FirstName != null || x.FirstName == FirstName)
&& (x.Year == 0 || x.Year == Year)
&& (x.Month == 0 || x.Month == Month)
.ToList();

Having problems forming a linq subquery in my Application Context

Here is an example of the SQL I am trying to convert to Linq
SELECT
* FROM assetassignment
WHERE asgn_type = 'trc' AND asgn_id = '54490' AND lgh_number <> 3015097 AND mov_number <>2030782
and asgn_enddate in (select max(asgn_enddate) FROM assetassignment
WHERE asgn_type = 'trc' AND asgn_id = '54490' AND lgh_number <> 3015097 AND mov_number <> 2030782 and asgn_enddate <= '03/19/2017')
I need to convert this to Linq. I am using a repository as follows:
public async task<AssetAssignment> FindPreviousTrip(string tractorNumber, DateTime endDate, int legNumber,string moveNumber)
{
using (var ctx = new AssetAssignmentContext(_nameOrConnectionString))
{
var previousTrip = from a in ctx.Set<AssetAssignment>()
where a.AssignmentType == "TRC" &&
a.AssignmentId == tractorNumber &&
a.LegId == legNumber &&
a.MoveNumber == moveNumber &&
a.EndDate).in()
}
}
As you can see I am making a stab at it, but keep failing on the subquery for the EndDate in statement.
I'll keep plugging at it, but any help is appreciated.
var previousTrip = from a in ctx.Set<AssetAssignment>()
where a.AssignmentType == "TRC" &&
a.AssignmentId == tractorNumber &&
a.LegId != legNumber &&
a.MoveNumber != moveNumber &&
a.EndDate == (from b in ctx.Set<AssetAssignment>()
where b.AssignmentType == "TRC" && b.AssignmentId == tractorNumber && b.LegId != legNumber && b.MoveNumber != moveNumber && b.EndDate <= new DateTime(2017, 19, 3)
select b.EndDate).Max()
select a;
You may want to create a variable for the common query
var baseTrip = from a in ctx.Set<AssetAssignment>()
where a.AssignmentType == "TRC" &&
a.AssignmentId == tractorNumber &&
a.LegId != legNumber &&
a.MoveNumber != moveNumber
select a;
var previousTrip = from a in baseTrip
where a.EndDate == (from b in baseTrip where b.EndDate <= new DateTime(2017, 19, 3) select b.EndDate).Max()
select a;

Nullable Parameter used in Lambda Query is being ignored

I'm trying to pass a null value from a RenderAction to another view. But in between, at the controller, my linq lambda expression is not loading the right field, despite the null value going through correctly..
SprintManager.cshtml
<div id="Global_Backlog_Board" class="Board_Panel">
#{Html.RenderAction("ListOfSingleCards", new
{
State_ID = 1
});}
</div>
HomeController.cs
public PartialViewResult ListOfSingleCards( int? Sprint_ID,
int State_ID = 1)
{
var Cards = db.Cards.Where(x => x.State_ID == State_ID &&
x.Sprint_ID == Sprint_ID &&
x.Deleted != 1 &&
x.Archive != 1).ToList();
return PartialView(Cards);
}
So Sprint_ID is being passed over and loaded as null here, but I can't get the query to load the rows correctly.
In fact, the following works:
var Cards = db.Cards.Where(x => x.State_ID == State_ID &&
x.Sprint_ID == null &&
x.Deleted != 1 &&
x.Archive != 1).ToList();
So I suppose I could check if Sprint_ID is null and depending on the result run one of the two seperate queries, but I'd like to understand why my original attempt is not working.
Thank you!
I don't know the correct answer but based on your solution you should be able to tidy it up:
var cards = new List<Card>();
var query = db.Cards.Where(x => x.State_ID == State_ID &&
x.Deleted != 1 &&
x.Archive != 1);
if (Sprint_ID.HasValue)
query = query.Where(x => x.Sprint_ID == Sprint_ID);
else
query = query.Where(x => x.Sprint_ID == null);
cards = query.ToList();
A nullable int won't return "null" in the way that you're thinking. You have to check the HasValue property of it to determine if there is a value, and if so then use it otherwise use null:
public PartialViewResult ListOfSingleCards( int? Sprint_ID,
int State_ID = 1)
{
var Cards = db.Cards.Where(x => x.State_ID == State_ID &&
x.Sprint_ID == Sprint_ID.HasValue ? Sprint_ID.Value : null &&
x.Deleted != 1 &&
x.Archive != 1).ToList();
return PartialView(Cards);
}
Until something better comes a long, I'm using this:
var Cards = new List<Card>();
if (Sprint_ID == null)
{
Cards = db.Cards.Where(x => x.State_ID == State_ID &&
x.Sprint_ID == null &&
x.Deleted != 1 &&
x.Archive != 1).ToList();
}
else
{
Cards = db.Cards.Where(x => x.State_ID == State_ID &&
x.Sprint_ID == Sprint_ID &&
x.Deleted != 1 &&
x.Archive != 1).ToList();
}

Linq2SQL check if item is null

foreach (var incident in new DataAccess.IncidentRepository().GetItems().Where(
i => (startDate == null || i.IncidentDate >= startDate)
&& (endDate == null || i.IncidentDate <= endDate)
&& (shiftId == null || i.ShiftId == shiftId)
&& (processAreaId == null || i.ProcessAreaId == processAreaId)
&& (plantId == null || i.PlantId == plantId)))
is there a way I can i.PlantId == plantId not to get added if plantId is null?
Thanks
var incident in new DataAccess.IncidentRepository().GetItems().Where(
i => i.IncidentDate >= startDate
&& i.IncidentDate <= endDate
&& i.ShiftId == shiftId
&& i.ProcessAreaId == processAreaId
&& (plantId == null || i.PlantId == plantId)))
Alternatively, you could:
var incidents = new DataAccess.IncidentRepository().GetItems().Where(
i => i.IncidentDate >= startDate
&& i.IncidentDate <= endDate
&& i.ShiftId == shiftId
&& i.ProcessAreaId == processAreaId));
if (plantId != null)
incidents = incidents.Where(i => i.PlantId == plantId);
foreach (var incident in incidents) {
// ...
}
var incident in new DataAccess.IncidentRepository().GetItems().Where(
i => i.IncidentDate >= startDate
&& i.IncidentDate <= endDate
&& i.ShiftId == shiftId
&& i.ProcessAreaId == processAreaId
&& object.Equals(i.PlantId, plantId)))

Categories