Converting an array and performing a contains - c#

I am reading some data from a database table. One of the fields in the database "VendorList" returns a comma seperated list of Vendors or just one id.
Ex: "1256,553,674" or "346"
There are a couple things I need to do:
Convert this string to an int[]
Perform a "Contains" against an IEnumerable collection.
Return that collection and assign it to a property.
This code is being called inside of a .Select when creating a new object and "Vendor" is a property on that new object.
Here is my code that I am currently using:
Vendors = (m.VendorList.Contains(","))
? (from v in vendors
where m.VendorList.Split(',')
.Select(n => Convert.ToInt32(n))
.ToArray()
.Contains(v.VendorID)
select v).ToList()
: (string.IsNullOrEmpty(m.VendorList))
? null
: (from s in vendors
where s.VendorID == int.Parse(m.VendorList)
select s).ToList()
The code works but it looks very messy and it will be hard to maintain if another developer were to try and refactor this.
I am sort of new to linq, can you provide any tips to clean up this mess?
As you can see I am using two ternary operators. The first one is to detect if its a comma separated list. The second is to detect if the comma separated list even have values.

Try this. I believe it's equivalent to what you're trying to do.. correct me if I'm wrong.
You could do the following in a single line of code, but I think it's more readable (maintainable) this way.
var Vendors = new List<int>();
if (m.VendorList != null)
Vendors.AddRange(vendors.Where(v => m.VendorList
.Split(',')
.Select(y => Convert.ToInt32(y))
.Contains(v))
.Select(v => v));

Vendors = from v in vendors
let vendorList = from idString in m.Split(',')
select int.Parse(idString)
where vendorList.Contains(v.VendorID)
select v;
There is no need to check for the presence of ",".

This is a case where I'd suggest pulling part of this out of your LINQ statement:
var vendorIds = m.VendorList
.Split(new[]{','}, StringSplitOptions.RemoveEmptyEntries)
.Select(n => Convert.ToInt32(n))
.ToArray();
someObj.Vendors = vendors.Where(v => vendorIds.Contains(v.VendorID));
This is more readable. By assigning a variable to vendorIds, you indicate to future programmers what this variable means. They don't have to fully grok all your LINQ code before they can understand the general intent.
This will perform better. In your original code, you are re-parsing the entire vendor list twice for each value in vendors. This code parses it once, and reuses the data structure for all of your ID checks. (If you have large lists of vendor IDs, you can further improve performance by making vendorIds a HashSet<>.)
If your input is an empty string, the RemoveEmptyEntries part will ensure you end up with an empty list of vendor IDs, and hence no matching Vendors. If your input has only one value without commas, you'll end up with a single ID in the list.
Note that this will not behave exactly like your original code, in that it won't set the value to null if given a null or empty m.VendorList. I'm guessing that if you take time to think about it, having a null m.VendorList is not actually something you expect to happen, and it'd be better to "fail fast" if it ever did happen, rather than be left wondering why your .Vendors property ended up null. I'm also guessing that if you have an empty .Vendors property, it will be easier for consuming code to deal with correctly than if they have to check for null values.

You can try this:
string str = "356"; //"1256,553,674";
string[] arr = str.Split(',');
List<int> lst = new List<int>();
foreach (string s in arr)
{
lst.Add(Convert.ToInt32(s));
}
List will contain all numbers in your string

string str = "1256,553,674";
IEnumerable<int> array = str.Split(',').Select(n => Convert.ToInt32(n)).ToArray();

Related

How can I use Linq to find items with matching words from a comma separated string?

I am trying to write a Linq query to find items with the string property Tags containing at least one of the words in a comma separated string, queryTags.
The property Tags is also a comma separated string, and a value could be "toy,ball,red,plastic".
The search-string queryTags could be for instance "red,ball". In this case, the item should be selected because both "red" and "ball" matches.
This is what I have so far:
Model
public class WebContentsImage
{
public int Id { get; set; }
public string Tags { get; set; } // E.g. "toy,ball,red,plastic"
// some more properties
}
Query
List<WebContentsImage> Images = await db.WebContentsImages
.Where(t => t.Tags.Any(queryTags.Contains))
.ToListAsync()
.ConfigureAwait(false);
I don't really understand what the query above should be doing. I have just copied it from this answer, hoping it aligned with what I am trying to do. Anyway, I get this runtime exception from it:
NotSupportedException: Could not parse expression 't.Tags.Any(Convert(__CreateDelegate_0, Func`2))': The given arguments did not match the expected arguments: Object of type 'System.Linq.Expressions.UnaryExpression' cannot be converted to type 'System.Linq.Expressions.LambdaExpression'.
Previously, I had this:
List<WebContentsImage> Images = await db.WebContentsImages
.Where(t => t.Tags.Contains(queryTags.Split(",")))
.ToListAsync()
.ConfigureAwait(false);
... which has this compile time error:
Argument 1: cannot convert from string[] to char
And this (which is just plain wrong):
List<WebContentsImage> Images = await db.WebContentsImages
.Where(t => t.Tags.Contains(queryTags, StringComparison.CurrentCultureIgnoreCase))
.ToListAsync()
.ConfigureAwait(false);
You’re using a db it seems, so you need to remain mindful of what can and what cannot be converted into an SQL
You might find better success with something like:
var x = db.WebContentsImages;
foreach(string tag in queryTags.Split(','))
x = x.Where(t => (',' + t.Tags + ',').Contains(',' + tag + ',', StringComparison.CurrentCultureIgnoreCase));
var list = x.ToListAsync();
The repeated where will act cumulatively and hopefully generate a set of sql like:
SELECT * FROM table
WHERE ','+tags+',' LIKE '%,red,%' AND
','+tags+',' LIKE '%,ball,%'
I should point out though that this is a fairly horrific way to store the data in the db and ask it to return things based on string LIKE.. tags should really have their own table and then a middleman table maps what Things have which Tags
You’re becoming confused with the current structure because string has a Contains method that returns true if a substring exists within this string, and Linq extends ienumerable collections to also have a different Contains method that tells whether a collection Contains a particular element. You’re mixing the two and asking string Contains to report on whether the string Contains an array etc. You need to be using the LINQ Contains all the time, which means splitting your Tags on comma and then asking if the resulting array of strings contains all of the array of strings resulting from splitting the queryTags on comma too
The problem is that while that can happen on the client I doubt your ORM will be able to carry out a split on the server side which means it will have to drag the entire db table down to the client. This is why I went the other way and converted everything to use String Contains in the hopes that it will become a bunch of LIKE AND on the server..
It would be better not to store the data this way
If Tags and queryTags were some kind of IEnumerable of string, you’d stand a better chance of saying
products.Where(
product => queryTags.All(queryTag => product.Tags.Contains(queryTag)
)
In English this is “for all products, filter to only products where all of the queryTags are present in the product.Tags list
You could manipulate your current data thus; make queryTags a string result from splitting the query string on comma, and rename your Tags csv string as TagsCsv and make a new property called Tags that returns TagsCsv.Split(',') but I wholly expect that it would have to be executed on the client, not in the db
var queryTags = “red,ball”.Split(',');
//horrific, for illustration purposes only
class Product{
String TagsCsv = “red,plastic,round,ball”;
String[] Tags { get { return TagsCsv.Split(',') } }
}
this should do it
List<WebContentsImage> Images = await db.WebContentsImages
.Where(i => i.Tag.Split(",").Any(t => queryTags.Split(",").Contains(t)));
.ToListAsync()
.ConfigureAwait(false);
you were nearly there

IEnumerable<T>.Union(IEnumerable<T>) overwrites contents instead of unioning

I've got a collection of items (ADO.NET Entity Framework), and need to return a subset as search results based on a couple different criteria. Unfortunately, the criteria overlap in such a way that I can't just take the collection Where the criteria are met (or drop Where the criteria are not met), since this would leave out or duplicate valid items that should be returned.
I decided I would do each check individually, and combine the results. I considered using AddRange, but that would result in duplicates in the results list (and my understanding is it would enumerate the collection every time - am I correct/mistaken here?). I realized Union does not insert duplicates, and defers enumeration until necessary (again, is this understanding correct?).
The search is written as follows:
IEnumerable<MyClass> Results = Enumerable.Empty<MyClass>();
IEnumerable<MyClass> Potential = db.MyClasses.Where(x => x.Y); //Precondition
int parsed_key;
//For each searchable value
foreach(var selected in SelectedValues1)
{
IEnumerable<MyClass> matched = Potential.Where(x => x.Value1 == selected);
Results = Results.Union(matched); //This is where the problem is
}
//Ellipsed....
foreach(var selected in SelectedValuesN) //Happens to be integer
{
if(!int.TryParse(selected, out parsed_id))
continue;
IEnumerable<MyClass> matched = Potential.Where(x => x.ValueN == parsed_id);
Results = Results.Union(matched); //This is where the problem is
}
It seems, however, that Results = Results.Union(matched) is working more like Results = matched. I've stepped through with some test data and a test search. The search asks for results where the first field is -1, 0, 1, or 3. This should return 4 results (two 0s, a 1 and a 3). The first iteration of the loops works as expected, with Results still being empty. The second iteration also works as expected, with Results containing two items. After the third iteration, however, Results contains only one item.
Have I just misunderstood how .Union works, or is there something else going on here?
Because of deferred execution, by the time you eventually consume Results, it is the union of many Where queries all of which are based on the last value of selected.
So you have
Results = Potential.Where(selected)
.Union(Potential.Where(selected))
.Union(potential.Where(selected))...
and all the selected values are the same.
You need to create a var currentSelected = selected inside your loop and pass that to the query. That way each value of selected will be captured individually and you won't have this problem.
You can do this much more simply:
Reuslts = SelectedValues.SelectMany(s => Potential.Where(x => x.Value == s));
(this may return duplicates)
Or
Results = Potential.Where(x => SelectedValues.Contains(x.Value));
As pointed out by others, your LINQ expression is a closure. This means your variable selected is captured by the LINQ expression in each iteration of your foreach-loop. The same variable is used in each iteration of the foreach, so it will end up having whatever the last value was. To get around this, you will need to declare a local variable within the foreach-loop, like so:
//For each searchable value
foreach(var selected in SelectedValues1)
{
var localSelected = selected;
Results = Results.Union(Potential.Where(x => x.Value1 == localSelected));
}
It is much shorter to just use .Contains():
Results = Results.Union(Potential.Where(x => SelectedValues1.Contains(x.Value1)));
Since you need to query multiple SelectedValues collections, you could put them all inside their own collection and iterate over that as well, although you'd need some way of matching the correct field/property on your objects.
You could possibly do this by storing your lists of selected values in a Dictionary with the name of the field/property as the key. You would use Reflection to look up the correct field and perform your check. You could then shorten the code to the following:
// Store each of your searchable lists here
Dictionary<string, IEnumerable<MyClass>> DictionaryOfSelectedValues = ...;
Type t = typeof(MyType);
// For each list of searchable values
foreach(var selectedValues in DictionaryOfSelectedValues) // Returns KeyValuePair<TKey, TValue>
{
// Try to get a property for this key
PropertyInfo prop = t.GetProperty(selectedValues.Key);
IEnumerable<MyClass> localSelected = selectedValues.Value;
if( prop != null )
{
Results = Results.Union(Potential.Where(x =>
localSelected.Contains(prop.GetValue(x, null))));
}
else // If it's not a property, check if the entry is for a field
{
FieldInfo field = t.GetField(selectedValues.Key);
if( field != null )
{
Results = Results.Union(Potential.Where(x =>
localSelected.Contains(field.GetValue(x, null))));
}
}
}
No, your use of union is absoloutely correct.
The only thing to keep in mind is it excludes duplicates as based on the equality operator. Do you have sample data?
Okay, I think you are are haveing a problem because Union uses deferred execution.
What happens if you do,
var unionResults = Results.Union(matched).ToList();
Results = unionResults;

List of classes question c#

I have a class contain many variables, something like that
class test
{
internal int x , y ;
internal string z;
}
I created a list of this class list<test> c
I want to do the following:
test if all the list items contain the same x
get the list's item that has z = "try"
I need a quick and fast way , instead of iterate though the entire items
Any suggestion please ,
LINQ to Objects is your friend. For the first:
bool allSameX = list.All(t => t.x == list[0].x);
Test firstTry = list.First(t => t.z == "try");
Test firstTryOrNull = list.FirstOrDefault(t => t.z == "try");
The first one depends on there being at least one value of course. Alternatives might be:
bool allSameX = !list.Select(t => t.x)
.Distinct()
.Skip(1)
.Any();
In other words, once you've gone past the first distinct value of x, there shouldn't be any more. One nice aspect of this is that as soon as it spots the second distinct value, it will stop looking - as does the first line (the All version) of course.
LINQ is wonderfully flexible, and well worth looking into closely.
EDIT: If you need to do the latter test ("find an element with a particular value for z") for multiple different values, you might want a dictionary or a lookup, e.g.
// If there are duplicate z values
var lookup = list.ToLookup(t => t.z);
// If z values are distinct
var dictionary = list.ToDictionary(t => t.z);
Without some pre-work, there's no way of performing the queries you want without iterating over at least some of the list.
You can use linq. Here is a link to small examples that will help you a lot for future too http://msdn.microsoft.com/en-us/vcsharp/aa336746
You could implement a custom collection class instead of a list, and put the search smarts into this e.g.
add a method AllItemsHaveSameX() and a private bool field allItemsHaveSameX
expose a dictionary keyed by the search strings with the index of the item that has that value.
When adding/removing items:
You would re-evaluate allItemsHaveSameX
Add/remove from your private dictionary.

Concatenate collection of XML tags to string with LINQ

I'm stuck with using a web service I have no control over and am trying to parse the XML returned by that service into a standard object.
A portion of the XML structure looks like this
<NO>
<L>Some text here </L>
<L>Some additional text here </L>
<L>Still more text here </L>
</NO>
In the end, I want to end up with one String property that will look like "Some text here Some additional text here Still more text here "
What I have for an initial pass is what follows. I think I'm on the right track, but not quite there yet:
XElement source = \\Output from the Webservice
List<IndexEntry> result;
result = (from indexentry in source.Elements(entryLevel)
select new IndexEntry()
{
EtiologyCode = indexentry.Element("IE") == null ? null : indexentry.Element("IE").Value,
//some code to set other properties in this object
Note = (from l in indexentry.Elements("NO").Descendants
select l.value) //This is where I stop
// and don't know where to go
}
I know that I could add a ToList() operator at the end of that query to return the collection. Is there an opertaor or technique that would allow me to inline the concatentation of that collection to a single string?
Feel free to ask for more info if this isn't clear.
Thanks.
LINQ to XML is indeed the way here:
// Note: in earlier versions of .NET, string.Join only accepts
// arrays. In more modern versions, it accepts sequences.
var text = string.Join(" ", topElement.Elements("L").Select(x => x.Value));
EDIT: Based on the comment, it looks like you just need a single-expression way of representing this. That's easy, if somewhat ugly:
result = (from indexentry in source.Elements(entryLevel)
select new IndexEntry
{
EtiologyCode = indexentry.Element("IE") == null
? null
: indexentry.Element("IE").Value,
//some code to set other properties in this object
Note = string.Join(" ", indexentry.Elements("NO")
.Descendants()
.Select(x => x.Value))
};
Another alternative is to extract it into a separate extension method (it has to be in a top-level static class):
public static string ConcatenateTextNodes(this IEnumerable<XElement> elements) =>
string.Join(" ", elements.Select(x => x.Value));
then change your code to:
result = (from indexentry in source.Elements(entryLevel)
select new IndexEntry
{
EtiologyCode = indexentry.Element("IE") == null
? null
: indexentry.Element("IE").Value,
//some code to set other properties in this object
Note = indexentry.Elements("NO")
.Descendants()
.ConcatenateTextNodes()
}
EDIT: A note about efficiency
Other answers have suggested using StringBuilder in the name of efficiency. I would check for evidence of this being the right way to go before using it. If you think about it, StringBuilder and ToArray do similar things - they create a buffer bigger than they need to, add data to it, resize it when necessary, and come out with a result at the end. The hope is that you won't need to resize too often.
The difference between StringBuilder and ToArray here is what's being buffered - in StringBuilder it's the entire contents of the string you've built up so far. With ToArray it's just references. In other words, resizing the internal buffer used for ToArray is likely to be cheaper than resizing the one for StringBuilder, particularly if the individual strings are long.
After doing the buffering in ToArray, string.Join is hugely efficient: it can look through all the strings to start with, work out exactly how much space to allocate, and then concatenate it without ever having to copy the actual character data.
This is in sharp contrast to a previous answer I've given - but unfortunately I don't think I ever wrote up the benchmark.
I certainly wouldn't expect ToArray to be significantly slower, and I think it makes the code simpler here - no need to use side-effects etc, aggregation etc.
I don't have experience with it myself, but it strikes me that LINQ to XML could vastly simplify your code. Do a select of XML document, then loop through it and use a StringBuilder to append the L element to some string.
The other option is to use Aggregate()
var q = topelement.Elements("L")
.Select(x => x.Value)
.Aggregate(new StringBuilder(),
(sb, x) => return sb.Append(x).Append(" "),
sb => sb.ToString().Trim());
edit: The first lambda in Aggregate is the accumulator. This is taking all of your values and creating one value from them. In this case, it is creating a StringBuilder with your desired text. The second lambda is the result selector. This allows you to translate your accumulated value into the result you want. In this case, changing the StringBuilder to a String.
I like LINQ as much as the next guy, but you're reinventing the wheel here. The XmlElement.InnerText property does exactly what's being asked for.
Try this:
using System.Xml;
class Program
{
static void Main(string[] args)
{
XmlDocument d = new XmlDocument();
string xml =
#"<NO>
<L>Some text here </L>
<L>Some additional text here </L>
<L>Still more text here </L>
</NO>";
d.LoadXml(xml);
Console.WriteLine(d.DocumentElement.InnerText);
Console.ReadLine();
}
}

How can I convert anonymous type to strong type in LINQ?

I have an array of ListViewItems ( ListViewItem[] ), where I store a SalesOrderMaster object in each ListViewItem.Tag for later reference.
I have some code that right now, goes through each ListViewItem safely casts the .Tag property into a SalesOrderMaster object, then adds that object to a collection of SalesOrders, only after checking to make sure the order doesn't already exist in that collection.
The process to compare sales orders is expensive, and I would like to convert this to a LINQ expression for clarity and performance. ( I also have the Parallel Extensions to .NET Framework 3.5 installed so I can use that to further improve LINQ performance)
So without further ado: This is what I have, and then what I want. ( what I want won't compile, so I know I am doing something wrong, but I hope it illustrates the point )
What I have: ( Slow )
foreach (ListViewItem item in e.Argument as ListViewItem[])
{
SalesOrderMaster order = item.Tag as SalesOrderMaster;
if ( order == null )
{
return;
}
if (!All_SalesOrders.Contains(order))
{
All_SalesOrders.Add(order);
}
}
What I want: ( Theory )
List<SalesOrderMaster> orders =
(from item in (e.Argument as ListViewItem[]).AsParallel()
select new { ((SalesOrderMaster)item.Tag) }).Distinct();
EDIT: I know the cast is cheap, I said the "Compare", which in this case translates to the .Contains(order) operation
EDIT: Everyone's answer was awesome! I wish I could mark more than one answer, but in the end I have to pick one.
EDIT : This is what I ended up with:
List<SalesOrderMaster> orders =
(from item in (e.Argument as ListViewItem[]) select (SalesOrderMaster) item.Tag).GroupBy(item => item.Number).Select(x => x.First()).ToList();
I see nobody has addressed your need to convert an anonymous type to a named type explicitly, so here goes... By using "select new { }" you are creating an anonymous type, but you don't need to. You can write your query like this:
List<SalesOrderMaster> orders =
(from item in (e.Argument as ListViewItem[]).AsParallel()
select (SalesOrderMaster)item.Tag)
.Distinct()
.ToList();
Notice that the query selects (SalesOrderMaster)item.Tag without new { }, so it doesn't create an anonymous type. Also note I added ToList() since you want a List<SalesOrderMaster>.
This solves your anonymous type problem. However, I agree with Mark and Guffa that using a parallel query here isn't you best option. To use HashSet<SalesOrderMaster> as Guffa suggested, you can do this:
IEnumerable<SalesOrderMaster> query =
from item in (ListViewItem[])e.Argument
select (SalesOrderMaster)item.Tag;
HashSet<SalesOrderMaster> orders = new HashSet<SalesOrderMaster>(query);
(I avoided using var so the returned types are clear in the examples.)
The part in that code that is expensive is calling the Contains method on the list. As it's an O(n) operation it gets slower the more objects you add to the list.
Just use a HashSet<SalesOrderMaster> for the objects instead of a List<SalesOrderMaster>. The Contains method of the HashSet is an O(1) operation, so your loop will be an O(n) operation instead of an O(n*n) operation.
Like Marc Gravell said, you shouldn't access the Tag property from different threads, and the cast is quite cheap, so you have:
var items = (e.Argument as ListViewItem[]).Select(x=>x.Tag)
.OfType<SalesOrderMaster>().ToList();
but then, you want to find distinct items - here you can try using AsParallel:
var orders = items.AsParallel().Distinct();

Categories