Linq-to-XML: Adding enumerations to objects - vb.net

I'm trying to figure out how to add an enumeration to an object with Linq. For example:
Dim thingBlock = <Things>
<Thing Name="Ish">
<SmallThing>Jibber</SmallThing>
<SmallThing>Jabber</SmallThing>
</Thing>
<Thing Name="Ugly Guy">
<SmallThing Name="Carl"></SmallThing>
<SmallThing Marks="Marks"></SmallThing>
</Thing>
</Things>
Dim myList As New List(Of Thing)
myList.AddRange(thingBlock.<Thing>.Select(Function(th) New Thing With {.Name = th.#Name}))
Public Class Thing
Public Property Name As String
Public Property SmallThings As New List(Of String)
End Class
This works well to create new Thing and add them to myList, but I can't figure out how to add the IEnumerable of String to my SmallThings list. For example, this doesn't work:
myList.AddRange(thingBlock.<Thing>.Select(Function(th) New Thing With {.Name = th.#Name, th.Select(Function(st) .SmallThings.Add(st.Elements.#Name.ToString)}))
I just want to add all the <SmallThing>.#Name to SmallThings property of the Thing class.

I'm not sure which values you wanted to extract from the <SmallThing>s but this seems to work.
' adds "Jibber" and "Jabber" '
myList.AddRange(thingBlock.<Thing>.Select(Function(th) New Thing With _
{ _
.Name = th.#Name, _
.SmallThings = th...<SmallThing>.Select(Function(st) st.Value).ToList _
}))
' adds "Carl" '
myList.AddRange(thingBlock.<Thing>.Select(Function(th) New Thing With _
{ _
.Name = th.#Name, _
.SmallThings = th...<SmallThing>.Select(Function(st) st.#Name).ToList _
}))
The key was to convert the projection to a list since SmallThings expected a list (Select returns an IEnumerable(Of T))

Related

vb.net Set type () ()

I have class sObrazac in which I have
'''<remarks/>
<System.Xml.Serialization.XmlArrayItemAttribute("Primatelji", IsNullable:=false), _
System.Xml.Serialization.XmlArrayItemAttribute("P", IsNullable:=false, NestingLevel:=1)> _
Public Property StranaB() As sPrimateljiP()()
Get
Return Me.stranaBField
End Get
Set
Me.stranaBField = value
End Set
End Property
'''<remarks/>
<System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.6.81.0"), _
System.SerializableAttribute(), _
System.Diagnostics.DebuggerStepThroughAttribute(), _
System.ComponentModel.DesignerCategoryAttribute("code"), _
System.Xml.Serialization.XmlTypeAttribute(AnonymousType:=true, [Namespace]:="http://e-porezna.porezna-uprava.hr/sheme/zahtjevi/ObrazacJOPPD/v1-1")> _
Partial Public Class sPrimateljiP
Private p1Field As Long
Private p2Field As String
Private p3Field As String
Private p4Field As String
.....
'''<remarks/>
Public Property P1() As Long
Get
Return Me.p1Field
End Get
Set
Me.p1Field = value
End Set
End Property
And now I'm setting the object what I have tried
Dim oPrimatelj As New sPrimateljiP
oPrimatelj.P1 = 1
oPrimatelj.P2 = 00019
oPrimatelj.P3 = 00019
oPrimatelj.P4 = 02994650199 ....
After setting that object I tried to push it into list and from it to array
Dim sList As New List(Of sPrimateljiP)
sList.Add(oPrimatelj)
oObrazac.StranaB = sList.ToArray
But as you know it will throw me
Value of type sPrimateljIP() cannot be converted to sPrimateljIP()()
I'm not quite familiar with two dimension arrays and I'm stuck here...
This may clear my question more. What is this element named P.
Note: I can't make schema, I need to adjust code to it.
Ok i found out what solution may be thanks to comment by Chetan. Use jagged array
Push list to jagged array and set it as object
Dim sList As New List(Of sPrimateljiP)
sList.Add(oPrimatelj)
sList.Add(oPrimatelj)
Dim jaggedArray()() As sPrimateljiP = New sPrimateljiP(0)() {sList.ToArray}
oObrazac.StranaB = jaggedArray

How to cast result back to generic list (using linq to object)?

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))

Implement Generic Interface via CodeDom

The CodeDom is not generating legal VB for me when I try to implement a generic interface.
Here is my VB code to generate the VB code.
Private Sub RunTest()
Dim compileUnit = New CodeCompileUnit
Dim ns As New CodeNamespace()
compileUnit.Namespaces.Add(ns)
ns.Imports.Add(New CodeNamespaceImport("System"))
ns.Imports.Add(New CodeNamespaceImport("System.Collections.Generic"))
Dim fooCollection = New CodeTypeDeclaration("FooCollection")
ns.Types.Add(fooCollection)
fooCollection.TypeAttributes = Reflection.TypeAttributes.Public
fooCollection.IsClass = True
fooCollection.BaseTypes.Add(New CodeTypeReference(GetType(System.Object)))
fooCollection.BaseTypes.Add(New CodeTypeReference( _
"System.Collections.Generic.IEnumerable" _
, New CodeTypeReference() {New CodeTypeReference("Foo")} _
))
Dim method = New CodeMemberMethod
fooCollection.Members.Add(method)
method.Attributes = MemberAttributes.Private
method.Name = "GetEnumerator"
method.ReturnType = New CodeTypeReference( _
"System.Collections.Generic.IEnumerable" _
, New CodeTypeReference() {New CodeTypeReference("Foo")} _
)
method.PrivateImplementationType = New CodeTypeReference( _
"System.Collections.Generic.IEnumerable" _
, New CodeTypeReference() {New CodeTypeReference("Foo")} _
)
Dim provider = New Microsoft.VisualBasic.VBCodeProvider
Dim options = New Compiler.CodeGeneratorOptions
Dim writer = New IO.StringWriter
provider.GenerateCodeFromCompileUnit(compileUnit, writer, options)
Console.WriteLine(writer.ToString)
End Sub
And that will generate:
Public Class FooCollection
Inherits Object
Implements System.Collections.Generic.IEnumerable(Of Foo)
Function System_Collections_Generic_IEnumerable`1_GetEnumerator() _
As System.Collections.Generic.IEnumerable(Of Foo) _
Implements System.Collections.Generic.IEnumerable(Of Foo).GetEnumerator
End Function
End Class
The problem is the name of the function. The tick mark in the function name doesn't make for a legal function name.
It seems that when using the PrivateImplentationType property of the CodeMethodMethod the Name property gets used as the name of the method you are implementing, not the name of the function.
How do you explicitly set the function name or at least how do I get it to be something legal?
To get a compilable output, just use ImplementationTypes: I just changed one line of your code (but I include a simple line for reference, and the corrected ReturnType):
method.Name = "GetEnumerator"
method.ReturnType = New CodeTypeReference( _
"System.Collections.Generic.IEnumerator" _
, New CodeTypeReference() {New CodeTypeReference("Foo")} _
)
method.ImplementationTypes.Add(New CodeTypeReference( _
"System.Collections.Generic.IEnumerable" _
, New CodeTypeReference() {New CodeTypeReference("Foo")} _
))
There doesn't seem to be a simple workaround using PrivateImplementationType. Microsoft's code in Microsoft.VisualBasic.VBCodeGenerator.GenerateMethod (which is in a Friend class so not easily overridden) caters for .'s, replacing them with _, but forgets to cater for ` in .NET 2.0-3.5 and (Of <type>) in 4.0. It is probably worth logging a bug at Connect, including pointing out the method name should be separately overridable compared to the method name on the interface being implemented.
You seem to need the functionality of PrivateImplementationType to support the non-generic GetEnumerator. Here is my simple adjustment of your original code to produce a single compilable library, with only warnings about empty code. This code still has "issues", such as the method.Attributes = MemberAttributes.Private being ignored where PrivateImplementationType is used...

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.