IEnumerable<Hashtable> to DictionaryEntry using Linq - c#

I'm trying to use Linq to get all DictionaryEntry objects from an IEnumerable<Hashtable> but for the life of me can't figure out how to do it.
I mean the most obvious thing for me would be to use .SelectMany(x => x).Cast<DictionaryEntry>() to try to flatten it, but that doesn't work!
Anyone has any ideas how this can be done using Linq alone?
EDIT
I've got the following code that works, trying to convert this to Linq:
var t = new Dictionary<string, object>();
foreach (PSObject obje in objects)
{
foreach (DictionaryEntry entry in (Hashtable) obje.BaseObject)
{
t.Add((string) entry.Key, entry.Value);
}
}
return t;
I've got this far with trying to convert this to Linq:
objects.Cast<PSObject>().Select(x => (Hashtable) x.BaseObject), which leaves me with a IEnumerable<Hashtable> but like I said, can't find a way to get the DictionaryEntry objects from there.

Would something like this work for you?
var t = objects
.Select(e => e.BaseObject)
.OfType<Hashtable>()
.SelectMany(e => e.Cast<DictionaryEntry>())
.ToDictionary(e => (string)e.Key, e => e.Value);
It:
Selects all of the BaseObject values.
Finds all of type Hashtable
Selects all of the DictionaryEntry values.
Combines them all into a dictionary.
Note that you will get an ArgumentException if there is a duplicate key in any of the Hashtables. That is an issue with your existing solution too, however. You could get around this by using .GroupBy(...) and selecting the first item, or making a decision in some other way.
You will also get an InvalidCastException when you try to convert the key to a string if the key is not a string. This is also an issue with your current solution. You could add a .Where(e => e.Key is string) below the .SelectMany(...) to filter out any entries with keys that are not strings.

Related

Check if keys exists in dictionary

I have a legacy code which converts lists to dictionary by doing some manipulation as shown below
var items = await processTask;
var itemDict = items.ToDictionary(dto => dto.ClientId, dto => mapper.ConvertTo(dto, hj));
But recently we started to see this issue which looks like we are getting duplicate keys
An item with the same key has already been added
What's the best way to fix this so that if duplicate keys comes it should not throw exception but we can log it. Can we do this in linq or it has to be done in for loop?
Unfortunately, you can't eliminate dups while in ToDictionary. You have to do something before ToDictionary to eliminate it, like call Distinct or similar. But may be better to have an explicit loop, where you get opportunity to do something with a dupe
var dict = new Dictionary<int, string>(); // whatever mapper converts to
foreach(var dto in items)
{
if (dict.ContainsKey(dto.ClientId))
{
// log duplicate here or do something
continue;
}
dict.Add(dto.ClientId, mapper.ConvertTo(dto, hj));
}
You can do it with LINQ using GroupBy:
var itemDict = items
.GroupBy(dto => dto.ClientId)
.ToDictionary(gr => gr.Key, gr => mapper.ConvertTo(gr.First(), hj))
Also logging duplicates will make this code less elegant.

List<T> Concatenation dynamically

I am trying to concate List<> as follows-
List<Student> finalList = new List<Student>();
var sortedDict = dictOfList.OrderBy(k => k.Key);
foreach (KeyValuePair<int, List<Student>> entry in sortedDict) {
List<Student> ListFromDict = (List<Student>)entry.Value;
finalList.Concat(ListFromDict);
}
But no concatenation happens. finalList remains empty. Any help?
A call to Concat does not modify the original list, instead it returns a new list - or to be totally accurate: it returns an IEnumerable<string> that will produce the contents of both lists concatenated, without modifying either of them.
You probably want to use AddRange which does what you want:
List<Student> ListFromDict = (List<Student>)entry.Value;
finalList.AddRange(ListFromDict);
Or even shorter (in one line of code):
finalList.AddRange((List<Student>)entry.Value);
And because entry.Value is already of type List<Student>, you can use just this:
finalList.AddRange(entry.Value);
Other answers have explained why Concat isn't helping you - but they've all kept your original loop. There's no need for that - LINQ has you covered:
List<Student> finalList = dictOfList.OrderBy(k => k.Key)
.SelectMany(pair => pair.Value)
.ToList();
To be clear, this replaces the whole of your existing code, not just the body of the loop.
Much simpler :) Whenever you find yourself using a foreach loop which does nothing but build another collection, it's worth seeing whether you can eliminate that loop using LINQ.
You may want to read up the documentation on Enumerable.Concat:
Return Value
Type: System.Collections.Generic.IEnumerable
An IEnumerable that contains the concatenated elements of the two input sequences.
So you may want to use the return value, which holds the new elements.
As an alternative, you can use List.AddRange, which Adds the elements of the specified collection to the end of the List.
As an aside, you can also achieve your goal with a simple LINQ query:
var finalList = dictOfList.OrderBy(k => k.Key)
.SelectMany(k => k.Value)
.ToList();
As specified here, Concat generates a new sequence whereas AddRange actually adds the elements to the list. You thus should rewrite it to:
List<Student> finalList = new List<Student>();
var sortedDict = dictOfList.OrderBy(k => k.Key);
foreach (KeyValuePair<int, List<Student>> entry in sortedDict) {
List<Student> ListFromDict = (List<Student>)entry.Value;
finalList.AddRange(ListFromDict);
}
Furthermore you can improve the efficiency a bit, by omitting the cast to a List<T> object since entry.Value is already a List<T> (and technically only needs to be an IEnumerable<T>):
var sortedDict = dictOfList.OrderBy(k => k.Key);
foreach (KeyValuePair<int, List<Student>> entry in sortedDict) {
finalList.AddRange(entry.Value);
}
Concat method does not modify original collection, instead it returns brand new collection with concatenation result. So, either try finalList = finalList.Concat(ListFromDict) or use AddRange method which modifies target list.

C# Comparison operators not supported for type Dictionary<string,string>

I get a dictionary and i want to get the matching database entries for the key field in the dictionary.
So the code below gets all database fields that equals the data in the dictionary.keys. So far so good. Now when i loop it and i try to get the field from the fields that matches the key x it fails with an exception:
{"Comparison operators not supported for type 'System.Collections.Generic.Dictionary`2+KeyCollection[System.String,System.String]'"}
var fields = _dc.fieldInfos.Where(x => x.name.Equals(param.MethodData.Keys));
foreach (var entry in param.MethodData)
{
KeyValuePair<string, string> entry1 = entry;
var field = fields.SingleOrDefault(x => x.name.Equals(entry1.Key));
if (field == null)
....
}
The line that fails is this:
var field = fields.SingleOrDefault(x => x.name.Equals(entry1.Key));
Any ideas?
Be mindful that the _dc.fieldInfos.Where(x => x.name.Equals(param.MethodData.Keys)) (line 1) doesn't actually get executed until you try to iterate over fields (line 5).
At that point, x.name.Equals(param.MethodData.Keys) fails because a string (x.name) cannot be compared to KeyCollection<string> (param.MethodData.Keys).
Essentially, you are getting the exception on unexpected line because of the deferred execution.
This is because the LINQ to SQL provider cannot translate the C# expression passed to the SingleOrDefault method into an SQL statement.
You can easily solve this problem by having LINQ to SQL execute the fields query before filtering it further with the SingleOrDefault method. This way the second operation will happen on the collection of objects in memory instead of the database:
var field = fields.ToList().SingleOrDefault(x => x.name.Equals(entry1.Key));
Related resources:
LINQ and Deferred Execution
Are you sure it's not this line that's failing:
var fields = _dc.fieldInfos.Where(x => x.name.Equals(param.MethodData.Keys));
as name isn't going to match a collection of strings.
which if so, I'd rewrite as:
var fields = _dc.fieldInfos.Where(x => param.MethodData.Keys.Contains(x.name));
entry1.key is a string, so I can't see why the line that you highlighted would fail with that message.
var fields = _dc.fieldInfos.Where(x => x.name.Equals(param.MethodData.Keys));
This retrieves (actually filters with deferred execution) fieldInfos by matching their name to a collection of keys. I don't know what fieldInfo.Name and param.MethodData.Keys mean, but i assume it's not what you want.
I'd suggest something along the lines of:
var pairs = param.MethodData.Join(_dc.fieldInfos,
kvp => kvp.Key,
fi => fi.name,
(kvp, fi) => new { Key = kvp.Key, FieldInfo = fi });
foreach (var pair in pairs)
param.MethodData[pair.Key] = pair.FieldInfo;
Im not exactly sure it's gonna work in Linq to SQL though.

C# remove duplicate dictionary items from List<>?

So I a collection of dictionary items in a list:
List<Dictionary<string, string>> inputData = new List<Dictionary<string, string>>(inputs);
List<Dictionary<string, string>> itemStack = new List<Dictionary<string, string>>();
Now what I want to do is for each inputData dictionary item I want to check if itemStack has the same value (Dictionary Item) already.
I was thinking it would be like?
foreach (var item in inputData)
{
if(!itemStack.Contains(item){ itemStack.Add(item)}
else{ //Duplicate found}
}
It doesn't really check the items values inside? It just assumes that it doesn't have it...
All i want is if itemStack contains and item that is already in the stack don't include it.
I know I'm missing something obvious.
Thanks,
Dictionary is reference type, so it doesn't check the "deep" value like you expected.
You will have to write your own "Contains" method, either as totally separate method or extension of the Dictionary itself then use it instead, for example:
if(!MyContains(itemStack, item)){ itemStack.Add(item)}
True that a HashSet would be better, but if you want to do it here, try this (assuming you are filtering duplicate keys only):
foreach (var item in inputData.Keys)
{
if (itemStack.Where(x => x.Key == item.Key).Count() > 0)
// There was a duplicate
}
Or, if you only care when the data is coming out you can call:
itemStack.Distinct()
I think, your way is right. On my mind, HashSet is good, but when you add a new element, it performs the same test on the contents of the same items.
Regards.
Based on your initial problem statement, you might do something like this:
var aggregateKnownKeys = itemStack.SelectMany(d => d.Keys);
itemStack.AddRange(
inputData.Select(d=> d.Where(p => !aggregateKnownKeys.Contains(p.Key))
.ToDictionary(p => p.Key, p => p.Value)));
If you only need to combine two dictionaries then you could do this to skip keys that exist in itemStack:
var inputData = new Dictionary<string, string>();
var itemStack = new Dictionary<string, string>();
var oldStack = itemStack;
itemStack = new[] { inputData.SkipWhile(d => oldStack.Keys.Contains(d.Key)), itemStack }
.SelectMany(d => d)
.ToDictionary(d => d.Key, d => d.Value);
Okay so this isn't quite a full answer but it's what I did.
So I have a List of items and instead of doing a full compare to whats in an List(Hence the other considered) I just did a single item check:
if(!String.IsNullOrEmpty(item["itemId"]))
{
alert.DaleksApproaching(item["itemId"]);
}
So when it does see it has a value it just does another event to get rid of it.
The idea of using LINQ and the method approaches about(Contains and Distinct)I like. I have yet to try that, but I plan on doing that. For this it doesn't use LINQ :(
Thanks everyone!

How can I convert IEnumerable<T> to List<T> in C#?

I am using LINQ to query a generic dictionary and then use the result as the datasource for my ListView (WebForms).
Simplified code:
Dictionary<Guid, Record> dict = GetAllRecords();
myListView.DataSource = dict.Values.Where(rec => rec.Name == "foo");
myListView.DataBind();
I thought that would work but in fact it throws a System.InvalidOperationException:
ListView with id 'myListView' must
have a data source that either
implements ICollection or can perform
data source paging if AllowPaging is
true.
In order to get it working I have had to resort to the following:
Dictionary<Guid, Record> dict = GetAllRecords();
List<Record> searchResults = new List<Record>();
var matches = dict.Values.Where(rec => rec.Name == "foo");
foreach (Record rec in matches)
searchResults.Add(rec);
myListView.DataSource = searchResults;
myListView.DataBind();
Is there a small gotcha in the first example to make it work?
(Wasn't sure what to use as the question title for this one, feel free to edit to something more appropriate)
Try this:
var matches = dict.Values.Where(rec => rec.Name == "foo").ToList();
Be aware that that will essentially be creating a new list from the original Values collection, and so any changes to your dictionary won't automatically be reflected in your bound control.
I tend to prefer using the new Linq syntax:
myListView.DataSource = (
from rec in GetAllRecords().Values
where rec.Name == "foo"
select rec ).ToList();
myListView.DataBind();
Why are you getting a dictionary when you don't use the key? You're paying for that overhead.
You might also try:
var matches = new List<Record>(dict.Values.Where(rec => rec.Name == "foo"));
Basically generic collections are very difficult to cast directly, so you really have little choice but to create a new object.
myListView.DataSource = (List<Record>) dict.Values.Where(rec => rec.Name == "foo");
Just adding knowledge the next sentence doesn´t recover any data from de db. Just only create the query (for that it is iqueryable type). For launching this query you must to add .ToList() or .First() at the end.
dict.Values.Where(rec => rec.Name == "foo")

Categories

Resources