LINQ fill nested list of Object from query results - vb.net

I am joining two tables Race and Odds (joined on RaceID) and would like to create an object for each Race with its nested odds. Via LINQ I am able to group the query results into objects but can't populate the nested Odds list for each Race. Any ideas?
Ideally, I should have
obj[0] with RaceID = 1, Odds{1,2,3}
obj[1] with RaceID = 2, Odds{4,5,6,7}
etc
Public Class Race
Private RaceID As Integer
Private Name As String
'other properties here
Private Odds As New List(Of Odds)
End Class
Public Class Odds
Private OddsID As Integer
Private Odds As System.Nullable(Of Decimal)
Private RaceID As Integer
'other properties here
End Class
My function
Dim objRace As New List(Of Race)
Using context As IDataContext = DataContext.Instance()
Dim repository = context.GetRepository(Of Race)()
objRace = repository.Find("SELECT Odds.OddsID, Odds.Odds, Odds.Name, Odds.Type, Odds.DateUTC, Odds.WebsiteName, Odds.RaceID AS RaceID, Race.RaceID AS RaceID,
Race.Name AS RaceName, Race.RaceDate, Race.Location, Race.URL, Race.PortalID AS PortalID, Race.RaceTime
FROM Odds RIGHT OUTER JOIN
Race ON Odds.RaceID = Race.RaceID
WHERE (Race.PortalID = #0)", PortalId)
End Using
LINQ to get objects
Dim result = objRace.GroupBy(Function(records) records.RaceID).[Select](Function(r) New Race() With {
.RaceID = r.Key
.Odds = ' <------ **somehow populate this nested list**
}).ToList()
my query results:

The r should be a implementation of IGrouping. This contains a Key that you are using for your RaceID and it is a IEnumerable itself.
So you can store the Odds by calling r.ToList() or r.ToArray. How ever you want to store them.
I just noticed that you already create the list instance for your Odds in the constructor. You may want to change this, so you can put the collection in directly using the With initialisation.

Related

Retrieve list that is saved in a datatable

I created a datatable containing the list of notes for songs:
Private Table As New DataTable
Public Sub New()
With Table
.Columns.Add("Name")
.Columns.Add("NoteList")
.Rows.Add("GOT", GOT)
.Rows.Add("Yesterday", Yesterday)
End With
End Sub
GOT and Yesterday are lists of notes (notes is a class containing note, duration etc..)
On the form I then assign the datatable to a combobox:
ComboSongs.DisplayMember = Songs.DataTable.Columns(0).ColumnName
ComboSongs.ValueMember = Songs.DataTable.Columns(1).ColumnName
ComboSongs.DataSource = Songs.DataTable
I try to get the list of notes like this:
Dim songToPlay As List(Of Note) = CType(ComboSongs.SelectedValue, List(Of Note))
When I try to get the list I get the error:
System.InvalidCastException: 'Unable to cast object of type 'System.String' to type 'System.Collections.Generic.List`1[test.Note]'.'
Now I am unsure where I am getting it wrong. What would be the correct way to do this?
Your ValueMember is what is returned through the ComboBox.SelectedValue. So since you set the ValueMember like this
ComboSongs.ValueMember = Songs.DataTable.Columns(1).ColumnName
you only get the ColumnName. I assume that's a string and, well, the error message tells you it is one
... Unable to cast object of type 'System.String' ...
I guess that should be "NoteList", since that would be returned by Songs.DataTable.Columns(1).ColumnName
But this all doesn't make much sense, as I guess you are selecting a song there, either "Yesterday" or "GOT". At the point you're at it's so convoluted to return the DataTable rows, and index them. You will need to find the row by name and that is just too complicated when you could just create a class with strong names. I'll give you a class based solution but I'm not sure if you can make that change.
Private Class Song
Public Property Name As String
Public Property NoteList As List(Of Note)
End Class
Private Class Note
' your implementation
End Class
Dim songs As New List(Of Song)()
songs.Add(New Song() With {.Name = "GOT", .NoteList = New List(Of Note)})
songs.Add(New Song() With {.Name = "Yesterday", .NoteList = New List(Of Note)})
' need to populate those NoteLists first
ComboSongs.DisplayMember = "Name"
ComboSongs.DataSource = songs
Dim songToPlay = songs.SingleOrDefault(Function(s) s.Name = ComboSongs.SelectedValue)
Dim noteList = songToPlay.NoteList

Unable to cast object error when attempting to return key/value pairs by querying a datacontext

I am attempting to get data from a datacontext. Normally, I've had no problems doing it, but I'm having trouble trying to return a list of key/value pairs.
Basically I'm attempting to grab all unique names from a table as the key column and the number of times they appear in the table as the value column.
My data would look like this:
apple 5
banana 1
dragonfruit 3
.
.
.
The full error message is:
Unable to cast object of type 'System.Data.Linq.DataQuery1[VB$AnonymousType_32[System.String,System.Int32]]' to type 'System.Collections.Generic.List`1
The code I'm using is this:
Dim indicators As List(Of Object)
Public Sub GetIndicatorData()
Using context = new A_DataContext
indicators = (From p In chartdata Group p By __groupByKey1__ = p.INDK8R Into g = Group
Select New With {.name = __groupByKey1__, .count = g.Count()}).AsEnumerable()
indDataSource = indicators
End Sub
but I've also tried to:
Return indicators as a list
Return indicators as an enumerable.
Use a class to encapsulate your anonymous type
Public Class NameAndCount
Public ReadOnly Property Name As String
Public ReadOnly Property Count as Integer
Public Sub New(name As String, count As Integer)
Me.Name = name
Me.Count = count
End Sub
End Class
' ...
Private indicators As IEnumerable(Of NameAndCount)
Public Sub GetIndicatorData()
Using context = new A_DataContext
indicators = From p In chartdata Group p By __groupByKey1__ = p.INDK8R Into g = Group
Select New NameAndCount(__groupByKey1__, g.Count())
indDataSource = indicators.ToList()
End Using
End Sub
Figured it out. I changed the declaration of indicators to simply be an object then removed the .enumerable (or .toList) from the query result.
Thank you for the consideration and time.

VBA List of Custom Datastructures

One of the main problems in VBA are custom data structures and lists.
I have a loop which generates with each iteration multiple values.
So as an example:
Each loop iteration generates a string "name" an integer "price" and an integer "value".
In C# for example I'd create a class which can hold these three values and with each loop iteration I add the class object to a list.
How can I do the same thing in VBA if I want to store multiple sets of data when not knowing how many iterations the loop will have (I cant create an array with a fixed size)
Any ideas?
The approach I use very frequently is to use a class and a collection. I also tend to use an interface model to make things more flexible. An example would look something like this:
Class Module IFoo
Option Explicit
Public Sub Create(ByVal Name as String, ByVal ID as String)
End Property
Public Property Get Name() as String
End Property
Public Property Get ID() as String
End Property
This enforces the pattern I want for my Foo class.
Class Module Foo
Option Explicit
Private Type TFoo
Name as String
ID as String
End Type
Private this as TFoo
Implements IFoo
Private Sub IFoo_Create(ByVal Name as String, ByVal ID as String)
this.Name = Name
this.ID = Name
End Sub
Private Property Get IFoo_Name() as String
IFoo_Name = this.Name
End Property
Private Property Get IFoo_ID() as String
IFoo_ID = this.ID
End Property
We get intellisense from the Private Type TFoo : Private this as TFoo where the former defines the properties of our container, the latter exposes them privately. The Implements IFoo allows us to selectively expose properties. This also allows you to iterate a Collection using an IFoo instead of a Foo. Sounds pointless until you have an Employee and a Manager where IFoo_BaseRate changes depending on employee type.
Then in practice, we have something like this:
Code Module Bar
Public Sub CollectFoo()
Dim AllTheFoos as Collection
Set AllTheFoos = New Collection
While SomeCondition
Dim Foo as IFoo
Set Foo = New Foo
Foo.Create(Name, ID)
AllTheFoos.Add Foo
Loop
For each Foo in AllTheFoos
Debug.Print Foo.Name, Foo.ID
Next
End Sub
While the pattern is super simple once you learn it, you'll find that it is incredibly powerful and scalable if implemented properly. It also can dramatically reduce the amount of copypasta that exists within your code (and thus reduce debug time).
You can use classes in VBA as well as in C#: Class Module Step by Step or A Quick Guide to the VBA Class Module
And to to the problem with the array: you can create an array with dynamic size like this
'Method 1 : Using Dim
Dim arr1() 'Without Size
'somewhere later -> increase a size to 1
redim arr1(UBound(arr1) + 1)
You could create a class - but if all you want to do is hold three bits of data together, I would define a Type structure. It needs to be defines at the top of an ordinary module, after option explicit and before any subs
Type MyType
Name As String
Price As Integer
Value As Integer
End Type
And then to use it
Sub test()
Dim t As MyType
t.Name = "fred"
t.Price = 12
t.Value = 3
End Sub

Store and retrieve through classes - Functions, Subs and best practices

After completing a computer science degree I've landed a job as a software developer (woo!). Through university I was heavily web programming orientated sticking to Javascript and PHP (in the procedural sense). I'm now jump into object oriented programming as well in Visual Basic .NET.
I want to start on the right foot with best practices and what not so I have a simple scenario I want to know what the best way to do it is.
Let's say I have a class called 'Config.vb' which in the and on creation the 'Sub Load' reads keys from the registry.
Keys are: 'Fname', 'Lname', 'address1', 'address2', 'city', 'shoesize'.
So I want to stored these keys and their values accessible to my Main.vb.
First approach would be to declare 6 variables to stored the values so
Dim firstName = regKey("firstname").value
Dim lastName = regKey("lastname").value...
Then to have accessor methods to retrieve these values
Property ReadOnly getFirstname As String
Get
Return firstName
End Get
End Property
But writing out 6 get methods seems to be too lengthy. I could well be wrong, this is why I'm asking; is this the best way to access these variables?
Alternatively,
I was thinking maybe bunching all the keys and values in just the one Dictionary variable so it would contain all the keys with their values, then just having the one function to that accepts the key string and returns the value like:
Private Function getkey(key) As String
Return dictionary.Item(key)
End Function
This is probably how I would approach it.
Any guidance or let me know your way doing it will all help me and other to learn better!
Thanks.
As Plutonix said. Storing data in the registry isn't a good idea. Depending on how you want to access the data. If you just want to read all the data about lots of (presumably) people into your program in one go then you could check out object serialization or if you're dealing with large amounts of data check out databases.A good start with basic OOP can be found here. Its quite an old article, but should still work just fine.
As far as your data is concerned, you're better defining a new class. Like this
Friend Class Person
Dim _firstName As String
Dim _lastName As String
Public Property FirstName
Set(value)
_firstName = value
End Set
Get
Return _firstName
End Get
End Property
Public Property LastName
Set(value)
_lastName = value
End Set
Get
Return _lastName
End Get
End Property
End Class
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
Dim p1 As New Person
p1.FirstName = regkey("firstname")
p1.LastName = regkey("lastname")
End Sub
Of course if you're dealing with several people then you'll be better creating a list. Instead of declaring p1 as a new instance of a person, decleare the list like this
Dim peopleList As New List(Of Person)
and add people list this
Dim tempPerson As New Person
tempPerson.FirstName = regkey("firstname")
tempPerson.LastName = regkey("lastname")
peopleList.Add(tempPerson)
or
tempPerson.FirstName = "Fred"
tempPerson.LastName = "Perry"
peopleList.Add(tempPerson)
The beauty of OOP is that within a class you can declare methods(procedures) that work with the data in each instance of the class for example. say you have a property in you class called DOB - list this
Dim _DateOfBirth As Date
Public Property DOB As Date
Set(value As Date)
_DateOfBirth = value
End Set
Get
Return _DateOfBirth
End Get
End Property
You can create a function in the class like this that returns the person's age without ever having to store and update the age as time goes by. Like this:
Public Function Age() As Integer
Age = DateDiff(DateInterval.Year, _DateOfBirth, System.DateTime.Now)
Return Age
End Function
To get the age of the tempperson, just use this
Dim tempPerson As New Person
tempPerson.FirstName = "Fred"
tempPerson.LastName = "Perry"
tempPerson.DOB = CDate("8/12/1967")
MessageBox.Show("Age of the person is " & tempPerson.Age)
Gotta love OOP!

How to sort an object in a list on a non-unique value?

I'm trying to categorize articles by stored keywords. I have a list of keywords for a category, and I want an article to get assigned a category that has the most keyword count.
For Each keyword As String In category.Keywords
category.tempCount += Regex.Matches(article.Item("title").InnerXml, Regex.Escape(keyword)).Count
category.tempCount += Regex.Matches(article.Item("description").InnerXml, Regex.Escape(keyword)).Count
Next
And this is done for each category, ran for each article. I'm trying to sort the list in order to tell which category is the best one for this article. However it is possible more than one category is the best, and that none of the categories fit. So running this did not help me:
Categories.Sort(
Function(article1 As ArticleCategory, article2 As ArticleCategory)
Return article1.tempCount.CompareTo(article2.tempCount)
End Function)
Maybe I'm doing this all wrong, but so far I think I'm on the right path. (I also have a default compare in the Category class, it just wasn't working either.)
I get an exception on the sorting most likely caused because they are not unique.
The exception I get is an InvalidOperationException: Failed to compare two elements in the array. That's with using the comparer I built in the ArticleClass
Imports System.Xml
Class ArticleCategory
Implements IComparer(Of ArticleCategory)
Public ReadOnly key As Int32
Public ReadOnly Name As String
Public ReadOnly Keywords As List(Of String)
Public tempCount As Integer = 0
Public Sub New(ByVal category As XmlElement)
key = System.Web.HttpUtility.UrlDecode(category.Item("ckey").InnerXml)
Name = System.Web.HttpUtility.UrlDecode(category.Item("name").InnerXml)
Dim tKeywords As Array = System.Web.HttpUtility.UrlDecode(category.Item("keywords").InnerXml).Split(",")
Dim nKeywords As New List(Of String)
For Each keyword As String In tKeywords
If Not keyword.Trim = "" Then
nKeywords.Add(keyword.Trim)
End If
Next
Keywords = nKeywords
End Sub
'This should be removed if your using my solution.
Public Function Compare(ByVal x As ArticleCategory, ByVal y As ArticleCategory) As Integer Implements System.Collections.Generic.IComparer(Of ArticleCategory).Compare
Return String.Compare(x.tempCount, y.tempCount)
End Function
End Class
You need to implement IComparable instead of IComparer.
IComparer would be implemented by the class performing the sorting (such as a List class) while IComparable would be implemented by the class being sorted.
For example:
Public Function CompareTo(other As ArticleCategory) As Integer Implements System.IComparable(Of ArticleCategory).CompareTo
Return Me.tempCount.CompareTo(other.tempCount)
End Function
The best solution I found was using the Microsoft LINQ (a query language for objects) it works very well and quickly produces the right result.
Dim bestCat As ArticleCategory
bestCat = (From cat In Categories
Order By cat.tempCount Descending, cat.Name
Select cat).First
Completing my solution:
For Each category As ArticleCategory In Categories
category.tempCount = 0
For Each keyword As String In category.Keywords
category.tempCount += Regex.Matches(System.Web.HttpUtility.UrlDecode(article.Item("title").InnerXml), Regex.Escape(keyword)).Count
category.tempCount += Regex.Matches(System.Web.HttpUtility.UrlDecode(article.Item("description").InnerXml), Regex.Escape(keyword)).Count
Next
Next
Dim bestCat As ArticleCategory
Try
bestCat = (From cat In Categories
Order By cat.tempCount Descending, cat.Name
Select cat).First
Catch ex As Exception
ReportStatus(ex.Message)
End Try
So this is my preferred method to do a sort or a query on a list object or an array. It produces the best result, in the fastest time without having to add the IComparer implementations to your class.
Check it out at Microsoft.com