I have a list of objects with parameters ID, and Code. I want to return the Objects that contain specific letters within the Code parameter.
ID Code
---------
0 ABCD
1 LMNO
2 ARDQ
4 PQRD
List of string -> Letters = ('A','M','DQ')
For this example, it should return any objects that contain 'A' or 'M' within the Code parameter.
Results -> Object ID's 0,1,2
I tried something like this
var Results = MyObjects.FindAll(x => Letters.Contains(x.Code))
But this isn't what I want, I can't wrap my head around how to do this.
The works given the update of the question from what was originally asked.
var Results = MyObjects.Where(x => letters.Any(y => x.Code.Contains(y))).Select(x => x.ID);
for each object, letters is checked to see if it is contained in the code.
After looking at this for a while, I think the best solution is a nested foreach.
var Results = New Object();
foreach(Object row in myObject)
{
foreach(string letter in letters)
{
if(row.column2.contains(letter))
{
Results.add(row);
};
};
};
Related
I am new to LINQ. I just want to ask if it is possible to check if a field contains a value from a list of string. Say I have a list of string as defined below.
List<String> yearList = new List<string>();
yearList.Add("2012");
yearList.Add("2015");
I also have a class Member:
class Member
{
public string Name {get; set;}
public int Year {get; set;}
...
}
Object data is of a class that implements IEnumerable<Member>:
IEnumerable<Member> data = ...
So data may be a List<Member> or a Member[], or anything else that represents an enumerable sequence of Member objects.
Now I want to get the data if the year field is in my list of years yearList.
Or to be more precise:
Requirement: Given an object data, that implements IEnumerable<Member>, and given an object yearList, which implements IEnumerable<string>, where the string is a textual representation of a year, give me all Members in object data that have a value of property Year that has a textual representation that equals at least one of the values in yearList.
For example data is as follows:
Name (string) | Year (int)
Ralph | 2012
Rafael | 2012
Bea | 2014
Lee | 2015
and yearList is the sequence of strings {"2012", "2015"}. I am expecting a result like
Name (string) | Year (int)
Ralph | 2012
Rafael | 2012
Bea | 2014
Lee | 2015
I tried the following, but that didn't work
data.where(x => x.year.contains(yearList)); <--- THIS CODE NEEDS TO BE CHANGED
Is that possible in LINQ?
Think how you would do it with loops
You might do:
foreach(var d in data){
if(yearList.Contains(d.year))
...
}
But this is different to what you wrote; what you wrote doesn't really make sense:
//data.where(x => x.year.contains(yearList));
//as a loop
foreach(var d in data){
if(d.year.Contains(yearList))
...
}
"If a single year contains a list of years..."
So, then you're working with things like LINQ Where and Select, think of them as like a loop operating over the list of things, and you provide a mini-method that accepts a single parameter, of whatever is in the collection, and returns something else. LINQ will loop over the collection calling your method once per item, passing in the item. You get to choose what the item is called
This is why it helps to have plural names for collections and singular names as the argument in the mini-method (lambda).
var cars = //list of cars
cars.Where(car => car.Maker == "Ford");
Avoid using bland names like x, because it *doesn't help you keep straight, in your head, what things are. Take a look at this:
var cars = //list of cars
var seekingMakers = new []{"Ford", GM"};
cars.Where(car => seekingMakers.Any(seekingMaker => car.Maker == seekingMaker));
We've named the outer (car) and the inner (seekingMaker) to help keep them apart and remind us what they are. seekingMaker is a single string in an string array seekingMakers.
So, for your Where you have to return a boolean from your lambda, and that is used to determine whether the item in the list makes it into the output or not
var winners = //your list of people/year
winners.Where(winner => yearList.Contains(winner.Year));
You can't say winner.Year.Contains(yearList) - you could say winner.Year.IsContainedWithin(yearList) - but there's no such thing as IsContainedWithin - it's the other way round: collection.Contains(singular) - not "singular contains collection"
Now, you never said what datatype "Year" was in your list of people - maybe it's a string, in which case it makes sense why you'd have yearList as a string.. but if it's not a string, then I strongly recommend you avoid having it be a different data type, because converting it in place will clutter up your LINQ statement, and also lower performance, because C# will spend a large amount of time converting values over and over again
At any time you want to run a search against some fixed value, match the datatype of the value to the data type of the data being searched:
Do this:
var winners = //your list of people/year with INTEGER years
var years = new[]{ 2012, 2015 };
winners.Where(winner => yearList.Contains(winner.Year));
Not these:
var winners = //your list of people/year with INTEGER years
var years = new[]{ "2012", "2015" };
//don't convert the winner's Year
winners.Where(winner => yearList.Contains(winner.Year.ToString()));
//really don't parse the string years to ints over and over
winners.Where(winner => yearList.Select(stringYear => int.Parse(stringYear)).Contains(winner.Year));
If your data is coming to you as strings, convert it once:
var winners = //your list of people/year with INTEGER years
//pass every item to int.Parse and capture the result to an array you can reuse
var years = stringYearsFromElsewhere.Select(int.Parse).ToArray();
winners.Where(winner => yearList.Contains(winner.Year));
Particularly when working with LINQ, strive to find ways to make the code read like a book..
I have the following code:
var linqResults = (from rst in QBModel.ResultsTable
group rst by GetGroupRepresentation(rst.CallerZipCode, rst.CallerState) into newGroup
select newGroup
).ToList();
With the grouping method:
private string[] GetGroupRepresentation(string ZipCode, string State)
{
string ZipResult;
if (string.IsNullOrEmpty(ZipCode) || ZipCode.Trim().Length < 3)
ZipResult = string.Empty;
else
ZipResult = ZipCode.Substring(0, 3);
return new string[]{ ZipResult, State };
}
This runs just fine but it does not group at all. The QBModel.ResultsTable has 427 records and after the linq has run linqResults still has 427. In debug I can see double-ups of the same truncated zip code and state name. I'm guessing it has to do with the array I'm returning from the grouping method.
What am I doing wrong here?
If I concatenate the return value of the truncated zip code and state name without using an array I get 84 groupings.
If I strip out the rst.CallerState argument and change the grouping method to:
private string GetGroupRepresentation(string ZipCode)
{
if (string.IsNullOrEmpty(ZipCode) || ZipCode.Trim().Length < 3)
return string.Empty;
return ZipCode.Substring(0, 3);
}
It will return me 66 groups
I don't really want to concatenate the group values as I want to use them seperately later, this is wrong as it is based on if the array worked, however, kind of like the following:
List<DataSourceRecord> linqResults = (from rst in QBModel.ResultsTable
group rst by GetGroupRepresentation(rst.CallerZipCode, rst.CallerState) into newGroup
select new MapDataSourceRecord()
{
State = ToTitleCase(newGroup.Key[1]),
ZipCode = newGroup.Key[0],
Population = GetZipCode3Population(newGroup.Key[0])
}).ToList();
Array is reference type, so when the grouping method compare two arrays with same values it can not determine they are the same, because the references are different. you can read more here
One solution would be considering a class instead of using an array for results of function, and use another class to compare your results implementing the IEqualityComparer Interface, and pass it to GroupBy method, so that the grouping method can find which combinations of ZipCode and State are really equatable. read more
Not sure if this will work because I can not replicate your code.
but maybe it will be easier to add a group key and your string[] in seperate variables before you go forth your grouping. like this.
var linqdatacleanup = QBModel.ResultsTable.Select(x=>
new {
value=x,
Representation = GetGroupRepresentation(rst.CallerZipCode, rst.CallerState),
GroupKey= GetGroupRepresentationKey(rst.CallerZipCode, rst.CallerState)
}).ToList();
so GetGroupRepresentationKey returns a single string and your GetGroupRepresentation returns your string[]
this will allow you to do your grouping on this dataset and access your data as you wanted.
but before you spend to much time on this check this stack overflow question. maybe it will help
GroupBy on complex object (e.g. List<T>)
I am having a nightmare in building an efficient search query. I am using LINQ. The requirement is as follows:
In the application there is one text fox field which is used as a Quick Search.
The table I will be searching holds three fields Make, Model and Extension.
A typical keyword user can enter is like Honda Accord XL
Based on the keywords database should return me the matching rows and here the problem starts. There is no restriction on the order which keywords will be entered to prepare the phrase, i.e. one can enter Accord XL Honda or it could be like XL Accord or could just be Honda. In the example Honda is Make, Accord is the Model and XL is the extension.
Ideally the search result should only pull up perfect matches like if Honda Accord is entered it will not bring up other models from Honda. But the major problem is I don't know what they will enter and I have to look into three different columns of the table using Contains operator.
Here is what I tried:
I spit the phrase into words and place them in an array
var arr = keyWord.Split(new [] {' '}). Next step I build the query inside a loop of those array elements:
foreach (var k in arr)
{
var item = new Vehicle();
var arrayItem = k;
var query = DataContext.Vehicles.Where(v =>v.RealName.Contains(arrayItem)
|| v.Model.Contains(arrayItem)
|| v.Style.Contains(arrayItem)).ToList();
foreach (var v in query)
{
if(!result.Contains(v))
result.Add(v);
}
}
return result;
Now when the loop is executing and matching records for Make it already fills the list with say 250 items. But how can I remove unwanted items like when a record has CT as Model or TYL as Extension? If I knew the order of the words in which the keyword was created then I will have the option to remove unmatched Make, Model or Extension from the list by using one line of code for each and return the final result. But in this case if I have to do it I again have to use loop and remove unmatched items, even that will not probably give me the correct data. And definitely this is not the efficient way to do this.
--- Try to use Below way your code first retrieve respective make to database ------then filter gettting previous result.
List<Vehicle> lsvehicle= new List<Vehicle()>;
foreach (var k in arr)
{
var arrayItem = k;
lsvehicle = DataContext.Vehicles.Where(v =>v.RealName.Contains(arrayItem) ).ToList();
}
foreach (var k in arr)
{
lsvehicle = lsvehicle.Where(v =>v.Model.Contains(arrayItem) || v.Style.Contains(arrayItem)).tolist();
}
return lsvehicle ;
This can be achieved by concatenating the Make + Model + Extension string and then comparing that the whole array is contained by this string
var query = DataContext.Vehicles;
foreach (var k in arr)
{
var item = new Vehicle();
var arrayItem = k;
query = query.Where(v => (v.RealName + v.Model + v.Style).Contains(arrayItem)).ToList();
}
return query;
NOTE: Logical answer, may require syntax error correction if any
I would suggest following approach, assuming you have the option to create view in database
and you are searching through three columns
1)Create a view With all combination
2)Use linq to get the record from the view
sql
create view [Vw_VehicleSearch]
AS
Select
M+V+E [MVE],
M+E+V [MEV],
V+M+E [VME],
v+E+M [VEM],
E+M+V [EMV],
E+V+M [EVM]
from
vehicletable
c#
public List<string> Search(string quickSearchText)
{
using(var ctx=new model()))
{
var result=ctx
.Vw_VehicleSearch
.Where(v=>v.MVE.Contains(quickSearchText)
|| v=>v.MEV.Contains(quickSearchText)
.|| v=>v.VME.Contains(quickSearchText)
.|| v=>v.VEM.Contains(quickSearchText)
.|| v=>v.EMV.Contains(quickSearchText)
.|| v=>v.EVM.Contains(quickSearchText)
.ToList();
return result.Select(r=>r.MVE).ToList();
}
}
What you want is: all vehicles of which either RealName, or Model, or Style contains all keywords. This can be achieved by:
var query = DataContext.Vehicles.Where(v =>
arr.All(s => v.RealName.Contains(s))
|| arr.All(s => v.Model.Contains(s))
|| arr.All(s => v.Style.Contains(s)))
.ToList();
Entity Framework is able to translate this query into SQL because it has a trick to convert the array arr into a table (of sorts) that can be used in the SQL statement (you should take a look at the generated SQL).
This is not the most efficient way to run a query. It will become considerably slower when the number of keywords becomes "large". I don't think that will be an issue here though.
Edit: I changed my inputs from a
List<string>
to a List<int>. They should always be valid integers now, no empty/null etc.
I currently have a 6 item list, the items consistent of integers.
I also have an object that contains a coma delimited string of integers. I'm trying to match the inputs to this list.
For example
Inputs could be, 1000,2000,3000
The object contains 1000,2000,3000,4000
I would want this to match.
If my input is 1000,2000,3000,4000 and my object only contains 1000,2000,3000 -> it should not match.
Here's my current code to create the object using linq to xml. "TaxUnitIdList" is the comma delimited string. (This is when my inputs were strings)
var obj = (from tug in tugElem.Descendants("CodeData")
select new TaxUnitGroups
{
Code = (string)tug.Attribute("code"),
CodeID = (string)tug.Attribute("codeid"),
Desc = (string)tug.Attribute("desc"),
TaxUnitIdList = (string)tug.Attribute("taxunits")
});
I'm thinking something along the following or a join (which I could not get to work) this is failing me due to the where wanting a boolean (which makes sense)
var matchedTugs = (from tug in tugElem.Descendants("CodeData")
let TaxUnitIdList = (string)tug.Attribute("taxunits")
let taxArr = TaxUnitIdList.Split(',').Select(int.Parse)
where taxArr.Contains(inputTaxUnits.All()) //This is where I screw up
select new TaxUnitGroups
{
Code = (string)tug.Attribute("code"),
CodeID = (string)tug.Attribute("codeid"),
Desc = (string)tug.Attribute("desc"),
TaxUnitIdList = (string)tug.Attribute("taxunits")
}).ToList();
Try this in the Where clause:
TaxUnitIdList.Split(',')
.Select(s => int.Parse(s))
.Except(inputTaxUnits.Where(s=>int.TryParse(s, out tempInteger))
.Select(s=>int.Parse(s)))
.Any();
Basically, we want to covert the TaxUnitId list into an integer array, convert the input list into a good (tryparse) input array as well, find the difference and verify that the result is 0.
I know, this is very simple for you guys.
Please consider the following code:
string[] str = { "dataReader", "dataTable", "gridView", "textBox", "bool" };
var s = from n in str
where n.StartsWith("data")
select n;
foreach (var x in s)
{
Console.WriteLine(x.ToString());
}
Console.ReadLine();
Supposedly, it will print:
dataReader
dataTable
right?
What if for example I don't know the data, and what the results of the query will be (but I'm sure it will return some results) and I just want to print the second item that will be produced by the query, what should my code be instead of using foreach?
Is there something like array-indexing here?
You're looking forEnumerable.ElementAt.
var secondMatch = str.Where(item => item.StartsWith("data")) //consider null-test
.ElementAt(1);
Console.WriteLine(secondMatch); //ToString() is redundant
SinceWherestreams its results, this will be efficient - enumeration of the source sequence will be discontinued after the second match (the one you're interested in) has been found.
If you find that the implicit guarantee you have that the source will contain two matches is not valid, you can use ElementAtOrDefault.
var secondMatch = str.Where(item => item.StartsWith("data"))
.ElementAtOrDefault(1);
if(secondMatch == null) // because default(string) == null
{
// There are no matches or just a single match..
}
else
{
// Second match found..
}
You could use array-indexing here as you say, but only after you load the results into... an array. This will of course mean that the entire source sequence has to be enumerated and the matches loaded into the array, so it's a bit of a waste if you are only interested in the second match.
var secondMatch = str.Where(item => item.StartsWith("data"))
.ToArray()[1]; //ElementAt will will work too
you got a few options:
s.Skip(1).First();
s.ElementAt(1);
The first is more suited for scenarios where you want X elements but after the y first elements. The second is more clear when you just need a single element on a specific location