LINQ - mandatory field and dont care if null - c#

I am building a search feature in an asp.net application and I am using LINQ to SQL to retrieve data based on the selected search criteria. The search criteria are
Country
City
District
number of rooms
Rent Cycle
Only the first search criterion, Country, is the mandatory field. However, if the user entered values for criteria 2, 3, 4 and/or 5 then the entered values should be taken into account and only retrieve results that matched all entered search criterion. Notice that if one of the criterion 2, 3, 4 and/or 5 is left empty (null) then LINQ should act as 'DONT CARE' and return that row.
So for example, if the criteria entered are:
Country = USA
City = null
District = null
number of rooms = null
Rent Cycle = null
Then all rows with Country == USA should be returned.
Another example:
Country = UK
City = null
District = null
number of rooms = 5
Rent Cycle = null
Then all rows with Country == UK and NumberOfRooms == 5 should be returned.
How do I achieve this in LINQ to SQL?
Here is what i have so far:
var data = from x in db.Units where x.Country == coutnryID && /*PLEASE HELP!*/ select x;

Try this (assuming cityId, districtId, rooms and rentCycle are the variables that you're wanting to search on:
var data = from x in db.Units
where x.Country == countryId
&& (cityId == null || x.City == cityId)
&& (districtId == null || x.District == districtId)
&& (rooms == null || x.Rooms == rooms)
&& (rentCycle == null || x.RentCycle == rentCycle)
select x;
I'm basically saying, if your variables you want to search on are null, then disregard them, otherwise match them to the corresponding field in the Unit.

You can build up the query in stages:
var query = from x in db.Units where x.Country == countryId;
if (cityId != null) query = query.Where(x.City == cityId);
if (districtId != null) query = query.Where(x.City == districtId);
if (rooms != null) query = query.Where(x.Rooms == rooms);
if (rentCycle != null) query = query.Where(x.RentCycle == rentCycle);
var data = query.Select();
That'll give you slightly more efficient SQL if slightly messier C#

Using GetValueOrDefault, and supply the default to the current value if null:
var data = from x in db.Units
where x.Country == countryId
&& (x.City == cityId.GetValueOrDefault(x.City))
&& (x.District == districtId.GetValueOrDefault(x.District))
&& (x.Rooms == rooms.GetValueOrDefault(x.Rooms))
&& (x.RentCycle == rentCycle.GetValueOrDefault(x.RentCycle))
select x;

Related

Combine two list using Linq and add data as needed from different tables

I need to change a process and have been struggling with it for a couple of days now.
The current task checks for all digits entered by the user in Table1. I don't have an issue with that since I can return it with this statement:
var itemsTable1 = db.Table1.Where(a =>
searchNumbers.Contains(a.Digit1) || searchNumbers.Contains(a.Digit2) || searchNumbers.Contains(a.Digit3) ||
searchNumbers.Contains(a.Digit4) || searchNumbers.Contains(a.Digit5) || _Digit6 == a.Digit6 && a.ValidFlag == 1
).ToList();
Now I need to look for the same digits on Table2 and make sure I bring those numbers as well. Although the tables will have the same columns for digits, they will not have the same number of columns in total. I could just right another statement as above for Table2, no problem there. However, I also need to bring the records that do not contain the digits but have the same Ids. So, my scenarios would be something like this:
Table1 = contains digits -> Table2 != contains digits
Table2 = contains digits -> Table1 != contains digits
Table1 = contains digits -> Table2 = contains digits
Finally, I need to display the data on either list in a descending order, which I assume, I'd would have to combine the two/three lists and return it to the model.
Is there a way of doing this with plain Linq? Or am I better off creating maybe a CTE in a stored procedure and pass the parameters there and then calling in the EF?
I assume you need this query:
var query =
from t1 in db.Table1
join t2 in db.Table2 on t1.Id equals t1.Id
let t1Contains = searchNumbers.Contains(t1.Digit1)
|| searchNumbers.Contains(t1.Digit2)
|| searchNumbers.Contains(t1.Digit3)
|| searchNumbers.Contains(t1.Digit4)
|| searchNumbers.Contains(t1.Digit5)
|| _Digit6 == t1.Digit6 && t1.ValidFlag == 1
let t2Contains = searchNumbers.Contains(t2.Digit1)
|| searchNumbers.Contains(t2.Digit2)
|| searchNumbers.Contains(t2.Digit3)
|| searchNumbers.Contains(t2.Digit4)
|| searchNumbers.Contains(t2.Digit5)
|| _Digit6 == t2.Digit6 && t2.ValidFlag == 1
where t1Contains != t2Contains || t1Contains && t2Contains
select
{
t1,
t2
};
Note, that you have not specified desired output and how to order result.
Following #Svyatoslav Danyliv suggestion. I have created the following:
//By using the list, we make sure that the search returns every single digit, regardless of position they occupy in the DB
var itemsT1 = db.Table1.Where(a => searchNumbers.Contains(a.Digit1) || searchNumbers.Contains(a.Digit2) || searchNumbers.Contains(a.Digit3) ||
searchNumbers.Contains(a.Digit4) || searchNumbers.Contains(a.Digit5) || _Digit6 == a.Digit6 && a.ValidDrawResults == 1);
var itemsT2 = db.Table2.Where(a => searchNumbers.Contains(a.Digit1) || searchNumbers.Contains(a.Digit2) || searchNumbers.Contains(a.Digit3) ||
searchNumbers.Contains(a.Digit4) || searchNumbers.Contains(a.Digit5) || _Digit6 == a.Digit6 && a.ValidDrawResults == 1);
//Create list to hold Ids from the records above
List<int?> t1Ids = new List<int?>();
List<int?> t2Ids = new List<int?>();
//Insert the Ids into the lists
foreach (var t1Id in t1Ids )
{
t1Ids.Add((int)t1Id.Id);
}
foreach (var t2Id in t2Ids)
{
t2Ids.Add((int)t2Id.Id);
}
//Get the records from opposite table that contains same Ids
var resultT1 = db.Table1.Where(r => t1Ids.Contains(r.Id)
);
var resultT2 = db.Table2.Where(r => t2Ids.Contains(r.Id)
);
//Combine the lists to pass to the view
var groupedT1 = itemsT1.Concat(resultT1).Distinct();
var groupedT2 = itemsT2.Concat(resultT2).Distinct();
using (db)
{
var vmT1T2 = new ViewModelTables
{
getTable1 = groupedT2.ToList(),
getTable2 = groupedT2.ToList()
};
return View(vmT1T2);
}
It worked out perfectly as far as bring the records that I needed.
Once again, thank you #Svyatoslav Danyliv for pointing me in the right direction. I appreciate and hope this can help someone else as well.

LINQ equivalent of SQL NOT IN statement

I have a query that in T-SQL it is
SELECT *
FROM rpm_scrty_rpm_usr ru
WHERE ru.inact_ind = 'N'
AND email_id IS NOT NULL
AND wwid IS NULL
AND LTRIM(RTRIM (email_id)) <> ''
AND dflt_ste_id NOT IN (25,346,350,352,353,354,355,357,358,366,372,411)
When I have been converting it to LINQ, I have everything except the "NOT IN"
var querynonSystem = (from ru in Rpm_scrty_rpm_usrs
where ru.Inact_ind == "N" && ru.Email_id != null && ru.Wwid == null && ru.Email_id.Trim() != ""
&& ru.Dflt_ste_id != 25
select ru).Count();
I did temporarily put in this line && ru.Dflt_ste_id != 25
However I need to have AND dflt_ste_id NOT IN (25,346,350,352,353,354,355,357,358,366,372,411)
I am seeing a lot of different code like
this lambda where !(list2.Any(item2 => item2.Email == item1.Email))
Then var otherObjects = context.ItemList.Where(x => !itemIds.Contains(x.Id));
For my linq query, how can I do this Not In in simple manner?
You can use Contains with !. In addition, if you just want to count rows, you can use Count.
var ids = new List<int> {25, 346, 350, 352, 353, 354, 355, 357, 358, 366, 372, 411};
var querynonSystem = XXXcontext.Rpm_scrty_rpm_usrs.Count(x =>
x.Inact_ind == "N" &&
x.Email_id != null &&
x.Wwid == null &&
x.Email_id.Trim() != "" &&
!ids.Contains(x.Dflt_ste_id));
From comment: if you want to retrieve all, you can still use Where and Select.
var querynonSystem = XXXcontext.Rpm_scrty_rpm_usrs.Where(x =>
x.Inact_ind == "N" &&
x.Email_id != null &&
x.Wwid == null &&
x.Email_id.Trim() != "" &&
!ids.Contains(x.Dflt_ste_id)).Select(x => x).ToList();
FYI: you cannot call Rpm_scrty_rpm_usrs table class to query. Instead, you need DbContext or some other repository.
There is no "not in" operator unless the type of the query is the same as the type you want to filter against (in which case you could use except). Here it is not. You're working on an IEnumerable and you want to filter on it's ID so a list of int. The where with lambda and contains is your best bet and WILL be translated to an in on the SQL side by most providers.
var FilterIds = new List<int>{1,2,3,4,344,3423525};
var querynonSystem = (from ru in Rpm_scrty_rpm_usrs
where ru.Inact_ind == "N" && ru.Email_id != null && ru.Wwid == null && ru.Email_id.Trim() != ""
&& ru.Dflt_ste_id != 25
select ru)
// Use this
.Where(ru=>!FilterIds.Any(id=>ru.dflt_ste_id ==id))
// Or this
.Where(ru=>!FilterIds.Contains(ru.dflt_ste_id))
.Count();

Filtering results with Linq

I have two dropdowns on my page. First dropdown shows Authors for books and the other dropdown shows status's i.e Overdue or All.
If they choose Overdue then I need to return all books that have been borrowed more than a week ago so the dueback date (Datetime variable) will be taken into consideration.
I currently have this working correctly filtering on the Author as shown here:
model.ListBooks = (from x in tempModel
where ((x.BookAuthor == model.ListAuthors.SelectedAuthor || model.ListAuthors.SelectedAuthor == null))
select x).ToList(); // Filter the results
But as soon as I pass in the additional search filter I.e status it fails to shows me any books that match the selected Author even though I haven't chosen a status this is what it currently looks like.
model.ListBooks = (from x in tempModel
where (
(x.BookAuthor == model.ListAuthors.SelectedAuthor || model.ListAuthors.SelectedAuthor == null)
&&
(model.BookStatus.SelectedStatusId == (int)Enums.Registration.OverDue && x.DueBack < DateTime.Now.)
)
select x).ToList(); // Filter the results
Can someone see what I'm doing wrong here?
I think your query fails if selected status is not OverDue. In that case you have
where authorFilter && (false && dateFilter)
that gives you false for all books. Thus you have only two statuses, you can just add status.SelectedStatusId != (int)Enums.Registration.OverDue check just as you did with null-check for selected author:
var authors = model.ListAuthors;
var status = model.BookStatus;
model.ListBooks = (from x in tempModel
where (authors.SelectedAuthor == null || x.BookAuthor == authors.SelectedAuthor) &&
(status.SelectedStatusId != (int)Enums.Registration.OverDue || x.DueBack < DateTime.Now)
select x).ToList();
I would use method syntax here to make query more readable:
var books = tempModel; // probably you will need IEnumerable<T> or IQueryable<T> here
if (model.ListAuthors.SelectedAuthor != null)
books = books.Where(b => b.BookAuthor == model.ListAuthors.SelectedAuthor);
if (model.BookStatus.SelectedStatusId == (int)Enums.Registration.OverDue)
books = books.Where(b => b.DueBack < DateTime.Now);
model.ListBooks = books.ToList();
Here:
(model.BookStatus.SelectedStatusId == (int)Enums.Registration.OverDue && x.DueBack < DateTime.Now.)
Should be instead:
(x.BookStatus.SelectedStatusId == (int)Enums.Registration.OverDue && x.DueBack < DateTime.Now.)
Because You like to compare element of LINQ query, not the model.

Using Linq to filter by List<ListItem>

I am trying to extend my linq query with additional search criteria to filter the data by sending also a List<Listitem> to the function for processing. The List can contain 1 or more items and the objective is to retreive all items which match any criteria.
Since i am sending several search criteria to the function the goal is to make a more accurate filter result the more information i am sending to the filter. If one or several criterias are empty then the filter will get less accurate results.
Exception is raised every time i execute following code, and I cant figure out how to solve the using statement to include the List<ListItem>. Appreciate all the help in advance!
Exception: Unable to create a constant value of type 'System.Web.UI.WebControls.ListItem'. Only primitive types or enumeration types are supported in this context.
using (var db = new DL.ENTS())
{
List<DL.PRODUCTS> products =
(from a in db.PRODUCTS
where (description == null || description == "" ||
a.DESCRIPTION.Contains(description)) &&
(active == null || active == "" || a.ACTIVE.Equals(active, StringComparison.CurrentCultureIgnoreCase)) &&
(mID == null || mID == "" || a.MEDIA_ID == mID) &&
(mID == null || objTypes.Any(s => s.Value == a.OBJECTS)) //Exception here!
select a).ToList<DL.PRODUCTS>();
return products;
}
Pass collection of primitive values to expression:
using (var db = new DL.ENTS())
{
var values = objTypes.Select(s => s.Value).ToArray();
List<DL.PRODUCTS> products =
(from a in db.PRODUCTS
where (description == null || description == "" || a.DESCRIPTION.Contains(description)) &&
(active == null || active == "" || a.ACTIVE.Equals(active, StringComparison.CurrentCultureIgnoreCase)) &&
(mID == null || mID == "" || a.MEDIA_ID == mID) &&
(mID == null || values.Contains(a.OBJECTS))
select a).ToList<DL.PRODUCTS>();
return products;
}
That will generate SQL IN clause.
Note - you can use lambda syntax to compose query by adding filters based on some conditions:
var products = db.PRODUCTS;
if (!String.IsNullOrEmpty(description))
products = products.Where(p => p.DESCRIPTION.Contains(description));
if (!String.IsNullOrEmpty(active))
products = products.Where(p => p.ACTIVE.Equals(active, StringComparison.CurrentCultureIgnoreCase)));
if (!String.IsNullOrEmpty(mID))
products = products.Where(p => p.MEDIA_ID == mID);
if (mID != null)
products = products.Where(p => values.Contains(p.OBJECTS));
return products.ToList();
Linq isn't able to convert the predicate on ListItem to something useful to Sql.
I would suggest that you pre-project the values of the ListItems into a simple List<string> before using this with Contains (which is converted to IN)
var listValues = objTypes.Select(_ => _.Value).ToList();
List<DL.PRODUCTS> products = ...
listValues.Contains(a.OBJECTS))

Set a value to list a datatype after checking values of other datatypes

I have a list that holds session data for a user. After I update information that is held within the session data, I need to set the session data to this new information. Trying to do a LINQ query that's like so, user is the global session data and that holds individual which is the List. So I need to lookup in the list where address and city equal this and once found I need to set the state equal to this and their is only going to be one record in the list that will match it.
user.Individual = user.Individual.Where(a => a.address == "ABC" && a.city == "MIAMI").ToList()
So above I'm able to look through the list to find where the address and city equals something. How do I set a.State=="FL" based on that Where, and if those two are true/found in the list.
Try this (this assumes you have one person matching your where clause):
//get the user
var idividual = user.Individual.SingleOrDefault(a => a.address == "ABC" && a.city == "MIAMI");
if(idividual != null)
{
idividual.State = "FL";
//persist user in DB or elsewhere
}
If you have more than 1 item in the resulting set, do this:
//get the user
var idividuals = user.Individual.Where(a => a.address == "ABC" && a.city == "MIAMI").ToList();
individuals.ForEach(i=>i.State="FL");
you just add the && to the where agian ?
user.Individual = user.Individual.Where(a => (a.address == "ABC" && a.city == "MIAMI") && a.State=="FL").ToList()
Edit what do you mean with set?

Categories