I am building a WinForms application that searches for books in the Database.
I have four tables:
Author,Book,Country,Genre;
There is a field in "Book" table called "Year".
I added startDate and endDate fields, so it can search for specific book released between the given two years(startDate and endDate both are integers). That is where my troubles began.
This is how I parse inputs.
int startDateResult = 0;
int? startDate = null;
if (inputStartDate == string.Empty)
{
startDate = null;
}
else
{
if (Int32.TryParse(inputStartDate, out startDateResult))
{
startDate = startDateResult;
}
else throw new Exception("Please input correct year");
}
int endDateResult = 0;
int? endDate = null;
if (inputEndDate == string.Empty)
{
endDate = null;
}
else
{
if (Int32.TryParse(inputEndDate, out endDateResult))
{
endDate = endDateResult;
}
else throw new Exception("Please input correct year");
}
This is the LINQ query I am using for searching.
specificBookForAuthor = _context.Books.Where(c =>
(c.Author.Name.Contains(First) || c.Author.Surname.Contains(Last))
&& book==string.Empty?true: c.Name.Contains(book)
&& country == string.Empty ? true : c.Author.Country.Name.Contains(country)
&& genre == string.Empty ? true : c.Genre.Name.Contains(genre)
&& inputYear == string.Empty ? true : c.Year==year
&& inputStartDate == string.Empty ? true : c.Year >= startDate
&& inputEndDate == string.Empty ? true : c.Year <= endDate
).Select(b => b).ToList();
This query did not work. Then, I tried to comment all the lines except "inputEndDate", typed 0 in startDate and 5000 in endDate. After Debug, found out, that "specificBookAuthor"-s count was 1. Which was CORRECT.
Commented Code :
specificBookForAuthor = _context.Books.Where(c =>
(c.Author.Name.Contains(First) || c.Author.Surname.Contains(Last))
//&& book==string.Empty?true: c.Name.Contains(book)
//&& country == string.Empty ? true :
// c.Author.Country.Name.Contains(country)
// && genre == string.Empty ? true : c.Genre.Name.Contains(genre)
// && inputYear == string.Empty ? true : c.Year==year
// && inputStartDate == string.Empty ? true : c.Year >= startDate
inputEndDate == string.Empty ? true : c.Year <= endDate
).Select(b => b).ToList();
Did the same with inputStartDate(commented inputEndDate line and uncommented inputStartDate). Worked fine.
I get the problem when I leave both of the fields uncommented. Like this:
specificBookForAuthor = _context.Books.Where(c =>
(c.Author.Name.Contains(First) || c.Author.Surname.Contains(Last))
//&& book==string.Empty?true: c.Name.Contains(book)
//&& country == string.Empty ? true :c.Author.Country.Name.Contains(country)
// && genre == string.Empty ? true : c.Genre.Name.Contains(genre)
// && inputYear == string.Empty ? true : c.Year==year
&& inputStartDate == string.Empty ? true : c.Year >= startDate
&& inputEndDate == string.Empty ? true : c.Year <= endDate
).Select(b => b).ToList();
In that case, "specificBookAuthor"-s count is NULL instead of 1.
Could you test the following change which may not be directly related to the issue, but it can give you a more readable/debugable code?
Rewrite your code to this:
specificBookForAuthor = _context.Books.Where(c =>
(c.Author.Name.Contains(First) || c.Author.Surname.Contains(Last)));
if(!string.IsNullOrEmpty(book))
specificBookForAuthor = specificBookForAuthor.Where(c => c.Name.Contains(book));
if(!string.IsNullOrEmpty(country))
specificBookForAuthor = specificBookForAuthor.Where(c => c.Author.Country.Name.Contains(country));
//....and so on with your other conditions;
//....finally:
specificBookForAuthor = specificBookForAuthor.ToList();
As I said, this may not resolve your problem, but this way you can properly debug condition by condition and maybe then you can find the problem.
So every Book has a property Year, representing the year in which a book was published.
Furthermore you have two nullable integers startDate and endDate and you want to limit the selected books to books published between startDate and endDate, with special cases if one or both of these values are null.
You have an input IQueryable<Book> and as output you want the query that will only fetch the books between startDate and endDate, with special treatment if these values are null.
To keep it readable, testable and maintainable, I suggest an extension function. See Extension Methods Demystified
public static IQueryable<Book> WherePublishedBetween(this IQueryable<Book> books,
Year? start,
Year? end)
{
if (start.HasValue)
{
if (end.HasValue)
return books.Where(book => start.Value <= book.Year && book.Year <= end.Value);
else
return books.Where(book => start.Value <= book.Year);
}
else
{ // no start value
if (end.HasValue)
return books.Where(book => book.Year <= end.Value);
else
return books;
}
}
Usage:
int? startYear = ...
int? endYear = ...
var queryBooks = myDbContext.Books
.Where(book => book.Author.Name.Contains(First)
|| book.Author.Surname.Contains(Last)
&& ... // check other properties)
.WherePublishedBetween(startYear, endYear)
// continue with the query, with whatever LINQ you need
.Select(book => ...)
The LINQ expression builder will be smart enough to combine these two Where statements using Expression.AndAlso
Related
I am a little confused about how I can use the date as an optional condition.
if there is a date then <= of date, if the date is null then don't filter based on date.
My code is like this
DateTime date= DateTime.UtcNow.AddDays(-10);
foreach (var camEvent in dbContext
.CAM_EVENTS
.Where(c => c.USER_ID == userID &&
c.CAM_ID == cam.CAM_ID &&
c.EVENT_DATE >= date) // I want to change this like
.OrderByDescending(c => c.DATE))
{...}
I want that line to look something like this
(date && c.EVENT_DATE >= date)
so it only filter when date is not null, but this is not working.
I'd do the following logic:
(date==null || (c.EVENT_DATE >= date))
You can do something like this:
DateTime date = DateTime.UtcNow.AddDays(-10);
var filteredContext = dbContext
.CAM_EVENTS
.Where(c => c.USER_ID == userID &&
c.CAM_ID == cam.CAM_ID)
.OrderByDescending(c => c.DATE);
if (date != null) {
filteredContext = filteredContext.Where(c.EVENT_DATE >= date);
}
foreach (var camEvent in filteredContext) {
...
}
You can use a ternary operator, also known as a conditional operator.
foreach (var camEvent in dbContext
.CAM_EVENTS
.Where(c => c.USER_ID == userID &&
c.CAM_ID == cam.CAM_ID &&
// if date is not null, it will return bool c.EVENT_DATE >= date, otherwise just true
(date != null ? c.EVENT_DATE >= date : true))
.OrderByDescending(c => c.DATE))
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();
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();
I have a action in MVC controller
public ActionResult ExportExcel(string ReportType,DateTime? FromDate,DateTime? ToDate)
{
var query = UnitOfWork.RepoTestResAnalysis.GetAll();
var QueryData = query.Where(s => s.MSO == ms && (FromDate != null && (s.TEST_DATE.Value >= FromDate)) && (ToDate!=null && (s.TEST_DATE.Value<=ToDate))).ToList();
}
Now If FromDate and ToDate are null then I am getting QueryData Count is Zero. But I need all records. So Can anyone tell me how can I get expected result. While FromDate & ToDate has value then I am getting expected result.
According to the information you have provided
Change you below statement:
var QueryData = query.Where(s => s.MSO == ms && (FromDate != null && (s.TEST_DATE.Value >= FromDate)) && (ToDate!=null && (s.TEST_DATE.Value<=ToDate))).ToList();
To
var QueryData = query.Where(s => s.MSO == ms && (FromDate == null || (s.TEST_DATE.Value >= FromDate)) && (ToDate == null || (s.TEST_DATE.Value<=ToDate))).ToList();
If the FromDate Or ToDate will be equal to NULL, it won't check them against s.TEST_DATE.Value.
You can do it like below also:
Change QueryData assignment to:
var QueryData = query.Where(s => s.MSO == ms &&
(s.TEST_DATE.Value >= (FromDate ?? DateTime.MinValue)) &&
(s.TEST_DATE.Value <= (ToDate ?? DateTime.MaxValue))).ToList();
I know it's an old question, but I will be glad if my code can help anybody.
I think following implementation will be the short and easy to read:
public ActionResult SomeMethod(string ReportType, DateTime? FromDate, DateTime? ToDate)
{
var query = UnitOfWork.RepoTestResAnalysis.GetAll();
var QueryData = query.Where(x => x.DateField >= (FromDate ?? x.DateField) && x.DateField <= (ToDate ?? x.DateField).ToList();
}
I have two if statements and they both are conflicting with each other.
By default I can do filter by Dates and a Drop Down list from this if Statement:
DateTime fromDate = DateTime.MinValue;
DateTime toDate = DateTime.MaxValue;
if (dateFilter.Contains('~'))
{
fromDate = dateFilter.Split('~')[0] == "" ? DateTime.MinValue : Convert.ToDateTime(dateFilter.Split('~')[0]);
toDate = dateFilter.Split('~')[1] == "" ? DateTime.MaxValue : Convert.ToDateTime(dateFilter.Split('~')[1]);
}
filteredTracks = DataRepository.GetTracks()
.Where(c => (trackFilter == "" || c.TrackName.ToLower().Contains(trackFilter.ToLower()))
&&
(fromDate == DateTime.MinValue || fromDate < c.Date)
&&
(toDate == DateTime.MaxValue || c.Date < toDate)
);
but cannot do normal filtering records using this if statement, if the above one is being used:
if (!string.IsNullOrEmpty(param.sSearch))
{
var isTrackSearchable = Convert.ToBoolean(Request["bSearchable_1"]);
filteredTracks = DataRepository.GetTracks()
.Where(c => isTrackSearchable && c.TrackName.ToLower().Contains(param.sSearch.ToLower()));
}
else
{
filteredTracks = allTracks;
}
is it possible to have both of these to work?
Any help would be a great help :)
In such cases you can reuse the same query, conditionally adding Where clauses. E.g. for the second if-statement you can do just:
filteredTracks = filteredTracks.Where(...
Where filteredTracks already contains a result of first if-statement.