list.add for a list (of ClassType) not adding to list - no errors reported - vb.net

;tldr ...Orders.Add(order class variable) does not add a record the the Orders list, but also does not generate any errors.
I'll try to make a long story short and I apologize for the book you are about to read - I'm coming back to VB after spending a while in Python and C#. There is a bit of an explanation before any of the code...
I have a command-line exe program that at its base is working fine. We get 4 differently formatted CSV files that the program reads, and based on the file name, runs them through a parser that pulls the text information out of the file and populates a Public Class that in turn calls a function to populate a database table. The class is a single instance class named DataRecord. This program works absolutely fine (and has for well over 18 months now). It reads one line, if a batch# doesn't exist it creates one, and post the data class to the DB, and loops thru the file one line at a time doing so.
Also, their are 2 files to this program - the OrderAutomation main program file and the DBAccess file (which handle the DataRecord Class and all the various DB calls to verify information and to push the record into the database tables.
It used to be for all 4 file types, each file was a single batch all from a single region (data delimiters) - but now 1 of the file types will start sending multiple regions in a single file (and not necessarily in order) - so I need to modify the program that if its this 1 file type, it goes through the entire file, grouping each region together (each region will get its own Batch#).
I figured this would be fairly simple. I'd create an additional class (VTLData) that has some basic region separation data, and then a list of the DataRecord class. That looks like this:
Public Class VTLData
Public Property Locality As String
Public Property ARProvider As String
Public Property CORegion As String
Public Property BatchID As String
Public Property ProviderZip As String
Public Property IsValidRegion As Boolean
Public Property Orders As List(Of RecordData)
Public Sub New(s As String)
Locality = s
ARProvider = "00000000"
CORegion = "XX"
BatchID = "VXX-00000"
ProviderZip = "00000"
IsValidRegion = True
Orders = New List(Of RecordData)
End Sub
End Class
Like I said earlier - the RecordData class has no changes and is working fine.
There is a point in the program where I know this is the new file type (basically the region information has gone from a zip code to a set of specific text values) so when I test for that, if it's new, I set a boolean NewVTL to true. Because the file can contain more than one region, I have a VTLList property that is a List (of VTLData). I make sure I don't already have a Region in this list already - and if so, reference that index in the list.
When I get to the point where the program normally processes the record, I added the following (VTLIndex is the index variable for the list - at this time its value is 0):
If NewVTL Then
DB.data.CanProcess = CanProcessLine
DB.VTLList(VTLIndex).Orders.Add(DB.data)
Else
. - the old processing method
.
.
End If
I have a watch set for the VTLList(VTLIndex).Orders.Count value - that is at 0 when that line is reached, and stays at 0 when that line is processed.
I've tried creating a Push procedure in the VTLData class where you pass in the db.data class values, create a new instance of the record data and try to assign it that way
Public Sub PushOrder(item As RecordData)
Dim order As New RecordData
With order
.ARProviderNumber = ARProvider
.OrderID = item.OrderID
.AcctSuffix = item.AcctSuffix
.BatchID = BatchID
. - the 70 some other items in the recorddata class
.
.
End With
Orders.Add(order)
End Sub
This would also run without error, but not increase the count of .Orders.Count value.
I'm assuming its something stupid that I'm missing and I'm hoping someone can filter through all this and be like "You forgot to do blah..."
The only other thing I can think of is the do that Push function, but start it with Orders.Add(new RecordData) - and then modify the individual values through an index (.Orders(0).ARProviderNumber = data.ARProviderNumber... etc)
But based on other things I've done in VB - this should be working - I'm sure I'm just missing something...
So thanks for reaching this point - and thank you for letting me know what mistake I've made!!!

So the only way I got this to work for me was to create a Function in the VTLData class that did the following:
Public Function PushOrder(item As RecordData) As Boolean
Dim i As Integer = Orders.Count
Orders.Add(New RecordData)
With Orders(i)
.ARProviderNumber = ARProvider
.OrderID = item.OrderID
.AcctSuffix = item.AcctSuffix
.
. - more fields being populated
.
End With
If Orders.Count <= i Then Return False
Return True
End Function

Related

Sorting a SortedDictionary by key length in Visual Basic?

I'm writing a script that anonymizes participant data from a file.
Basically, I have:
A folder of plaintext participant data (sometimes CSV, sometimes XML, sometimes TXT)
A file of known usernames and accompanying anonymous IDs (e.g. jsmith1 as a known username, User123 as an anonymous ID)
I want to replace every instance of the known username with the corresponding anonymous ID.
Generally speaking, what I have works just fine -- it loads in the usernames and anonymous IDs into a dictionary and one by one runs a find-and-replace on the document text for each.
However, this script also strips out names, and it runs into some difficulty when it encounters names contained in other names. So, for example, I have two pairs:
John,User123
Johnny,User456
Now, when I run the find-and-replace, it may first encounter John, and as a result it replaces Johnny with User123ny, and then doesn't trigger Johnny.
The simplest solution I can think of is just to run the find-and-replace from longest key to shortest. To do that, it looks like I need a SortedDictionary.
However, I can't seem to convince Visual Basic to take my custom Comparer for this. How do you specify this? What I have is:
Sub Main()
Dim nameDict As New SortedDictionary(Of String, String)(AddressOf SortKeyByLength)
End Sub
Public Function SortKeyByLength(key1 As String, key2 As String) As Integer
If key1.Length > key2.Length Then
Return 1
ElseIf key1.Length < key2.Length Then
Return -1
Else
Return 0
End If
End Function
(The full details above are in case anyone has any better ideas for how to resolve this problem in general.)
I think it takes a class that implements the IComparer interface, so you'd want something like:
Public Class ByLengthComparer
Implements IComparer(Of String)
Public Function Compare(key1 As String, key2 As String) As Integer Implements IComparer(Of String).Compare
If key1.Length > key2.Length Then
Return 1
ElseIf key1.Length < key2.Length Then
Return -1
Else
'[edit: in response to comments below]
'Return 0
Return key1.Compare(key2)
End If
End Function
End Class
Then, inside your main method, you'd call it like this:
Dim nameDict As New SortedDictionary(Of String, String)(New ByLengthComparer())
You might want to take a look (or a relook) at the documentation for the SortedDictionary constructor, and how to make a class that implements IComparer.

How to check a list (Of T) if it contains an item, but ignoring if one property is different?

So I have a struct that is called alarm, and a list of alarms called alarmList. I go through a loop to see if there are any new alarms. If the List of alarms already has it, I say ignore it and continue:
Dim alarms() = G4TAPIs.G4TGetActivity(MyActRequest) 'get updated list of alarms
'this list may include all of the alarms previously.
For Each alarm In alarms
'if acknowledged, don't show
If alarm.AckedFlag = True And alarm.ResetFlag = True Then
Continue For
End If
If alarmList.Contains(alarm) Then
Continue For
End If
'do stuff to process
alarmList.add(alarm) 'add new alarm
Next
What happens is this guy in called regularly. Alarms() is updated with ALL past alarms, including the ones I already processed, but it updates their time to the current time, despite them already having happened. So i check to see if they're already processed on my list with contains.
Is there a way (linq methods are welcomed as well!) to edit the contains condition so that it ignores one feature if it is different? Particularly alarm.when (a Date type variable). It updates to the current date, and I want to make sure that the contains list checks to see if they're equal in all instances BUT the .when.
What's the best way to do this?
Create an IEqualityComparer that ignores the property you don't want to check, and use the overloaded Contains that accepts the value and an IEqualityComparer.
Sorry - this is c#, not VB... I rarely write VB, and if I tried - it would likely be very ugly.
class AlarmEqualityComparer : IEqualityComparer<Alarm>
{
public bool Equals(Alarm a1, Alarm a2)
{
// check whatever properties you want
}
public int GetHashCode(Alarm a1)
{
// build a hashcode using the properties that are checked
}
}
Edit... VB translation added.
Class AlarmEqualityComparer
Implements IEqualityComparer(Of Alarm)
Public Function Equals(a1 As Alarm, a2 As Alarm) As Boolean
' Check whatever properties you want
End Function
Public Function GetHashCode(a1 As Alarm) As Integer
' Build a hashcode using the properties that are checked
End Function
End Class

VB.net - Overwriting random access file

This part of my program is designed to add user details to a random access file. The sub routine below is designed to do this:
'This allows a user to be added to the User File
Dim UserToAdd As User
Dim UserFile As Integer
Dim RecordNumber As Long
'Read the data off the form and populate corresponding
'UserToAdd values
With UserToAdd
.UserID = Val(txt_UserID.Text)
.UserBarcode = txt_UserBarcode.Text
.Forename = txt_Forename.Text
.Surname = txt_Surname.Text
.AccessRights = cmb_AccessRights.Text
End With
'Find the next open space in the User File
UserFile = FreeFile()
'Now open the file used to store User records
FileOpen(UserFile, UserFileName, OpenMode.Random, OpenAccess.Write, OpenShare.Shared, Len(UserToAdd))
RecordNumber = Len(UserFile) + 1
'Add the new user to the file
FilePut(UserFile, UserToAdd, RecordNumber)
FileClose(UserFile)
There are no problems in actually saving the details, however, the file is overwritten every time another record is added. How could I stop this and what have I done wrong above?
It appears a couple things are happening, the first is since you are writing a RandomAccess file the Records need to be of the same length, therefore your Structure needs to be setup something like this (Since you didn't post your structure/class I am guessing that this could be a problem, if not the second issue is probably what is causing your issue).
Public Structure User
Public UserId As Integer
<VBFixedString(50)> Public UserBarCode As String
<VBFixedString(20)> Public Forename As String
<VBFixedString(20)> Public Surname As String
<VBFixedString(20)> Public AccessRights As String
End Structure
The second is that your RecordNumber is not valid you are just getting the length of an Integer(your UserFile Variable). By giving your structure a fixed size you can use the LOF Method to get the length of your open file then divide that by your record size to determine the amount of records something like this.
RecordNumber = (LOF(UserFile) \ Len(UserToAdd)) + 1
As I said in my comment these functions are left over from VB6, but I can see why you would want to use them, there appears to be a lack of information on any other way.

Active Record with Entity Framework

I'm working on a project that was built using ADO.NET (raw sql) and Active Record pattern. I am slowly moving it away from ADO.NET to Entity Framework Code First 4.3.
Here is an example of the pattern. The interface is a static Load and an instance Save (and Delete -- not shown).
Public Class Part
Public Property Id as Integer
Public Shared Function Load(_id As Integer) As Part
Using context As New DataContext()
Return context.Find(_id)
End Using
End Function
Public Sub Save()
Using context As New DataContext()
If Id = 0 Then
context.Parts.Add(Me)
Else
context.Entry(Me).State = Data.EntityState.Modified
End If
context.SaveChanges()
End Using
End Sub
End Class
I realize Active Record is not ideal for EF but I'd like to make it work to remove all of the ADO.NET code while not touching the rest of the code.
This mostly works, but I've run into an issue I don't know how to solve. In order to keep Foreign Keys in sync we handle it like such:
Public Sub Save()
ParentPart = Part.Load(ParentPartId)
ChildPart = Part.Load(ChildPartId)
Using context = New iTracContext()
If bid = 0 Then
context.BillOfMaterials.Add(Me)
Else
context.Entry(Me).State = Data.EntityState.Modified
End If
context.SaveChanges()
End Using
End Sub
This makes sure EF doesn't complain that we have non-matching relationships -- the Id always wins.
The issue is that its throwing an exception now when I save.
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
This is thrown from the line:
context.Entry(Me).State = Data.EntityState.Modified
How is anything in the ObjectStateManager for this context? It is brand new and should be empty, no?
If I remove the two Part.Load(...) lines it works fine.
Is there some type of change tracker that lives outside the context that I'm not aware of? That seems like it would kill any attempt at the Active Record pattern.
I'm also open to any suggestions on how to make Active Record work with EF. The context.Entry line is terrible but I don't know what else to do.
Telling me not to do Active Record isn't helpful, but feel free.
I believe Entity Framework may still be tracking the object from the context you loaded it from, because you create a new context for each Load and Save call. If this is the case, try detaching the objects after you load them:
Public Shared Function Load(_id As Integer) As Part
Using context As New DataContext()
Part part = context.Find(_id)
context.Entry(part).State = EntityState.Detached ' Detach from the initial context
Return part
End Using
End Function

DNN Dal+ - retrieve individual info class collection items (vb.NET)

I can't seem to find any answers that work. Here's the setup:
Info class:
Public Class ProductStageInfo
Private _ProductNumber As String
Private _ProductReference As String
Public Sub New()
End Sub
Public Property ProductNumber() As String
Get
Return _ProductNumber
End Get
Set(ByVal Value As String)
_ProductNumber = Value
End Set
End Property
End Class
and so on; I have four class declarations in the info class, the one above has fifteen different items - product number, product reference, product name, and so forth. The other's are catalogue classifications, which 'stage' of production the product is in, quality assurance questions; etc.
Then in the Controller class for DNN, I have those various info classes filled via queries to the DB DNN was deployed on; example:
Public Shared Function LoadStages(ByVal ProductNumber As String) As List(Of ProductStageInfo)
Return CBO.FillCollection(Of ProductStageInfo)(CType(DataProvider.Instance().ExecuteReader("Product_LoadStages", ProductNumber), IDataReader))
End Function
and everything works so far, I can fill a datalist using <%# DataBinder.Eval(Container.DataItem, "ProductNumber" %> and in code behind:
Dim ProductStageList As List(Of ProductStageInfo)
ProductStageList = ProductController.LoadStages(ProductNumber)
ProductStageDataList.DataSource = ProductStageList
ProductStageDataList.DataBind()
so far, so good...
but now I need to allow individuals to 'create' stages, and one of the business reqs' is that people shouldn't be able to create, for example, a delivery stage before a packaging stage.
So, how do I go about 'finding' a product number, product reference, stage number, within a collection? I thought I could fill the collection with all the stages of a certain product number, and then do an if/then stage = 0 found, stage > 5 found, etc.
If ProductStageList.Contains(strProductNumber) then
end if
gives error value of type string cannot be converted to namespace.ProductStageInfo; same thing for ProductStageList.Find...
maybe I just don't understand the whole collection/index/thing. All the examples I've found are regarding single dimension collections - 'how to find name within this collection', and the responses use strings to search through them, but somehow the Info class is being treated differently, and I'm not sure how to translate this...
any hints, tips, advice, tutorials.... appreciate it :)
thanks!
Pretty sure I just found the answer by reviewing another module; basically I need to create an empty object instead of a list object of the same class and use the two to iterate through using for/each, etc.
Dim objStages As ProductStagesInfo
Dim intStages, StageSelected As Integer
Dim intStageOption As Integer = -1
Dim blnValid As Boolean = True
Dim ProductChosen As String = lblStagesCNHeader.Text
Dim ProductStageList As List(Of ProductStagesInfo) = ProductController.LoadStages(ProductChosenNumber)
For intStages = 0 To StageList.Count - 1
objStages = StageList(intStages)
intStageOption += 1
Select objStages.StageSetNumber
Case "0"
Next
objStages._ provides me the ability to get the data I needed to do the business logic
<.<
seems so simple once you see it, wish I could just store it all in my brain
blah!