In VB6, there used to be a Collection data type that would allow retrieval of an item in the collection by either its key or its ordinal. However, it wasn't strongly typed.
Now, with VB.Net, I am looking for a suitable replacement that is strongly type and could be used with a generic collection.
This is a simple example of what I want to do. The only problem is that the underlying collection class, BindingList, does not support efficient retrieval of an item by an alpha key, so I have to loop through the elements to get the item I am looking for. For large collections, this is not efficient.
I have looked though the various Collection type classes and have found no suitable replacement.
This is what I want to do, except for the looping that is done with the Item property.
Rather than just saying "Use Hash tables" or something like that, if you could, please include the detailed out as I have done for the short example below.
Public Class Car
Public Sub New(ByVal keyName As String, ByVal property1 As String)
_KeyName = keyName
_Property1 = property1
End Sub
Dim _KeyName As String
Public Property KeyName() As String
Get
Return _KeyName
End Get
Set(ByVal value As String)
_KeyName = value
End Set
End Property
Public _Property1 As String
Public Property Property1() As String
Get
Return _Property1
End Get
Set(ByVal value As String)
_Property1 = value
End Set
End Property
End Class
Public Class Cars
Inherits System.ComponentModel.BindingList(Of Car)
Public Overloads ReadOnly Property Item(ByVal key As String) As Car
Get
For Each CurrentCar As Car In Me.Items
If CurrentCar.KeyName = key Then
Return CurrentCar
End If
Next
Return Nothing
End Get
End Property
End Class
I believe you're looking for Dictionary<TKey, TValue>. In fact, if you do want your own collection class that's strongly typed and isn't (itself) generic, if you change your parent class to Dictionary<string, Car>, you should be all set. This all does, of course, assume that you add the cars to the collection with an explicit string key. If you want the lookup to be based on the value of a property in the collection, you'd do better either using or inheriting from List<Car> and using LINQ to query the list. You could then have...
Public Overloads ReadOnly Property Item(ByVal key As String) As Car
Get
Return (from c in Me where c.KeyName = key select c).SingleOrDefault()
End Get
End Property
Do you really need both access by key AND index?
If you do not, then use a Dictionary(Of String, Car), and use
- MyCol.Items("XXX") to retrieve an item by key (or the shorthand MyCol("XXX"))
- MyCol.ContainsKey("XXX") to test if a key exists in the collection
- For Each Entry as KeyValuePair(Of String, Car) in MyCol if you want to enumerate all objects AND their key
- For Each Entry as Car in MyCol.Values if you want to enumerate the entries without consideration for the key
If you need both access by index and key, I'm afraid your best bet is to use a List(of Car) and a Dictionary(of Car) rolled into one custom collection class, because I believe they went away from that kind of collection which is not really all that useful for most problems.
This is what I am thinking is my best solution. I welcome comments for a better way!
Imports Microsoft.VisualBasic
Public Class Car
Implements Xs(Of Car).IKeyName
Private _KeyName As String
Public Sub New(ByVal keyName As String, ByVal property1 As String)
_KeyName = keyName
_Property1 = property1
End Sub
Public Property KeyName() As String Implements Xs(Of Car).IKeyName.KeyName
Get
Return _KeyName
End Get
Set(ByVal value As String)
_KeyName = value
End Set
End Property
Public _Property1 As String
Public Property Property1() As String
Get
Return _Property1
End Get
Set(ByVal value As String)
_Property1 = value
End Set
End Property
End Class
Public Class Cars
Inherits System.ComponentModel.BindingList(Of Car)
Public Overloads ReadOnly Property Item(ByVal key As String) As Car
Get
For Each CurrentCar As Car In Me.Items
If CurrentCar.KeyName = key Then
Return CurrentCar
End If
Next
Return Nothing
End Get
End Property
End Class
Public Class X
Private _KeyName As String
Public Property KeyName() As String
Get
Return _Keyname
End Get
Set(ByVal value As String)
_Keyname = value
End Set
End Property
End Class
Public Class Xs(Of X)
Inherits Hashtable
Interface IKeyName
Property KeyName() As String
End Interface
Public Shadows Sub Add(ByVal item As IKeyName)
MyBase.Add(item.KeyName, item)
End Sub
Public Shadows ReadOnly Property Item(ByVal key As String) As x
Get
If Me.ContainsKey(key) Then
Return MyBase.Item(key)
Else
'If I mispell a key, I don't want to end up creating a new mispelled version, I want an error
Throw New Exception("Element with key " & key & " is not found")
End If
End Get
End Property
End Class
Public Class Cars2
Inherits Xs(Of Car)
End Class
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim MyCar As New Car("key1", "prop1")
'First approach
Dim MyCars As New Cars
MyCars.Add(MyCar)
Dim RetrievedCar As Car = MyCars.Item("key1") 'Inefficient retrieval by key (uses looping)
'Second approach
Dim Cars2 As New Cars2
Cars2.Add(MyCar)
Dim RetrievedCar2 As Car = Cars2.Item("key1") 'Can now efficiently retreive an item by its key
End Sub
The OrderedDictionary in the System.Collections.Specialized namespace can be accessed by index and by key, if you ever need that. But looking at your solution, it looks like a standard Dictionary, but less efficient because it forces a string type for keys.
Is there any reason you can't use the Dictionary .NET provides you, or another collection type that's already in .NET like OrderedDictionary?
Related
I am using the EntityFramework.SQLite library and for the life of me can not figure out how to combine two tables together into a temporary table for display purposes in xaml code.
Here's the code for my two tables I want to combine which is about all I am using right now besides a class for my temp table called CategoryList (this is part of the business/data logic dll library):
Partial Public Class CategoryList
Public Sub New()
Me.CategoryInfo = New CategoryReference
'Me.CategoryCode = New HashSet(Of CategoryCodes)
Me.CategoryCodes = New CategoryCodes
End Sub
Public Property MyId As Integer
<Key, ForeignKey("CodeID")>
<Required>
Public Property CodeID As Integer
<Key, ForeignKey("CategoryID")>
Public CategoryID As Integer
'Public Property CategoryCode As ICollection(Of CategoryCodes)
Public Property CategoryInfo As CategoryReference
' Public Property CategoryInfo As ICollection(Of CategoryReference)
Public Property CategoryCodes As CategoryCodes
End Class
<Table("CategoryCodes")>
Public Class CategoryCodes 'category shortnames/codes
<MaxLength(100)>
<Required>
Public Property CategoryCode As String
Get
Return _CategoryCode
End Get
Set(value As String)
_CategoryCode = value
End Set
End Property
Private _CategoryCode As String
'<NotNull>
' <PrimaryKey>
'<Unique(Name:="UQ__CategoryCodes__0000000000000081_CategoryCodes", Order:=0)>
<Key>
<Required>
Public Property CodeID As Integer
Get
Return CategoryCode
End Get
Set(value As Integer)
_CodeID = value
End Set
End Property
Private _CodeID As Integer
End Class
<Table("CategoryReference")>
Partial Public Class CategoryReference 'table design for category data
<MaxLength(100)>
Public Property CategoryName As String
Get
Return _CategoryName
End Get
Set(value As String)
_CategoryName = value
End Set
End Property
Private _CategoryName As String
<MaxLength(100)>
Public Property CategoryDescription As String
Get
Return _CategoryDescription
End Get
Set(value As String)
_CategoryDescription = value
End Set
End Property
Private _CategoryDescription As String
'<Unique(Name:="UQ__CatagoryReference__000000000000005F_CatagoryReference", Order:=0)>
<ForeignKey("CodeID")>
<Required>
Public Property CodeID As Integer
Get
Return _CodeID
End Get
Set(value As Integer)
_CodeID = value
End Set
End Property
Private _CodeID As Integer
'<Unique(Name:="UQ__CatagoryReference__000000000000005A_CatagoryReference", Order:=0)>
<Key>
<Required>
Public Property CategoryID As Integer
Get
Return _CategoryID
End Get
Set(value As Integer)
_CategoryID = value
End Set
End Property
Private _CategoryID As Integer
End Class
It may seem long but the tables are very simple and the get/set blocks make it look long (A vb.net editor can turn them into simple property's if she/he wishes). I may be using the CategoryList class wrong but here's where I use it in my xaml datasource in my main application code (I have business logic/data processing in a dll library):
Private Property ViewModel As List(Of UIELLUWP.DataAccess.CategoryList)
Dim categories As New UIELLUWP.DataAccess.SQLiteDb
ViewModel = categories.Categories.ToList
Errors received with current code:
I receive an error that Table "CategoryList" does not exist when I run the above code.
my solution I found out to this question was to add asnotracking because tracking was taking place.
Every time i use some class e.g Artikel as follows:
Public Class Artikel
Property ID As Integer
Property Nummer As String
Property Name As String
Property Position As Integer
End Class
For such classes i would like to have collection class. The features i would like to have is like:
--> Add (passing Artikel object)
--> Remove (passing Artikel object)
--> Sort entire collection (based on Position property desc/asc)
--> Compare two Artikels (pass by Artikels and tell by which property has to be compared)
--> Check whether two artikels equals
--> Every added artikel has to be marked by Key (so maybe dictionary)? <key><Artikel>
--> Remove Artikel (passing by Key index)
Could somone from you there tell me or even better provide example of collection class pass those requirments?
EDIT: Startup:
Artikel's collection:
Option Strict On
Public Class Articles
Public Property collection As Dictionary(Of Integer, Artikel)
Sub New()
'Initiate new collection
collection = New Dictionary(Of Integer, Artikel)
End Sub
'Add new Artikel to collection
Public Function AddToCollection(ByVal artikel As Artikel) As Boolean
collection.Add(artikel)
Return True
End Function
'Remove specific Artikel
Public Sub RemoveFromCollectionByArtikel(artikel As Artikel)
If Not IsNothing(collection) Then
collection.Remove(artikel)
End If
End Sub
'Get collection
Public Function GetCollection() As Dictionary(Of Integer, Artikel)
Return collection
End Function
'Sort collection by property position
Public Sub SortByPosition()
collection.Sort()
End Sub
'Remove specific sending keys and then reorder them
Public Sub RemoveAllMarkedAsDeleted(keys As List(Of Integer))
'-- Check whther anything has been marked as deleted
If keys.Count > 0 Then
For Each row In keys
collection.Remove(row)
Next
ReorderKeys()
End If
'Reorder all Artikels in collection
Private Sub ReorderKeys()
Dim newCollection As New Dictionary(Of Integer, Artikel)
Dim index As Integer = 0
For Each collitem In collection
newCollection.Add(index, collitem.Value)
index += 1
Next
collection.Clear()
collection = newCollection
End Sub
End Class
Artikel class (additionally i implemented IComparable to be able to sort)
Option Strict On
Public Class Artikel
Implements IComparable(Of Artikel)
Property ID As Integer
Property Nummer As String
Property Name As String
Property Position As Integer
Public Function CompareTo(pother As Artikel) As Integer Implements IComparable(Of Artikel).CompareTo 'we can sort because of this
Return String.Compare(Me.Position, pother.Position)
End Function
Public Shared Function FindPredicate(ByVal partikel As Artikel) As Predicate(Of Artikel)
Return Function(partikel2 As Artikel) partikel.ID = partikel2.ID
End Function
Public Shared Function FindPredicateByUserId(ByVal partikel As String) As Predicate(Of Artikel)
Return Function(partikel2 As Artikel) partikel = partikel2.ID
End Function
End Class
Parts of it look good, but I would ultimately do it a bit differently. First, consider overloads on the item class to make them easier to create and default initialization:
Public Class Article
Property ID As Integer = -1
Property Key As String = ""
Property Name As String = ""
Property Position As Integer = -1
Property PubDate As DateTime = DateTime.Minimum
Public Sub New()
End Sub
' whatever minimum data a new item requires
Public Sub New(k As String, n As String)
Key = k
Name = n
End Sub
' full initialization:
Public Sub New(k As String, n As String, pos As Int32,
pubDt As DateTime)
...
End Sub
End Class
I added some properties for variety, and I suspect "Nummer" might be the "Key" mentioned in the OP, but whatever it is, I would add it to the Article class as that name, if it has some importance.
You might need a simple ctor for serialization (???). Some of these will find and use a Private parameterless constructor, but your code will be forced to use one of the overloads in order to provide some minimum level of data when a new one is created.
You probably do not need IComparable. That is typically for more complex comparisons, such as multiple or complex properties. An example is a carton or box:
If (width = Other.Width) AndAlso (height = Other.Height) Then
Return 0
ElseIf (width = Other.Height) AndAlso (height = Other.Width) Then
Return 0
End If
Plus more gyrations to work out which is "less" than the other. One reason you dont need it, is because If Art1.Postion > Art2.Postion is trivial. The other reason in your case, is because a Dictionary cannot be sorted.
Rather than a Dictionary, an internal List would work better for some of the things you describe but still allow you to have it act like a Dictionary to the extent you need it to. For this, I might build it using ICollection<T>:
Public Class ArticleCollection
Implements ICollection(Of Article)
Pressing Enter after that line will add all the required methods including:
Public Sub Add(item As Article) Implements ICollection(Of Article).Add
Public Sub Clear() Implements ICollection(Of Article).Clear
Public Function Contains(item As Article) As Boolean Implements ICollection(Of Article).Contains
Public ReadOnly Property Count As Integer Implements ICollection(Of Article).Count
Public Function Remove(item As Article) As Boolean Implements ICollection(Of Article).Remove
It remains completely up to you how these are implemented. It also doesn't rule out adding methods such as RemoveAt(int32) or RemoveByKey(string) depending on what you need/how it will be used. One of the benefits to ICollection(Of T) is that it includes IEnumerable which will allow use for each loops (once you write the Enumerator): For Each art In Articles
To emulate a dictionary to allow only one item with a specific property value:
Public Class ArticleCollection
Implements ICollection(Of Article)
Private mcol As List(Of Article)
...
Public Sub Add(item As Article) Implements ICollection(Of Article).Add
' check for existing key
If KeyExists(item.Key) = False Then
mcol.Add(item)
End If
End Sub
You can also overload them:
' overload to match Article ctor overload
Public Sub Add(key As String, name As String)
If KeyExists(key) = False Then
' let collection create the new item
' with the minimum required info
mcol.Add(New Article(key, name))
End If
End Sub
If you add an Item Property, you can index the collection ( Articles(3) ):
Property Item(ndx As Int32) As Article
Get
If ndx > 0 AndAlso ndx < mcol.Count Then
Return mcol(ndx)
Else
Return Nothing
End If
End Get
Set(value As Article)
If ndx > 0 AndAlso ndx < mcol.Count Then
mcol(ndx) = value
End If
End Set
End Property
' overload for item by key:
Public Property Item(key As String) As Article
An Add method and an Item Property will be important if the collection will display in the standard NET CollectionEditor.
There are several ways to implement sorting. The easiest is to use linq in the code which uses your collection:
Articles = New ArticleCollection
' add Article items
Dim ArticlesByDate = Articles.OrderBy(Function(s) s.PubDate).ToList()
Where PubDate is one of the Article properties I added. The other way to handle sorting is by the collection class returning a new collection (but it is so simple to do, there is little need for it):
Friend Function GetSortedList(bSortAsc As Boolean) As List(Of Article)
If bSortAsc Then
Return mcol.OrderBy(Function(q) q.PubDate).
ThenBy(Function(j) j.Position).ToList()
Else
Return mcol.OrderByDescending(Function(q) q.PubDate).
ThenByDescending(Function(j) j.Position).ToList()
End If
End Function
Whether it implements ICollection(Of T), inherits from ICollection(Of T) or does work off a Dictionary depends entirely on what this is, how it is used and whatever rules and restrictions there are (including if it will be serialized and how). These are not things we know.
MSDN has an article on Guidelines for Collections which is excellent.
Create your class
Public Class Artikel
Property ID As Integer
Property Nummer As String
Property Name As String
Property Position As Integer
sub new (_ID as integer, _Nummer as string, _Name as string, _Position as integer)
ID = _ID
Nummer = _Nummer
Name = _Name
Position = _Position
End Sub
End Class
Create another class which holds a private list and add sub routines to it
Public Class ArtikelList
Private _List as new list (of Artikel)
Public sub remove(Key as integer)
Dim obj as Artikel = nothing
for each x as Artikel in _List
if x.ID = Key then
obj = x
exit for
end if
Next
if not isnothing(obj) then
_List.remove(obj)
end if
End sub
Sub Add(obj as Artikel)
Dim alreadyDeclared as boolean = falsse
for each x as Artikel in _List
if x.ID = obj.id then
alreadyDeclared = true
exit for
end if
Next
if not AlreadyDeclared then
_List.add(obj)
Else
'Somehow inform the user of the duplication if need be.
end if
End sub
End Class
Then use your list class.
dim L as new ArtikelList
L.add(new Artikel(1280, "AFKforever!", "Prof.FluffyButton", 96))
L.remove(1280)
I only added one sub routine as an example. I hope it helps but feel free to ask for more example routines.
This can also be done by creating a class which inherits from the list class, exposing all list class functionality but by using this method you are forced to create every subroutine that will be used. This way you only use routines that you created exclusively for the purpose Artikel objects handling.
Check if two Artikels are equal
Public Class Artikel
Property ID As Integer
Property Nummer As String
Property Name As String
Property Position As Integer
sub new (_ID as integer, _Nummer as string, _Name as string, _Position as integer)
ID = _ID
Nummer = _Nummer
Name = _Name
Position = _Position
End Sub
End Class
Public Overrides Overloads Function Equals(obj As Object) As Boolean
If obj Is Nothing OrElse Not Me.GetType() Is obj.GetType() Then
Return False
else
dim _obj as artikel = obj
if Me.ID = _obj.ID then
Return true
else Return False
End If
End Function
End Class
Use it like:
If x.equals(y) then
'they have the same ID
end if
I have a user control with a property "Rules" that is a generic list.
Every "rule" is associated to a combobox control and i have to create a property to host data for the combobox. I used another generic list to accomplish this.
In design works well, i can add items normally in property grid, but when i run the program the values are not maintained.
Rules property:
Private _regras As New List(Of ParametrosColunasGrid)
<Category("Ecletica")> _
<Browsable(True)> _
<System.ComponentModel.DesignerSerializationVisibility(System.ComponentModel.DesignerSerializationVisibility.Content)>
Public Property Regras() As List(Of ParametrosColunasGrid)
Get
Return _regras
End Get
Set(value As List(Of ParametrosColunasGrid))
_regras = value
End Set
End Property
Public Class ParametrosColunasGrid
'...
Private _itens_Combo As New List(Of ItemComboBox)
<DesignerSerializationVisibility(DesignerSerializationVisibility.Content)>
Public Property ItensCombo As List(Of ItemComboBox)
Get
Return _itens_Combo
End Get
Set(value As List(Of ItemComboBox))
_itens_Combo = value
End Set
End Property
'...
End Class
ItemComboBox Class:
<Serializable()>
Public Class ItemComboBox
Public Property Value As String
Public Property Key As String
Public Overrides Function ToString() As String
Return _Value
End Function
End Class
I have a class that inherits from CollectionBase. I tried to use the contains method to detect whether the Key already exists before inserting a new one. Here is what I have tried.
<Serializable()> Public Class validationList
Inherits CollectionBase
Public Function Add(ByVal Item As validationItem) As Integer
Return Me.List.Add(Item)
End Function
Default Public ReadOnly Property Item(ByVal index As Integer) As validationItem
Get
Return CType(List.Item(index), validationItem)
End Get
End Property
Public Sub Remove(ByVal index As Integer)
Me.List.RemoveAt(index)
End Sub
Protected Overrides Sub OnInsert(ByVal index As Integer, ByVal value As Object)
If Me.List.Contains(value) Then MsgBox("Already exist")
MyBase.OnInsert(index, value)
End Sub
Public Function IndexOf(ByVal key As validationItem)
Return List.IndexOf(key)
End Function
Public Sub AddRange(ByVal item() As validationItem)
For counter As Integer = 0 To item.GetLength(0) - 1
List.Add(item(counter))
Next
End Sub
End Class
<Serializable()> Public Class validationItem
Implements IEquatable(Of validationItem)
Private _key As validationTypes
Private _value As String
Public Sub New()
' Empty constructor is needed for serialization
End Sub
Public Sub New(ByVal k As validationTypes, ByVal v As String)
_key = k
_value = v
End Sub
Public Enum validationTypes
Madatory = 0
[Integer] = 1
Numeric = 2
[Decimal] = 3
MaxValue = 4
MinValue = 5
MinLength = 6
Email = 7
End Enum
Public Property Value As String
Get
Return _value
End Get
Set(ByVal Value As String)
_value = Value
End Set
End Property
Public Property Key As validationTypes
Get
Return _key
End Get
Set(ByVal value As validationTypes)
_key = value
End Set
End Property
Protected Overloads Function Equals(ByVal eqItem As validationItem) As Boolean Implements IEquatable(Of Testing_Project.validationItem).Equals
If eqItem Is Nothing Then Return False
Return Me._key = eqItem.Key
End Function
Public Overrides Function Equals(ByVal eqItem As Object) As Boolean
If eqItem Is Nothing Then Return False
Dim eqItemObj As validationItem = TryCast(eqItem, validationItem)
If eqItemObj Is Nothing Then Return False
Return Equals(eqItemObj)
End Function
Public Overrides Function GetHashCode() As Integer
Return Me._key.GetHashCode()
End Function
End Class
The validationList will be exposed from a usercontrol as a property, so that items could be added from the designer. When adding items I need to detect whether they already exist. I tried overriding the OnInsert but this sometime return that duplicates exists even when their aren't and doesn't report that duplicate exist when I try to add existing keys.
This indirectly answers the question after dealing with the issue which emerged in comments about Collection(Of T):
Add a reference to System.Collections.ObjectModel if needed, then
Imports System.Collections.ObjectModel
' a ValidationItem collection class
Public Class ValidationItems
Inherits Collection(Of ValidationItem)
Public Shadows Sub Add(NewItem As ValidationItem)
' test for existence
' do not add if it is not unique
Dim dupe As Boolean = False
For n As Int32 = 0 To Items.Count - 1
If Items(n).Key = NewItem.Key Then
dupe = True
Exit For
End If
Next
If dupe = False then
items.Add(newitem)
End if
' I would actually use an IndexOfKey function which might
' be useful elsewhere and only add if the return is -1
If IndexOfKey(NewItem.Key) <> -1 Then
Items.Add(newItem)
End If
End Sub
Some NET collection types implement Add as a function and return the item added. This sounds weird since you pass it the item to add. But returning Nothing if the item cannot be added is a neat semaphore for "I cant/wont do that". I cant recall if the std NET Collection Editor recognizes that or not.
One problem with using Contains is that it will test if item passed as param is the same object as one in the list. They never will be the same object, even if they have the same values. Testing the key in a loop is simpler than calling a method which implements an interface. (That previous answer was totally valid in the context presented, but the context has changed).
Even if you stay with CollectionBase, you want to handle it in the Add. If you try to remove it in OnInsert, VS will have problems deserializing the collection.
Also, your validationitem needs a Name property or the Collection Editor will display "Myassembly+MyType" as the Name (or a ToString override).
Other issues:
I am not sure your IndexOf will work. The list contains ValidationItems (objects), but you check it for _key (string). This will not matter if you change to Collection(Of T) which implements it for you.
The simple ctor is needed by the Collection Editor, not serialization. But the important thing is that it is there.
As for the comment about all Zeroes coming back - that is because your ValidationItem is not yet decorated for designer serialization. Maybe not the Collection Property either, that isnt shown.
I have a class called Person:
Public Class Person
Private PersonID as String
Private Name as String
Private Records as GenericCollection(Of PublicRecord)
Public Sub New(ByVal ID as String)
Me.PersonID = ID
Me.Name = getPersonName(ID)
End Sub
'Get/Sets
End Class
getPersonName is simply a function that does exactly as it is described. GenericCollection class is as follows:
Public Class GenericCollection(Of ItemType)
Inherits CollectionBase
' Purpose: Provides a generic collection class from which all other collections
' classes can be inherited if they wish to extend the functionality below.
#Region "Public Methods"
Public Function Add(ByVal NewObject As ItemType) As Integer
Return MyBase.InnerList.Add(NewObject)
End Function
Public Sub New()
MyBase.New()
End Sub
#End Region
#Region "Public Properties"
Default Public Property Item(ByVal Index As Integer) As ItemType
Get
Return CType(MyBase.InnerList(Index), ItemType)
End Get
Set(ByVal value As ItemType)
MyBase.InnerList(Index) = value
End Set
End Property
#End Region
End Class
PublicRecord class is:
Public Class PublicRecord
Private RecordID As String
Private RecordDataOne As String
Private RecordDataTwo As String
Public Sub New()
MyBase.New()
End Sub
'Get/Sets
End Class
One of the requirements I've been told can be done is that I should be able to grab all Persons in a Collection of Persons, then since all of those Persons will have Collectinos of Records within them... grab a specific set of data from the Collection of Records.
We'll say, I want to: getPersonsOverAge21() from the Collection of Records inside each Person inside the Collection of Persons.
Is this even possible? If so, can someone explain how it would work?
There's no need to implement your own generic collection class. .Net has already done this for you in the System.Collections.Generic namespace. Look at a List(Of Person) or even just a simple IEnumerable(Of Person).
Now you haven't explained how your record objects relate to your person type or what data they contain, so I can only speculate on the next part. But it sounds kind of like you want something like this:
Dim people As List(Of Person) = GetPeopleFromDatabase()
Dim peopleOver21 As IEnumerable(Of Person) = people.Where(Function(p) p.Age >= 21)
Dim peopleOver21Query = From p In people _
Where (p.Age >= 21) _
Select p