How to cast result back to generic list (using linq to object)? - vb.net

Dim a As New List(Of EntityTableRow)
a = myTable1.TableRows
Dim lFinal2 = (From el In a Group el By Key = New With {Key el.Description, Key el.Activity, Key el.ServiceGroup, Key el.SubServiceGroup, Key el.ResourceGroup, Key el.Country, Key el.DCN, Key el.Workforce, Key el.RateType, Key el.LoadType} _
Into Group Select New With { _
.PageNum = "", _
.Description = Key.Description, _
.Activity = Key.Activity, _
.MonthCosts = (From k In Group.SelectMany(Function(g) g.MonthCosts.Keys).Distinct() _
Select New With {.Month = k, .Sum = Group.Sum(Function(g) _
If(g.MonthCosts.ContainsKey(k), g.MonthCosts(k), 0))}).ToDictionary(Function(i) i.Month, Function(i) i.Sum)})
I am not able to cast the above result back into the original object form from the below code:
Dim myTable1_grouped As New EntityDATATable(Of EntityTableRow)
myTable1_grouped.TableRows = CType(lFinal2, List(Of EntityTableRow))
original class is like below. The class has a number of more string properties, which I have omitted for this snippet. All those properties also are using in the grouping in above linq code.:
Public Class EntityTableRow
Public PageNum As Integer
Public Description As String
Public Activity As String
.....
.....
.....
Public MonthCosts As New Dictionary(Of Integer, Double)
End Class
The error I am getting is:
System.InvalidCastException was caught
Message="Unable to cast object of type 'WhereSelectEnumerableIterator2[VB$AnonymousType_12[VB$AnonymousType_010[System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String],System.Collections.Generic.IEnumerable1[Addin.EntityTableRow]],VB$AnonymousType_235[System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.String,System.Collections.Generic.Dictionary2[System.Int32,System.Double]]]' to type 'System.Collections.Generic.List`1[Addin.EntityTableRow]'."

There are 2 things you have to do there:
Set your class name when creating the results:
Into Group Select New EntityTableRow With { _
instead of:
Into Group Select New With { _
And add ToList() to enumerate and get results into a List<EntityTableRow>:
myTable1_grouped.TableRows = CType(lFinal2.ToList(), List(Of EntityTableRow))

Related

LINQ query is not working

I am trying to create a dictionary from a list. I am trying to filter the list such that it contains the id that I am adding as a key to the dictionary. So dictionary would be Key, List - with id as Key.
I have no idea why the following won't work - although the rawlist contains ids, filteredlist is empty. I am checking the value within the loop. Fresh set of eyes please?
resultList is dictionary(string, List)
For Each c As String In listIds
Dim filteredlist = rawList.Where(Function(x) x.id.Trim().Contains(c.Trim())).ToList()
resultList.Add(c,filteredlist)
Next
I need the filtered list. I have tried Contains, Equals and "="
i.e.
Dim filteredlist = rawList.Where(Function(x) x.id.Trim().Equals(c.Trim())).ToList()
and
Dim filteredlist = rawList.Where(Function(x) x.id.Trim() = (c.Trim())).ToList()
Please look into it thanks!
Looks like you have it backwards. Typically you have a list for looking for something first and not iterate through that list which IMHO takes longer.
Filter Linq Child Collection by another List/Array
Public Class POC
Public Property Id As Integer
Public Property Desc As String
Public Property Orders As List(Of Order)
End Class
Public Class Order
Public Property Id As Integer
Public Property Desc As String
End Class
Private Shared Function GetOrders(numberOfOrders As Integer) As List(Of Order)
Dim orders = New List(Of Order)()
For i As Integer = 1 To numberOfOrders
orders.Add(New Order() With { .Id = i, .Desc = "{i} Order" })
Next
Return orders
End Function
Private Shared Function GetPOCOsAndOrders() As List(Of POC)
Return New List(Of POC)() From { _
New POC() With { .Id = 1, .Desc = "John", .Orders = GetOrders(1) },
New POC() With { .Id = 2, .Desc = "Jane", .Orders = GetOrders(2) },
New POC() With { .Id = 3, .Desc = "Joey", .Orders = GetOrders(3) }
End Function
Private Shared Sub Main(args As String())
Dim orders = New List(Of Integer)() From { 2, 3 }
Dim items = GetPOCOsAndOrders()
Dim peopleAndOrdersWhereOrderNumberIsGreaterThanTwo = items.Where(Function(x) x.Orders.Any(Function(y) orders.Contains(y.Id)))
'I should only get the last two people out of three and their orders
peopleAndOrdersWhereOrderNumberIsGreaterThanTwo.ToList().ForEach(Function(x) Console.WriteLine("{x.Id} {x.Desc} {x.Orders.Count}"))
Console.ReadLine()
End Sub
There are many silght variations possible with the limited information you have given us, but a working example is:
Option Infer On
Option Strict On
Module Module1
Public Class datum
Property Id As String
Property Name As String
Public Sub New()
' empty constructor
End Sub
Public Sub New(Id As String, Name As String)
Me.Id = Id
Me.Name = Name
End Sub
End Class
Sub Main()
' sample data...
Dim rawData = New List(Of datum)
rawData.Add(New datum("cat", "Lulu"))
rawData.Add(New datum("cat", "Uschi"))
rawData.Add(New datum("snake", "Sid"))
rawData.Add(New datum("fox", "Reynard"))
rawData.Add(New datum("mouse", "Jerry"))
' what to look for:
Dim listIds As New List(Of String) From {"CAT", "mouse", "ELK"}
' somewhere to store the results:
Dim dict As New Dictionary(Of String, List(Of String))
' filter by what to look for:
For Each c In listIds
Dim foundItems = rawData.Where(Function(x) String.Compare(x.Id, c, StringComparison.OrdinalIgnoreCase) = 0)
' only add items to the dictionary if the Id was found:
If foundItems.Count() > 0 Then
' use the case of the id from the raw data:
dict.Add(foundItems(0).Id, foundItems.Select(Function(f) f.Name).ToList())
End If
' alternative which includes dictionary entries where the Id was not found:
'dict.Add(If(foundItems.Count = 0, c, foundItems(0).Id), foundItems.Select(Function(f) f.Name).ToList())
Next
' show the dictionary contents:
For Each kvp In dict
Console.WriteLine(kvp.Key & ": " & String.Join(", ", kvp.Value))
Next
Console.ReadLine()
End Sub
End Module
Output:
cat: Lulu, Uschi
mouse: Jerry
I have included commented-out code for some variations you may want.

How to assign linq anonymous type variable

I have a linq query that gives me an System.Collections.Generic.IEnumerable(of <Anonymous Type>). Actually I wanted to create a class to get better access to what I want form that query but I fail aready with declaring a member variable that should hold the query outcome. The query I use in the Sub New() of my class is (explained and c# version here:)
Dim jan1 = New DateTime(DateTime.Today.Year, 1, 1)
Dim startOfFirstWeek = jan1.AddDays(1 - CInt(jan1.DayOfWeek))
Dim weeks = Enumerable.Range(0, 54).[Select](Function(i) New With { _
Key .weekStart = startOfFirstWeek.AddDays(i * 7) _
}).TakeWhile(Function(x) x.weekStart.Year <= jan1.Year).[Select](Function(x) New With { _
x.weekStart, _
Key .weekFinish = x.weekStart.AddDays(4) _
}).SkipWhile(Function(x) x.weekFinish < jan1.AddDays(1)).[Select](Function(x, i) New With { _
x.weekStart, _
x.weekFinish, _
Key .weekNum = i + 1 _
})
My Class should look like:
Public Class WeekInfo
Private _weeks as ?????
end Class
Could anyone tell me what is the usual procedure to accomplish that task and how to put the find a type for my member to access the weeks variable?
VB.Net anonymous types are exposed as type Object so you're variable would be:
Dim weeks As IEnumerable(Of Object)
You will not get intellisense on the anonymous type's fields outside of the function you created them in but you can access them dynamically, i.e.
For Each week In weeks
Console.WriteLine(week.weekStart + " " + week.weekFinish)
Next
If you need to expose the type externally you should define a concrete class or struct instead.
Make your WeekInfo class look like this:
Public Class WeekInfo
Public Property WeekStart As Date
Public Property WeekEnd As Date
Public Property WeekNumber As Int32
End Class
And then load it with you linq query as list of your WeekInfo(s):
Dim jan1 = New DateTime(DateTime.Today.Year, 1, 1)
Dim startOfFirstWeek = jan1.AddDays(1 - CInt(jan1.DayOfWeek))
Dim weeks = Enumerable.Range(0, 54).Select(Function(i) New With { _
Key .weekStart = startOfFirstWeek.AddDays(i * 7) _
}).TakeWhile(Function(x) x.weekStart.Year <= jan1.Year).[Select](Function(x) New With { _
x.weekStart, _
Key .weekFinish = x.weekStart.AddDays(4) _
}).SkipWhile(Function(x) x.weekFinish < jan1.AddDays(1)).[Select](Function(x, i) New WeekInfo With { _
.WeekStart = x.weekStart, _
.WeekEnd = x.weekFinish, _
.WeekNumber = i + 1 _
}).ToList()

LINQ query to group data between two list collections, populating a nested object (VB.Net)

I have these objects:
Public Class MakeInfo
Public Property Name As String
Public Property Description As String
Public Property Stock As StockInfo
End Class
Public Class ModelInfo
Public Property Make As String
Public Property Name As String
Public Property Description As String
Public Property Stock As StockInfo
End Class
Public Class StockInfo
Public Property Count As Integer
Public Property MinPrice As Double
End Class
Using LINQ I need to take a List(Of MakeInfo) and a List(Of ModelInfo) and aggregate the StockInfo from ModelInfo into the List(Of MakeInfo).
So for each MakeInfo I will have a total count of all stock where MakeInfo.Name = ModelInfo.Make, and also a minimum price.
I think it's going to be something like this, but I'm having trouble accessing the nested object and not sure if it's going to be possible with a single query. I've tried a few variations, but here's where I'm up to:
newList = From ma In makeData _
Group Join mo In modelData _
On ma.Name Equals mo.Make _
Into Group _
Select New MakeInfo With {.Name = m.Name, _
.Description = m.Description, _
.Stock = ?}
If you are actually trying to modify the original MakeInfo objects, you should stop the query with the group. newList will have each make paired with the group of models of that make. You can iterate through newList, and for each make, aggregate the models into the StockInfo for the make.
Dim newList = From ma In makeData _
Group Join mo In modelData _
On ma.Name Equals mo.Make _
Into Group
For Each g In newList
g.ma.Stock = g.Group.Aggregate(
New StockInfo() With {.MinPrice = Double.NaN},
Function(si, model)
Return New StockInfo With {
.Count = si.Count + 1,
.MinPrice = If(Double.IsNaN(si.MinPrice) OrElse
model.Stock.MinPrice < si.MinPrice,
model.Stock.MinPrice,
si.MinPrice)}
End Function)
Next
If you actually want new instances, just change the loop to create a new item instead of modifying the existing make.
Dim results As New List(Of MakeInfo)()
For Each g In newList
Dim newMake As New MakeInfo()
newMake.Name = g.ma.Name
newMake.Description = g.ma.Description
newMake.Stock = g.Group.Aggregate( same as before )
results.Add(newMake)
Next
I eventually found the right way to do this and it doesn't require loop iteration:
Dim new_makes = From ma In makes
Join mo In models On mo.Make Equals ma.Name
Group By ma.Name, ma.Description
Into TotalCount = Sum(mo.Stock.Count), LowestPrice = Min(mo.Stock.MinPrice)
Select New MakeInfo With {.Name = Name, _
.Description = Description, _
.Stock = New StockInfo With {.Count = TotalCount, .MinPrice = LowestPrice}}
I was certain that it would be possible.

How can I get a hashtable key name from a value in VB.NET?

I have a hashtable in VB.NET and I need to get the string value of a key from it's value. For example, if I do:
hashtable.add("string1","string2")
How would I get the value "string1" if I had "string2"?
You can't (at least not without simply looping through every value). Consider the fact that multiple keys can map to the same value:
hashtable.Add("string1", "string2")
hashtable.Add("string3", "string2")
Now given "string2" what would you expect to be returned?
If you really need to do a "reverse" lookup, then the simplest solution is to probably have two hashtable, one for the "forward" lookup and one for the "reverse" lookup.
As Dean / codeka says you can't strictly do this.
However you can do something like this as the Keys and Values of a Hashtable are in the same (unspecified) order:
Hashtable ht = new Hashtable();
ht.Add("string1", "str2");
ht.Add("string2", "str2");
List<string> keys = new List<string>(ht.Keys.OfType<string>());
string key = ht.Values.OfType<string>()
.Select((htI, i) => new { Key = keys[i], Value = htI })
.Where(htKVP => htKVP.Value == "str2")
.Select(htKVP => htKVP.Key)
.FirstOrDefault();
However, you would be better using a Dictionary<string, string> just because it is generically typed and lets you get to Linq easier.
Converted for VB.NET that is:
Dim ht as new Hashtable()
ht.Add("string1", "str2")
ht.Add("string2", "str2")
Dim keys as new List(Of String)(ht.Keys.OfType(Of String)())
Dim key as String = ht.Values.OfType(Of String)() _
.Select(Function(htI, i) New With { .Key = keys(i), .Value = htI }) _
.Where(Function(htKVP) htKVP.Value = "str2") _
.Select(Function(htKVP) htKVP.Key) _
.FirstOrDefault()
But again I'd start with:
Dim dt as New Dictionary(Of String, String)
You could add this as an extension method like so:
Imports System.Runtime.CompilerServices
Module StringExtensions
<Extension()>
Public Function FirstKeyForValue(ByVal Hashtable as ht, ByVal value As String) As String
return ht.Values.OfType(Of String)() _
.Select(Function(htI, i) New With { .Key = keys(i), .Value = htI }) _
.Where(Function(htKVP) htKVP.Value = "str2") _
.Select(Function(htKVP) htKVP.Key) _
.FirstOrDefault()
End Function
End Module
There is a much easier way than Matt's answer. You can perform linq on a hashtable.
Of course the below sample code can be modified to use the proper types used in your Hashtable variable as both Key and Value can be any type:
Public Class HashtableTest
Private Lookup As New Hashtable
Private Sub New()
Lookup("Key1") = "Value1"
Lookup("Key2") = "Value2"
End Sub
Public Sub Test()
MsgBox(GetKey("Value2"))
End Sub
Public Function GetKey(Value As String) As String
Dim FoundKey As String = ""
If Lookup.ContainsValue(Value) Then
FoundKey = (From Key As String In Lookup.Keys.Cast(Of String) Where Lookup(Key).ToString() = Value Select Key).FirstOrDefault()
End If
Return FoundKey
End Function
End Class

How do I use Linq ToDictionary to return a dictionary with multiple values in the dictionary items?

I want to group items from a linq query under a header, so that for each header I have a list of objects that match the header title. I assumed the solution would be to use ToDictionary to convert the objects, but this allows only one object per "group" (or dictionary key). I assumed I could create the dictionary of type (String, List Of()), but I can't figure out how to write it.
As an example I have written a simplified version below.
Public Class order
Public ID As Integer
Public Name As String
Public DateStamp As Date
End Class
Public Function GetOrdersSortedByDate() As Generic.Dictionary(Of String, Generic.List(Of User))
Dim orders As New List(Of order)(New order() _
{New order With _
{.ID = 1, .Name = "Marble", .DateStamp = New Date(2010, 1, 1)}, _
New order With _
{.ID = 2, .Name = "Marble", .DateStamp = New Date(2010, 5, 1)}, _
New order With _
{.ID = 3, .Name = "Glass", .DateStamp = New Date(2010, 1, 1)}, _
New order With _
{.ID = 4, .Name = "Granite", .DateStamp = New Date(2010, 1, 1)}})
' Create a Dictionary that contains Package values,
' using TrackingNumber as the key.
Dim dict As Dictionary(Of String, List(Of order)) = _
orders.ToDictionary(Of String, List(Of order))(Function(mykey) mykey.Name, AddressOf ConvertOrderToArray) ' Error on this line
Return dict
End Function
Public Function ConvertOrderToArray(ByVal myVal As order, ByVal myList As Generic.List(Of order)) As Generic.List(Of order)
If myList Is Nothing Then myList = New Generic.List(Of order)
myList.Add(myVal)
Return myList
End Function
The error is as follows
'Public Function ConvertOrderToArray(myVal As order, myList As System.Collections.Generic.List(Of order)) As System.Collections.Generic.List(Of order)'
does not have a signature compatible with delegate
'Delegate Function Func(Of order, System.Collections.Generic.List(Of order))(arg As order) As System.Collections.Generic.List(Of order)'.
What do I do to output a list for each dictionary item?
you could first group all your result by name and then call to dictionnary with the group key as key
i don't know how to code it in VB but what it would look like in C#
Dictionary<string,List<Order>> dict = orders
.GroupBy(x => x.Name)
.ToDictionary(gr => gr.Key,gr=>gr.ToList() );
Instead of ToDictionary, you want ToLookup. A lookup will store a list of values for each key, so the key is no longer required to be unique. The lookup returned from this method is immutable though.