IQueryable WHERE inside a for loop does not produce expected result - c#

Objective:
Implementing AND filter. Where users input list of keywords in an input, then the list gets filtered based on the first word, then the next and so on. For instance, when they input "May", their list should be filtered to show only records for the month of may. In case, they input "May June", they should get empty list. Because, you shouldn't find records for the month of June within a list for the month of May
My failed attempt:
Below example does not work as I expected to
List<string> keywords; // e.g. "May", "June"
for (int i = 0; i < keywords.Length; i++) {
string searchTerm = keywords[i];
entityObj = entityObj.Where(x =>
x.Month.ToLower().Contains(searchTerm) ||
x.Year.ToString().ToLower().Contains(searchTerm) ||
x.Product.ToLower().Contains(searchTerm));
}
entityObj is of type IQueryable<SomeEntity>. Assume there is a table in the database with Month, Year and Product columns. On the first iteration, searchTerm will have May as its value. So, entityObj should return IQueryable object for records that only has May in the Month column. I expect the second iteration to return nothing, since we are looking for value of June in a result of the previous iteration which has only May result. However, I am getting records that have June value instead. No matter how many keywords I have, it is always returning the result based on the last element in the list which is the last iteration.
When I do below
string searchTerm = "May";
entityObj = entityObj.Where(x =>
x.Month.ToLower().Contains(searchTerm) ||
x.Year.ToString().ToLower().Contains(searchTerm) ||
x.Product.ToLower().Contains(searchTerm));
searchTerm = "June";
entityObj = entityObj.Where(x =>
x.Month.ToLower().Contains(searchTerm) ||
x.Year.ToString().ToLower().Contains(searchTerm) ||
x.Product.ToLower().Contains(searchTerm));
I get the result that I expect, empty list. However, When I do that inside a for loop, it does not work. Am I missing something here?

The issue must be because of using same variable entityObj.
First of all, entityObj is an IQueryable - right? That means it just a query and not a collection or list.
Secondly do not use same variable to store the result as well.
Instead try this it should work fine
string searchTerm = "May";
var mainList= entityObj.Where(x => x.Month.ToLower().Contains(searchTerm) ||
x.Year.ToString().ToLower().Contains(searchTerm) ||
x.Product.ToLower().Contains(searchTerm)).ToList();
searchTerm = "June";
var subList = mainList.Where(x => x.Month.ToLower().Contains(searchTerm) ||
x.Year.ToString().ToLower().Contains(searchTerm) ||
x.Product.ToLower().Contains(searchTerm));
I am sure this should work but even this one doesn't work then you might want to look into how C# is referencing variable assigning memory allocation.
Don't go too deep but there is shallow copy and deep copy techniques you might want to look into.
https://www.geeksforgeeks.org/shallow-copy-and-deep-copy-in-c-sharp/

Related

How to convert list of string into list of number then apply OrderByDescending on that list in C#

Consider this as the values in column Emp_code.
E1000
E1001
E9000
E4000
E1339
E10000
I'm using this code to first remove the E from all of the occurrences than convert them into number than apply OrderByDescending to the list.
var idd = db?.HRMS_EmpMst_TR?.Where(a => a.Emp_code != null)?
.Select(x=>x.Emp_code.Remove(0,1)).Select(int.Parse).OrderByDescending(y => y).First();
Can somebody help me with this code. I want to get 10000 as the answer.
Thanks for the help!
You need to
Use TrimStart('E') to remove E char from each string and parse it to
integer.
Get Max value from the processed sequence.
var input = new List<string>(){"E1000", "E1001", "E9000", "E4000", "E1339"};
var result = input
.Select(x => int.Parse(x.TrimStart('E'))) //Remove E and then parse string to integer
.Max(); //Get max value from an IEnumerable
Try Online: .NET Fiddle
You didn't say so, but I think you are working with a database, so you are working IQueryable, and not IEnumerable. This means that you can't use methods like String.TrimStart nor String.Parse.
So you have something called db, of which you didn't bother to tell us what it is. I assume it is a DbContext or something similar to access a database management system.
This DbContext has a table HRMS_EmpMst_TR, filled with rows of which I don't know what they are (please, next time give us some more information!). What I do know, that there are no null rows in this table. So your Where is meaningless.
By the way, are you not certain that db is not null?
if (db == null) return null;
After this, we know that db.HRMS_EmpMst_TR is a non null possible empty sequence of rows, where every row has a string column EmpCode. Every EmpCode starts with the character E followed by a four digits number. You want the EmpCode with the largest number.
string largestEmpCode = db.HRMS_EmpMst_TR
.OrderByDescending(row => row.EmpCode)
.Select(row => row.EmpCode)
.FirstOrDefault();
You get the string E9000, or null, if the table is empty. If you want 9000 just remove the first character and parse. What do you want if the table is empty?
if (largestEmpCode != null)
{
int largestEmpCodeValue = Int32.Parse(largestEmpCode.SubString(1));
}
else
{
// TODO: handle empty table.
}
There is room for improvement
If you are certain that every EmpCode is the character E followed by a four digit number, and you want to do calculations with this number, consider to change the EmpCode column to an integer column, without the E. This is a one time action, and it will make future calculations much easier.
Database column:
int EmpCodeValue;
LINQ to get the largest EmpCodeValue:
int largestEmpCodeValue = db.HRMS_EmpMst_TR
.Select(row => row.EmpCodeValue)
.Max();
If other parts of your application really need an "E followed by four digits", you can always make an extension method. I don't know what HRMS_EmpMst_TR are, let's assume it is a table of EmpMst
public string GetEmpCode(this EmpMst empMst)
{
return String.Format("E{0,04}", empMst.EmpCode);
}
I'm not sure about the ,04 part. You'll have to look it up, how to convert integer 4 to string "0004"
Usage:
List<EmpMst> fetchedEmpMsts = ...
string firstEmpCode = fetchedEmpMsts[0].GetEmpCode();
Or:
var result = db.HRMS_EmpMst_TR
.Where(empMst => empMst.Name == ...) // or use some other filter, just an example
.AsEnumerable()
.Select(empMst => new
{
Id = empMst.Id,
Name = empMst.Name,
EmpCode = empMst.GetEmpCode(),
...
});

How do I check the values retrieved from this Linq query as .ToLower()?

This is my code:
DataRow r = VirtualTable
.AsEnumerable()
.FirstOrDefault(tt => (tt.Field<string>("Column1") == value1) ||
(tt.Field<string>("Column1") == value2));
This code retrieves a data row whose 'Column1' matches a given string. I then check this against a bool if statement. However, though I can change my string's capitalization, I don't know how to handle it with the value Linq gives me. Still learning linq, so I don't know my way around it yet.
In short, I have the string "Red box" in the table, but want it to be read as "red box" so it will match my internal string of the same value.
Additionally, I was trying to retrieve the IndexOf the row this query gives me, but I'm always retrieving a -1 even if it finds a match.
Here's the code to retrieve it:
int SelectedIndex = VirtualTable.Rows.IndexOf(r);
Try string.Equals to ignore case and overload Select to get row's index:
var row = VirtualTable
.AsEnumerable()
.Select((tt, index) => new {
value = tt.Field<string>("Column1"),
index = index})
.FirstOrDefault(item =>
string.Equals(item.value, value1, StringComparison.OrdinalIgnoreCase) ||
string.Equals(item.value, value2, StringComparison.OrdinalIgnoreCase));
// If we have the row found, we can get
if (row != null) {
var r = row.value; // value, e.g. "bla-bla-bla"
int selectedIndex = row.index; // as well as its index, e.g. 123
...
}
You can use String.Equals(string,StringComparisonOption) to compare two strings using case-insensitive comparison. This avoids generating yet-another-temporary-string as ToLower() would do, eg:
tt.Field<string>("Column1").Equals(value1,StringComparison.OrdinalIgnoreCase)
or
tt.Field<string>("Column1").Equals(value1,StringComparison.CurrentCultureIgnoreCase)
Make sure you use the appropriate comparison option. Different cultures have different casing rules. Ordinal is the fastest option as it compares strings using binary rules.

Distinct the collection in Linq C#

I have the collection
From the collection the column name ma_position_value should not contain the same value
For Example
If the collection have 5 records, The column ma_position_value should not contain the same value from all 5 records...but it can contain same value for 2 or 3 or 4 records from the collection Atleast one column value should change.
So the main intension is ALL 5 records should not contain same value.Any one should get different value.So if all 5 is same I tried to throw a message
So I have just write a bool to return it if it is change
bool lblMa = false;
lblMa = ibusCalcWiz.iclbMssPaServiceSummary
.Where(lbusMssPaServiceSummary => lbusMssPaServiceSummary.icdoSummary.ma_position_value.IsNotNullOrEmpty()).Distinct().Count() > 1;
But it is always return true.
Just select distinct ma_position_value property values:
bool allSame = ibusCalcWiz.iclbMssPaServiceSummary
.Select(i => i.ma_position_value)
.Distinct()
.Count() == 1;
HINT: Do not use long variable names in lambda expressions. There is a rule of thumb - the bigger scope of variable usage, the bigger should be name. If scope is very small (lambda expression) then name should be very small (single letter is enough).
You can get the first one, and check if they are all equal:
// Only works if the collection is non-empty!
string first_ma_position_value = ibusCalcWiz.iclbMssPaServiceSummary.First().ma_position_value;
bool allTheSame = ibusCalcWiz.iclbMssPaServiceSummary
.All(lbusMssPaServiceSummary.icdoSummary.ma_position_value == first_ma_position_value);
or you can do the distinct, as you originally wanted, but on the value of the column instead of on the objects
bool allTheSame = ibusCalcWiz.iclbMssPaServiceSummary
.Select(lbusMssPaServiceSummary => lbusMssPaServiceSummary.icdoSummary.ma_position_value)
.Distinct()
.Count() == 1;

To apply mulitple criteria in lambda expression in c#

I have two main tables Listings and Place . In listing table there is a field PlaceId which referes to a Place entity/row/object . I want to query on both tables so that i get both of them like this .
var query = context.Listings
.Include("Place")
.Where(l => l.Place.TypeId == Type.Ro)
.OrderBy(l => l.Id).ToList();
after this now i want to put some filter on this query , here is the condition .
i got only a string like this var filter = "1,2,4"; . Now i want to filter on listing to gett all these listing where bedroom is equal to 1 OR 2 OR 4 .
What i have done
string minBeds = "1,2,4";
foreach (var item in minBeds.Split(','))
{
int minBed = int.Parse(item);
query = query.Where(l=>l.Place.Bedroom == minBed).ToList();
}
But doing this is giving me Zero result.
The problem with the way you're filtering it. After the first pass, you're filtering out everything except where Bedroom == 1, on the second pass you're filtering out everything except where Bedroom == 2, but since the only items in the list have Bedroom == 1, you won't have anything in the result set.
The solution is to use the conventional C# || operator:
query = query.Where(l => l.Place.Bedroom == "1" ||
l.Place.Bedroom == "2" ||
l.Place.Bedroom == "4");
Or if you want to be more flexible, use the Contains method:
string[] minBeds = "1,2,4".Split(',');
query = query.Where(l => minBeds.Contains(l.Place.Bedroom));
Note if Bedroom is an integer, you'll need to convert the input to an appropriate type first:
var minBeds = "1,2,4".Split(',').Select(int.Parse);
query = query.Where(l => minBeds.Contains(l.Place.Bedroom));
Also note, I've eliminated the ToList here. Unless you need to access items by index and add / remove items from the result collection, it's most likely just a waste of resources. You can usually rely on Linq's native laziness to delay processing to query until you really need the result.

c# filter search, multiple searchboxes

List<LICENSE> licenseList = context.LICENSE.Where(l => ( string.IsNullOrEmpty(licenseID) || l.LICENSE_ID.Contains(licenseID) ) && ( string.IsNullOrEmpty(hardwareID) || l.HARDWARE_ID.Contains(hardwareID) ) ).Take(10).ToList();
This is my current solution for handling more than one searchbox. Its a search function that combines 2 or more textfields in to a search. So my questions are: Is this an ok way to filter out the passed searchstrings. And how do i use it when the queries are decimals instead of strings? Thanks
Your example is perfectly fine.
With regards to it being decimals:
If it is a nullable type, then you first have to check if it has a value, and if it has that it is not the default value for a decimal which is 0.
If it is not a nullable type, then all you have to do is check that it is/is not == to 0 which is the default type. I always just check to make sure it's greater than zero, based on the assumption that a license won't be negative.
I am going to assume that it's not a nullable type as it seems to be an inline declared var, so here is a formatted example for decimal:
List<LICENSE> licenseList =
context.LICENSE.Where(l => licenseID == 0 || l.LICENSE_ID.Contains(licenseID))
.Where(l => hardwareID == 0 || l.HARDWARE_ID.Contains(hardwareID))
.Take(10)
.ToList();
Interesting thing to note, if you don't know the default type of a field, you can always do
licenseID == default(decimal)
You may try using a foreach loop on the search boxes, modifying linq, for any of those.
object[] a = {"seach", 5}; // "Data"
string[] Search = { "asdf", "asdf" }; //Search boxes
var s = a.Where(l => ((string)l).Contains(Search[0])); //first search
for (int i = 1; i < Search.Length; i++) //consecutive searches
s = s.Where(l => ((string)l).Contains(Search[i]));
Yes it looks ok. You could also use a loop somehow like this:
var query = context.LICENSE;
foreach(var item in stringVariables) {
query = query.Where(x => string.IsNullOrEmpty(item) || l.LICENSE_ID.Contains(item));
}
and stringVariables can be predefined or some algorithm to decide whether it's a search field or not.
Concerning the numbers (and assuming your column has type int, if it is a string you don't have to change anything), you probably have a nullable number, depending on your search form. So, you also have to check whether it's null or not and whether it's the right number. You may want to cast it to a string to also have the Contains function. But that all depends on your application.

Categories