Unwanted Object Updates inside VBA Collection - ms-access

I am trying to build a collection with a new key on top of an older collection. I've looked at other, similar topics but they all had particular issues that I couldn't relate to mine. My collection issue requires that I take an object from an older collection (Col_0), split it into a twin object with slightly different property values, and then place both into a new collection with a key based on one of the altered properties. My problem is that the altered twin overwrites the original inside the new collection. Here is a brief example:
Public Sub FillOutput(ID As String, Col_0 As Collection)
'---Obj is a class scope object not defined here.
'---ID is the item key for items in Col_0 collection
Dim Col_1 As Collection
Set Obj_0 = Col_0.Item(ID)
'---Now I have the right item from the collection
Loop_Count = 0
Set Col_1 = New Collection
While Loop_Count < 2
If Loop_Count = 0 Then
Obj_0.Color = "Red"
Col_1.Add Obj_0, CStr(Obj_0.Color)
ElseIf Loop_Count = 1 Then
Obj_0.Color = "Blue"
Col_1.Add Obj_0, CStr(Obj_0.Color)
End If
Loop_Count = Loop_Count + 1
Wend 'Loop_count
End If
End Sub
I've tried a number of things such as creating an interim object that gets initialized and set to nothing inside the loop. As below:
ElseIf Loop_Count = 1 Then
Set pObj = New clsObjTable
pObj = Obj_0
pObj.Color = "Blue"
Col_1.Add pObj, CStr(pObj.Color)
Set pObj = Nothing
End If
Still doesn't work. My final collection keeps getting overwritten such that both items are "Blue". I have a suspicion that its about keys getting transferred and screwed up but I just don't know enough here. Any help or guidance here would be much appreciated!

Collections do not actually contain copies of objects, they only contain references to them. If you change your object after adding it to the collection, the object referenced in your collection has the changes too.
The only way to change this behavior, is to manually copy the object. But that's not an easy thing to do. Read this question on cloning objects, but note that you can't just clone built-in objects.
You could try to create a helper function to move all relevant properties from your object to a fresh copy of an object of the same object type, and then return the copy, when struggling with a built-in Access object. But some objects have read-only or private properties, and are thus unclonable.

Related

RTE 91 vs CTE Object Required: Fixing One Causes the Other?

Function printerpart(outputtext As Collection) As Collection
Dim TotalRecords As Integer 'Original build didn't include this line; no other declaration of TotalRecords, though?
Set TotalRecords = outputtext.Count
For i = 1 To TotalRecords
outputext = outputtext(i)
outputext = Replace(outputext, "&", "and")
Print #1, outputext
Next i
Set printerpart = New Collection
End Function
When attempting to run this function, an error occurs on the line assigning a value/object to TotalRecords. Initial builds did not include the Set statement on that line, but failing to include it results in RTE 91. With Set, however, the function encounters a compile-time error: Object Required.
Each call to printerpart passes outputtext as a collection of string objects.
I am aware of how terrible the variable names are and intend to fix them.
This question seems to imply that the Set statement should only be used to assign Object variables, and that lacking it is the cause of RTE 91 in most cases. Does declaring TotalRecords as an Integer make it an object? The same errors occur if TotalRecords is not declared until its assignment statement.
What is the proper method for resolving these errors in this context, given that the commonly suggested fix for one issue causes the other?
When you remove the "set" the error you get is not according to TotalRecords, it refers to outputtext, seems like what you are passing to function does not have the .count property, check again the variable passed to the function please

Retain/release couple for CCObject necessary?

Can somebody explain why retain and release here? I can't see it is necessary.
Code as follows, thanks.
void CCDictionary::setObject(CCObject* pObject, const std::string& key) {
CCAssert(key.length() > 0 && pObject != NULL, "Invalid Argument!");
if (m_eDictType == kCCDictUnknown)
{
m_eDictType = kCCDictStr;
}
CCAssert(m_eDictType == kCCDictStr, "this dictionary doesn't use string as key.");
CCDictElement *pElement = NULL;
HASH_FIND_STR(m_pElements, key.c_str(), pElement);
if (pElement == NULL)
{
setObjectUnSafe(pObject, key);
}
else if (pElement->m_pObject != pObject)
{
CCObject* pTmpObj = pElement->m_pObject;
pTmpObj->retain();
removeObjectForElememt(pElement);
setObjectUnSafe(pObject, key);
pTmpObj->release();
}
}
You should see the details of what the function removeObjectForElement(pElement) does.
It is safer to retain the pTmpObj if the function removeObjectForElement would access the field of what the pointer pTmpObj points to.
If the object which pTmpObj points to is released by another procedure, you may get BAD_ACCESS error when call the removeObjectForElement method here.
Cocos2d-x containers (CCDictionary, CCArray) retains object when it's added, and release when object removed from container. Container itself is interface to another structure, ccArray or uthash, that does not use cocos2d-x's reference counting system and operates with raw data instead of CCObjects.
Most of CCObjects are autoreleased. Autoreleased object deleted at the end of update cycle, if it's reference counter hits 1 (retained only by list of autoreleased object). Autoreleased object can safely be removed from container, then added into another container inside single update cycle.
But if object is not autoreleased, it's deleted immediately when reference counter hits 0. So, when cocos2d-x container call release, object can be deleted, but object's pointer can still be stored in uthash or ccArray.
To protect object from being deleted, when data structure stores it's pointer, we retain object, then release it at the end of procedure.
This pattern also should be used, for example, when we move node from one parent to another:
node->retain();
node->removeFromParent();
newRoot->addChild(node);
node->release();
Usually, node is autoreleased, and it's safe to move it this way. But if node is not autoreleased, removeFromParent call can delete it, and node becomes garbage pointer.

cant set Index ObjectChoiceField (load slow json)

I have a select that I get Json post with http, but I try to sets initially selected index but there is nothing in the list do not select anything. because the json is great.
public AppMainScreen() {
loadLists();
MySelect = new ObjectChoiceField( "Select: ", new Object[0], 3 );
VerticalFieldManager vfm = new VerticalFieldManager(Manager.VERTICAL_SCROLL);
vfm.add(MySelect);
add(vfm);
}
This statement appears wrong to me:
new ObjectChoiceField( "Select: ", new Object[0],3);
The second parameter to this constructor is supposed to be an array of objects whose .toString() method will be used to populate the choices. In this case, you have given it a 0 length array, i.e. no Objects. So there is nothing to choose. And then you have asked it to automatically select the 3rd item, and of course there is no 3rd item.
You should correct the code to actually supply an object array.
One option to make it easy is have your JSON load actually create a String array with one entry per selectable item. Then you use the index selected to identify the chosen item.

How to do a simple entity copy in Linq-to-SQL?

When using a Linq-to-SQL class, how can I make a simple copy of an entity and save it?
My entity has a guid for a unique ID that gets automatically generated in the SQL Server.
I don't require a "deep clone".
I tried to use some clone methods that are out there but I couldn't figure out how to get everything serialized that needed to be serialized (got stuck on the DataContext not being serializable).
Can I just get an entity, detach it from the DataContext, null out the unique ID and InsertOnSubmit in a new DataContext? If so, how would I do this?
VB.net code preferred but not required.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
UPDATE:
Public Shared Function ReIssue(RequestID As Guid) As Guid
Dim req As Request
Dim new_req As Request
Using dc1 As New MBDataContext()
req = (From r In dc1.Requests Where r.ID = RequestID).Single()
End Using
new_req = req
new_req.ID = Guid.Empty
new_req.CreateDate = Nothing
Using dc2 As New MBDataContext()
dc2.Requests.InsertOnSubmit(new_req)
dc2.SubmitChanges()
End Using
End Function
I get an error: An attempt has been made to Attach or Add an entity that is not new, perhaps having been loaded from another DataContext. This is not supported.
on this line: dc2.Requests.InsertOnSubmit(new_req)
Nulling out the unique id and then calling InsertOnSubmit is the right way to go. Some things you have to consider though:
What is the type of the id? Is it an int? A Guid? Is it nullable? If it is nullable, make sure to set it to null, if it is an int, then to 0, or a Guid, then to Guid.Empty.
Does the type have a timestamp of some kind? If so, then you have to reset/set it to null as well, depending on the type.
Once you've done that, you can call InsertOnSubmit and then SubmitChanges and the change should take place.
Note, if you are doing this for a large number of records, you are better off writing a stored procedure which will perform the insert into the table using a select from the other table. It will be much faster that way (you won't be loading the data from the database into memory then pushing it back, inserting the records one at a time).
This method seems to have worked perfectly.
Making the final code look like this:
Public Shared Function ReIssue(RequestID As Guid) As Guid
Using dc As New MBDataContext()
Dim req As Request
req = (From r In dc.Requests Where r.ID = RequestID).Single()
Dim new_req As Request = DirectCast(Entity.Copy(req, New Request()), Request)
dc.Requests.InsertOnSubmit(new_req)
dc.SubmitChanges()
req.ActiveRequestParentID = new_req.ID
dc.SubmitChanges()
Return new_req.ID
End Using
End Function
Public NotInheritable Class Entity
Private Sub New()
End Sub
Public Shared Function Copy(source As Object, destination As Object) As Object
Dim sourceProps As System.Reflection.PropertyInfo() = source.[GetType]().GetProperties()
Dim destinationProps As System.Reflection.PropertyInfo() = destination.[GetType]().GetProperties()
For Each sourceProp As System.Reflection.PropertyInfo In sourceProps
Dim column As ColumnAttribute = TryCast(Attribute.GetCustomAttribute(sourceProp, GetType(ColumnAttribute)), ColumnAttribute)
If column IsNot Nothing AndAlso Not column.IsPrimaryKey Then
For Each destinationProp As System.Reflection.PropertyInfo In destinationProps
If sourceProp.Name = destinationProp.Name AndAlso destinationProp.CanWrite Then
destinationProp.SetValue(destination, sourceProp.GetValue(source, Nothing), Nothing)
Exit For
End If
Next
End If
Next
Return destination
End Function
End Class

Tuple style object in VBA

I'm using VBA in an Access application and I would like to have a n-tuple object that contains values of different data types. Then I would like a collection of those objects.
If I was doing this in javascript it would look like:
var myStructure = {
name: "blah"
age: 33
moreStuff : "test"
};
And then I would need a collection of myStructure. How can I best accomplish this in VBA?
You can define your own variable type with code such as:
Public Type T_Person
name as string
dateOfBirth as date
....
email() as string (*)
....
End type
You can then declare a T_person type in your code with:
Dim currentPerson as T_Person
currentPerson.name = myName
currentPerson.dateOfBirth = myDate
currentPerson.email(1) = myFirstEmail
....
(*) I do not remember the details for declaring arrays in such circumstances. You might have to determine array's length when defining the variable. Please check help.
The same result can also be reached by declaring a class module named, for example, "Person". In this class module, you'll be not only able to follow the objet properties (such as name, dateOfBirth, etc), but also object events (initialisation and deletion). You'll be also able to create methods on this object. You code would then look like:
Dim myPerson as Person
set myPerson = New Person
myPerson.name = myName
myPerson.dateOfBirth = myDate
if myPerson.age > 18 then (*)
'the guy is an adult'
myPerson.createAccount
Else
'the guy is not ...'
Endif
(*) Here, age is a calculated proerty of your object, available when dateOfBirth is not null. Please google "VBA class module" to find different examples for the implementation of a class module in VBA.
Now, if you want to manage a collection of similar "objects" (here, Persons), you will have to go through the creation of a class module for your objects collection (a "Persons" class module for example) and make use of the "Collection" objet available in VBA. You will then end with 2 different class modules: Person (will hold each person's detail), and Persons (will hold the collection of Persons). You'll then be able to have code like this:
Public myPersons as Persons 'at the app level, 1 main collection'
myPersons.add .... 'adding a new person to your collection'
myPersons.count ... 'counting number of persons in myPersons'
Please google on "VBA collection object" for examples on Collection management in VBA. Check my code proposal, as this was written on the fly, and without VBA help file.
The "Class" solution is clearly more powerfull, but more complex than the "Type". Go for it if you need it. It is definitely worth the pain!
PS: I am not very happy with my namings here, as this can lead to very confusing code when dealing with the myPersons collection and myPerson instance of o Person object. I'd advise you to find a more obvious one, like "PersonCollection" and "Person", or even "Individual"
You can use Variant multidimensional arrays to store your collections. Variants can store any data type making it very versatile.
Const Name as Integer = 0
Const Age as Integer = 1
Const moreStuff as Integer = 2
Dim myStructure as Variant
Redim myStructure(0 to 2, 0 to n)
myStructure(Name, 0) = "Blah"
myStructure(Age, 0) = 33
myStructure(moreStuff, 0) = "test"
Note: You can only expand the last dimension of a multidimensional array in VBA and preserve the values, so make sure that it is for the dimension you want to scale.
That is the basic data structure and you can develop a class or functions to wrap everything up to suit your needs.
You may also want to look into the Scripting.Dictionary object, though I have read that it is considered unsafe. It is more dynamic than using Type definitions, and unlike Collection, it gives you access to the keys.
Here's a sample:
Public Function TestDictionary()
Dim d As New Scripting.Dictionary 'need reference to Microsoft Scripting Runtime
'Dim d As Object
'Set d = CreateObject("Scripting.Dictionary")
Dim k As Variant
d.Add "Banana", "Yellow"
d.Add "Apple", "Red"
d.Add "Grape", "Green"
For Each k In d.Keys
Debug.Print k; ": "; d.Item(k)
Next k
End Function
The Collection class is an option.
Dim col As New Collection
col.Add("blah", "name")
col.Add(33, "age")
col.Add("test", "moreStuff")
This gives you the most flexibility. However it isn't very efficient and the Collection class has no way to get a list of keys.