Public Sub CopyCollections()
Dim t, iEnumerableType, iListType, iDicType As Type
Dim coll, obj As Object
Dim il As IList
Dim id As IDictionary
Dim f, fields() As FieldInfo
Dim value As Object
'We must precopy this collection into the collection copy
'A simple assignment would just copy a reference to the colletion, while
'we need to copy the collection itself, so that adding/removing elements
'of the original won't effect the copy
m_collectionCollection = New Specialized.HybridDictionary
t = m_object.GetObjectType
fields = t.GetFields(BindingFlags.Instance Or BindingFlags.NonPublic Or BindingFlags.Public)
For Each f In fields
iListType = f.FieldType.GetInterface("IList", True)
iDicType = f.FieldType.GetInterface("IDictionary", True)
'iEnumerableType = f.FieldType.GetInterface("IEnumerable", True)
If Not iListType Is Nothing OrElse Not iDicType Is Nothing Then
Dim ICloneType As Type = f.FieldType.GetInterface("ICloneable", True)
If Not ICloneType Is Nothing Then
'Getting the ICloneable interface from the object.
Dim IClone As ICloneable = CType(f.GetValue(m_object), ICloneable)
'We use the clone method to set the new value to the field.
coll = IClone.Clone()
Else
'If the field doesn't support the ICloneable interface then just set it.
coll = Activator.CreateInstance(f.FieldType)
'need to copy references one-by-one
If Not iListType Is Nothing Then
il = CType(coll, IList)
For Each obj In f.GetValue(m_object.GetSourceObject)
il.Add(obj)
Next
Else
id = CType(coll, IDictionary)
For Each de As DictionaryEntry In f.GetValue(m_object.GetSourceObject)
id.Add(de.Key, de.Value)
Next
End If
End If
m_collectionCollection.Add(f.Name, coll)
End If
Next
End
If I have define collection but this collection isn't initialized, it is a mistake. I think that in lines:
'Getting the ICloneable interface from the object.
Dim IClone As ICloneable = CType(f.GetValue(m_object), ICloneable)
'We use the clone method to set the new value to the field.
coll = IClone.Clone()
will be have a try and catch or verify.
Rollback of the collection is incorrect, don't rollback.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
It is correct. But, The rollback of collection isn't correct. I have a following structure:
A--OneToMany--B
B--OneToOne--C
If I delete a object B of the collection the object A, add Object B to collection the object A, then I execute rollback of the transaction. The rollback of the database is correct, but The rollback of the cache is incorrect, because the collection with the object added don't deleted and the object deleted don't restore.
I think that can be because, I am using an inherits of the collection to bind a DataGrid. (I am using a example to bind collection to datagrid).
Saludos
Victor
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
****************************************
now I am need execute the next instruction to view collection in a datagrid:
dim c as Cuenta= new Cuenta
c.codigoCuenta=1
c.find(c)
datagrid.dataSource=c.transacciones
''It is correct!!!!!! I am assume that has data in my database (Cuenta have 4 transaciones)
''But now,
''Begin transaction
dim t as Transaccion = new Transaccion
dim t1 as Transaccion= new Transaccion
.......
c.transacciones.add(t)
c.transacciones.add(t2)
c.transacciones(1).delete
if I execute commit, it is correct.
If I execute rollback, the collection is the same. I think that the collection will be restored to initial state.
*****************************************
the code of the transaction and cuenta is the following:
' Clase base para crear cuentas bancarias o caja
Public Class Cuenta
Inherits CPersistentObject
' Clave unica de la cuenta
Private _codigoCuenta As Integer
' Descripcion corta de la cuenta
Private _descripcion As String
' Numero de la cuenta (si es bancaria, vacio si es caja)
Private _numero As String
' Saldo actual de la cuenta
Private _saldo As Decimal
'Estado en la que puede estar una cuenta
Private _tipoEstado As ERP.TipoEstado
Private _codigoTipoEstado As Integer
'Listado de pagos de una cuenta
Private _pagos As System.Collections.ArrayList
'Listado de Cobros de una Cuenta
Private _cobros As System.Collections.ArrayList
'Listado de transacciones de una cuenta
Private WithEvents _transacciones As TransaccionC
' Constructor
Public Sub New()
MyBase.New()
_transacciones = New TransaccionC
_transacciones.ContainerObject = Me
End Sub
Property Transacciones() As CPersistentCollection
Get
Return _transacciones
End Get
Set(ByVal Value As CPersistentCollection)
If Not CPersistentObject.Equals(_transacciones, Value) Then
_transacciones = Value
SetDirtyFlag()
End If
End Set
End Property
' Propiedad para manipular el _codigoCuenta
Public Property CodigoCuenta() As Integer
Get
Return _codigoCuenta
End Get
Set(ByVal Value As Integer)
If _codigoCuenta <> Value Then
_codigoCuenta = Value
SetDirtyFlag()
End If
End Set
End Property
' Propiedad para manipular el _descripcion
Public Property Descripcion() As String
Get
Return _descripcion
End Get
Set(ByVal Value As String)
If _descripcion <> Value Then
_descripcion = Value
SetDirtyFlag()
End If
End Set
End Property
' Propiedad para manipular el _numero
Public Property Numero() As String
Get
Return _numero
End Get
Set(ByVal Value As String)
If _numero <> Value Then
_numero = Value
SetDirtyFlag()
End If
End Set
End Property
' Propiedad para manipular el atributo _saldo
Public Property Saldo() As Decimal
Get
Return _saldo
End Get
Set(ByVal Value As Decimal)
If _saldo <> Value Then
_saldo = Value
SetDirtyFlag()
End If
End Set
End Property
'Propiedad para manipular el objeto tipo estado
Public Property TipoEstado() As ERP.TipoEstado
Get
If _tipoEstado Is Nothing Then
_tipoEstado = New ERP.TipoEstado
_tipoEstado.CodigoTipoEstado = Me._codigoTipoEstado
_tipoEstado.Retrieve(_tipoEstado)
End If
Return _tipoEstado
End Get
Set(ByVal Value As ERP.TipoEstado)
If Not CPersistentObject.Equals(_tipoEstado, Value) Then
_tipoEstado = Value
CodigoTipoEstado = _tipoEstado.CodigoTipoEstado
SetDirtyFlag()
End If
End Set
End Property
'Propiedad para manipular el atributo _codigoTipoEstado
Public Property CodigoTipoEstado() As Integer
Get
If _tipoEstado Is Nothing Then
Return _codigoTipoEstado
Else
Return _tipoEstado.CodigoTipoEstado
End If
End Get
Set(ByVal Value As Integer)
If Not _tipoEstado Is Nothing Then
If _tipoEstado.CodigoTipoEstado <> Value Then
_tipoEstado.CodigoTipoEstado = Value
SetDirtyFlag()
End If
End If
_codigoTipoEstado = Value
End Set
End Property
' Disminuir el saldo
Public Function decrementarCantidad(ByVal c As Integer) As Transaccion
Me._saldo = Me._saldo - c
'Generar la transaccion respectiva
Dim t As Transaccion = New Transaccion
t.CodigoTransaccion = ERP.GestorSerial.getSerial("Transaccion")
t.Valor = c
t.Saldo = Me.Saldo 'Ultimo saldo se asigana a transaccion
t.Descripcion = "Ingreso en la cuenta"
t.Fecha = Now
t.Cuenta = Me
Me.agregarTransaccion(t) 'Agregar Transaccion a la cuenta
Return t
End Function
' Incrementar el saldo de la cuenta
Public Function incrementarCantidad(ByVal c As Decimal) As Transaccion
Me._saldo = Me._saldo + c
'Generar la transaccion respectiva
Dim t As Transaccion = New Transaccion
t.CodigoTransaccion = ERP.GestorSerial.getSerial("Transaccion")
t.Valor = c
t.Saldo = Me._saldo 'Ultimo saldo se asigana a transaccion
t.Descripcion = "Ingreso en la cuenta"
t.Fecha = Now
t.Cuenta = Me
Me.agregarTransaccion(t) 'Agregar Transaccion a la cuenta
Return t
End Function
' Permite agregar una nueva transaccion a la coleccion que posee la cuenta
Public Sub agregarTransaccion(ByVal t As Transaccion)
Me._transacciones.Add(t)
End Sub
' Cambiar de estado un cuenta (Puede estar activada o desactivada).
Public Sub cambiarEstado(ByVal e As ERP.TipoEstado)
Me.TipoEstado = e
End Sub
' Recupera las transacciones asociadas a la cuenta.
Public Function listadoTransacciones() As CPersistentCollection
Return Me._transacciones
End Function
Public Overrides Function getNewObject() As CPersistentObject
Return New Cuenta
End Function
Public Overrides Function IsValid() As Boolean
Return True
End Function
End Class ' END CLASS DEFINITION Cuenta
End Namespace ' GestionBancosCaja
' Modelo esttico
Imports AToMSFramework
Namespace GestionBancosCaja
Public Class TransaccionC
Inherits CPersistentCollection
Public Overloads Sub Add(ByVal ol As Transaccion)
MyBase.Add(ol)
End Sub
Default Public Shadows ReadOnly Property Item(ByVal index As Integer) As Transaccion
Get
Return CType(list.Item(index), Transaccion)
End Get
End Property
Public Overrides ReadOnly Property AllowEdit() As Boolean
Get
Return True
End Get
End Property
Public Overrides Function AddNew() As Object
Dim ol As New Transaccion
If Not Me.ContainerObject Is Nothing Then
'Assign the order header reference if possible
'Dim p As Transaccion = Me.ContainerObject
ol.Cuenta = Me.ContainerObject
End If
list.Add(ol)
Return ol
End Function
Public Overrides ReadOnly Property AllowNew() As Boolean
Get
Return Not Me.ContainerObject Is Nothing
End Get
End Property
Public Overrides ReadOnly Property AllowRemove() As Boolean
Get
Return True
End Get
End Property
End Class
' Clase base para registro de movimientos en las diferentes cuentas.
Public Class Transaccion
Inherits CPersistentObject
' OID del objeto transaccion
Private _codigoTransaccion As Integer
' Fecha en la que se realiza la transaccion
Private _fecha As Date
' Cantidad que implica la transaccionn.
Private _valor As Decimal
' Saldo despues de incrementar o decrementar el saldo de la cuenta. Es mas informativo, para no tener que volver a recalcular el saldo en cierta fecha.
Private _saldo As Decimal
' Descripcion corta de la transaccion.
Private _descripcion As String
' Cuenta a la que pertenece
Private _cuenta As Cuenta
Private _codigoCuenta As Integer
' Constructor
Public Sub New()
End Sub
' Propiedad para acceder al atributo _codigoTransaccion
Public Property CodigoTransaccion() As Integer
Get
Return _codigoTransaccion
End Get
Set(ByVal Value As Integer)
If _codigoTransaccion <> Value Then
_codigoTransaccion = Value
SetDirtyFlag()
End If
End Set
End Property
' Propiedad para acceder al atributo _fecha
Public Property Fecha() As Date
Get
Return _fecha
End Get
Set(ByVal Value As Date)
If _fecha <> Value Then
_fecha = Value
SetDirtyFlag()
End If
End Set
End Property
' Propiedad para acceder al atributo _valor
Public Property Valor() As Decimal
Get
Return _valor
End Get
Set(ByVal Value As Decimal)
If _valor <> Value Then
_valor = Value
SetDirtyFlag()
End If
End Set
End Property
' Propiedad para acceder al atributo _saldo
Public Property Saldo() As Decimal
Get
Return _saldo
End Get
Set(ByVal Value As Decimal)
If _saldo <> Value Then
_saldo = Value
SetDirtyFlag()
End If
End Set
End Property
' Propiedad para acceder al atributo _descripcion
Public Property Descripcion() As String
Get
Return _descripcion
End Get
Set(ByVal Value As String)
If _descripcion <> Value Then
_descripcion = Value
SetDirtyFlag()
End If
End Set
End Property
'Propiedad para manipular el objeto Cuenta
Public Property Cuenta() As Cuenta
Get
If _cuenta Is Nothing Then
_cuenta = New Cuenta
_cuenta.CodigoCuenta = Me._codigoCuenta
_cuenta.Retrieve(_cuenta)
End If
Return _cuenta
End Get
Set(ByVal Value As Cuenta)
If Not CPersistentObject.Equals(_cuenta, Value) Then
_cuenta = Value
CodigoCuenta = _cuenta.CodigoCuenta
SetDirtyFlag()
End If
End Set
End Property
'Propiedad para manipular el atributo _codigoCuenta
Public Property CodigoCuenta() As Integer
Get
If _cuenta Is Nothing Then
Return _codigoCuenta
Else
Return _cuenta.CodigoCuenta
End If
End Get
Set(ByVal Value As Integer)
If Not _cuenta Is Nothing Then
If _cuenta.CodigoCuenta <> Value Then
_cuenta.CodigoCuenta = Value
SetDirtyFlag()
End If
End If
_codigoCuenta = Value
End Set
End Property
Public Overrides Function getNewObject() As CPersistentObject
Return New Transaccion
End Function
Public Overrides Function IsValid() As Boolean
Return True
End Function
End Class ' END CLASS DEFINITION Transaccion
End Namespace ' GestionBancosCaja
Saludos
Victor.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
'Begin transaction
dim t as Transaccion = new Transaccion
dim t1 as Transaccion= new Transaccion
.......
c.transacciones.add(t)
c.transacciones.add(t2)
c.transacciones(1).delete
Since it's not in the sample code do I assume that you are doing a CPersistenceBroker.StartTransaction() call before you add/delete items in the collection?
You might want to look at the CheckCacheAfterRollback4 unit test in CVS (in InheritedClasses/AtomsFrameworkTests.vb) since it does both the add and delete from the collection during a transaction and it works properly.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
I am doing a CPersistenceBroker.StartTransaction()
dim t as Transaccion = new Transaccion
dim t1 as Transaccion= new Transaccion
.......
c.transacciones.add(t)
c.transacciones.add(t2)
c.transacciones(1).delete
now, if I execute rollBack. The cache of the collection is incorrect. Because the collection don't have the same objects.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
I don't think that should make a difference since I am only looking for objects that implement IList or IDictionary. If you inherit the CPersistentCollection then you will have an object that implements IList.
Can you do some debugging for me?
In CCacheEntry.vb set breaks at CopyCollections() and at RestoreCollections().
What _should_ happen is that when the transaction starts the collections in the object should be copied into new objects and placed in a "collection of collections" called m_collectionCollection.
When the transaction aborts RestoreCollections should copy the collections from m_collectionCollection over the collections in the object they came from.
If you can verify a few things it will help
1. That the collections are all being correctly copied in the first place. (Look at f.name to see what field is being copied).
2. That the restoreCollections method is being called
3. That the collections are being correctly copied back out of m_collectionCollection.
I appreciate the help on this one
- Richard.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
When execute the method CopyCollection in a code:
m_collectionCollection.Add(f.Name, coll) is correct.
The value of the m_collectionCollection.count=3.
Now if I execute rollback of the transsaction, the code : RestoreCollections() is called.
In the method AbortChanges the instructions:
For Each ck In al
MyBase.Remove(ck)
Next
also are called. That is to say, the remove is executed.
Now, My object continues with the information in the collection, the previous one is not restored.
The following code in RestoreCollection:
If Not iListType Is Nothing OrElse Not iDicType Is Nothing Then
'We must restore this collection from the collection copy
'Just use a straight value assignment - no need to worry about cloning
f.SetValue(m_object.GetSourceObject, m_collectionCollection.Item(f.Name))
End If
It is never executed (f.SetValue), Why ???
Why to do the cache of all the collecciones of the objects in cache. I think that will be alone of the objects that are used in the transaction.
Saludos
Victor
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
This indicates that there were 3 collection objects that were copied. This would mean that the Cuenta private fields:
Private _pagos As System.Collections.ArrayList
Private _cobros As System.Collections.ArrayList
Private WithEvents _transacciones As TransaccionC
were all copied correctly.
>In the method AbortChanges the instructions:
>For Each ck In al
>MyBase.Remove(ck)
>Next
This code is actually removing objects from the cache that were added during the transaction. In other words, if you begin a transaction, create and save an object and then abort the transaction then the object you created should be removed from the cache. This code won't affect collections of individual objects
>The following code in RestoreCollection:
>If Not iListType Is Nothing OrElse Not iDicType Is Nothing Then
>'We must restore this collection from the collection copy
>'Just use a straight value assignment - no need to worry about cloning
>f.SetValue(m_object.GetSourceObject, m_collectionCollection.Item(f.Name))
>End If
If RestoreCollections is called but f.SetValue() is never called then none of the fields for the object implement the IList or IDictionary interface.
I assume that m_object.GetSourceObject is the same object that the collections were copied from in CopyCollections(). I also assume that the m_collectionCollection.Count value is still 3.
If you set a break on the line
iListType = f.FieldType.GetInterface("IList", True)
in RestoreCollections() and then check the value of f.name you should see that the _pagos, _cobros and _transacciones fields are all checked to see if they implement the IList interface (iListType should not be nothing when the call completes). If that doesn't happen can you let me know what you find in the debug session.
By the way, I noticed that in class Cuenta you have
> Private WithEvents _transacciones As TransaccionC
But that the property exposes a different type
> Property Transacciones() As CPersistentCollection
It might be a good idea to change the property type to match that of the internal object
I hope we get to the bottom of this soon
- Richard.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Public Sub RestoreCollections()
Dim t, iEnumerableType, iListType, iDicType As Type
Dim f, fields() As FieldInfo
t = m_object.GetObjectType
fields = t.GetFields(BindingFlags.Instance Or BindingFlags.NonPublic Or BindingFlags.Public)
For Each f In fields
iListType = f.FieldType.GetInterface("IList", True)
iDicType = f.FieldType.GetInterface("IDictionary", True)
'iEnumerableType = f.FieldType.GetInterface("IEnumerable", True)
If Not iListType Is Nothing OrElse Not iDicType Is Nothing Then
'We must restore this collection from the collection copy
'Just use a straight value assignment - no need to worry about cloning
f.SetValue(m_object.GetSourceObject, m_collectionCollection.Item(f.Name))
End If
Next
End Sub
the atribute fields does not have the collection name, f.name doesn't have _transacciones, _pagos, _cobros.
The problem is that in the code:
t = m_object.GetObjectType
m_object does not have the object that possesses the collections.
This means that in the method AbortChanges of CCacheEntry doesn't have my object that possesses the collections or it does not fulfill certain conditions.
In the next code in method AbortChanges:
For Each x In Me
ce = x.Value
'Lets cancel deletes first - just means resetting transaction to None
If ce.TransactionType = CCacheEntry.CacheTransaction.Deleted AndAlso ce.PersistentObject.GetClassMap.RelationalDatabase Is reldb Then
ce.TransactionType = CCacheEntry.CacheTransaction.None
ce.RestoreCollections()
End If
'Now look for cache entries to be saved and restore them
If ce.TransactionType = CCacheEntry.CacheTransaction.Saved AndAlso ce.PersistentObject.GetClassMap.RelationalDatabase Is reldb Then
ce.RestoreObject()
ce.RestoreCollections()
ce.TransactionType = CCacheEntry.CacheTransaction.None
End If
Next
The instructions are never executed inside the sentence IF( if ce is an object with collections).
Which they are the conditions in order that these instructions are executed.
Saludos
victor
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
>The problem is that in the code:
>t = m_object.GetObjectType
>m_object does not have the object that possesses the collections.
m_object is an instance of the interface IPersistableObject and is designed to wrap around the actual object being saved. It can be either a CInjectedObject or a CPersistentObject depending on wether the object inherits from CPeristentObject or not.
the GetObjectType property will return the type of the object that m_object is wrapping. If you have a look at m_object.GetObjectType.Name you should get a value of "Cuenta"
Now something occurred to me when you said the code in the IF statements didn't execute. The code to restore the collections will only execute if the object has been saved or deleted inside a transaction.
There is no code to roll back the changes if the object is dirty (ie modified) but not yet saved.
For example
broker.StartTransaction()
myObject.Property = NewValue
myObject.Save()
broker.Rollback()
will leave myObject.Property in it's current (modified) state - however the cached copy of the object should still be in it's original state.
So I went and wrote a unit test that changed an object, aborted the transaction and checked the cached copy of the object.
I found that when the cache returned a copy of an object to the application it was only doing a shallow copy. Normally this is perfectly fine, however because collections are copied by reference, changing the collection in the application changed it in the cache.
I've just committed some changes on the v2_0 branch in CVS to fix the problem, and hopefully you will now be OK.
By the way, I would not recommend getting the code from the HEAD branch just at the moment as it's a bit messy and some parts are broken while I work on many-to-many associations.
- Richard.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Hi,
You have following code:
Public Sub CopyCollections()
Dim t, iEnumerableType, iListType, iDicType As Type
Dim coll, obj As Object
Dim il As IList
Dim id As IDictionary
Dim f, fields() As FieldInfo
Dim value As Object
'We must precopy this collection into the collection copy
'A simple assignment would just copy a reference to the colletion, while
'we need to copy the collection itself, so that adding/removing elements
'of the original won't effect the copy
m_collectionCollection = New Specialized.HybridDictionary
t = m_object.GetObjectType
fields = t.GetFields(BindingFlags.Instance Or BindingFlags.NonPublic Or BindingFlags.Public)
For Each f In fields
iListType = f.FieldType.GetInterface("IList", True)
iDicType = f.FieldType.GetInterface("IDictionary", True)
'iEnumerableType = f.FieldType.GetInterface("IEnumerable", True)
If Not iListType Is Nothing OrElse Not iDicType Is Nothing Then
Dim ICloneType As Type = f.FieldType.GetInterface("ICloneable", True)
If Not ICloneType Is Nothing Then
'Getting the ICloneable interface from the object.
Dim IClone As ICloneable = CType(f.GetValue(m_object), ICloneable)
'We use the clone method to set the new value to the field.
coll = IClone.Clone()
Else
'If the field doesn't support the ICloneable interface then just set it.
coll = Activator.CreateInstance(f.FieldType)
'need to copy references one-by-one
If Not iListType Is Nothing Then
il = CType(coll, IList)
For Each obj In f.GetValue(m_object.GetSourceObject)
il.Add(obj)
Next
Else
id = CType(coll, IDictionary)
For Each de As DictionaryEntry In f.GetValue(m_object.GetSourceObject)
id.Add(de.Key, de.Value)
Next
End If
End If
m_collectionCollection.Add(f.Name, coll)
End If
Next
End
If I have define collection but this collection isn't initialized, it is a mistake. I think that in lines:
'Getting the ICloneable interface from the object.
Dim IClone As ICloneable = CType(f.GetValue(m_object), ICloneable)
'We use the clone method to set the new value to the field.
coll = IClone.Clone()
will be have a try and catch or verify.
Rollback of the collection is incorrect, don't rollback.
Hi Victor,
Thanks for the notice. I've fixed the code and tested it and you should be OK now (obviosuly you'll need to get the latest version from CVS)
Hi.
It is correct. But, The rollback of collection isn't correct. I have a following structure:
A--OneToMany--B
B--OneToOne--C
If I delete a object B of the collection the object A, add Object B to collection the object A, then I execute rollback of the transaction. The rollback of the database is correct, but The rollback of the cache is incorrect, because the collection with the object added don't deleted and the object deleted don't restore.
I think that can be because, I am using an inherits of the collection to bind a DataGrid. (I am using a example to bind collection to datagrid).
Saludos
Victor
Hi Victor,
Would you be able to create a unit test that duplicates the error? I'm not quite sure what you are doing and a unit test will really help me debug it.
- Richard.
Hi.
I have a following structure:
<class name="Cuenta" table="cuenta" database="erp" namespace="Finanzas.GestionBancosCaja">
<attribute name="CodigoCuenta" column="codigocuenta" find="true" key="primary" />
<attribute name="Numero" column="numero" />
<attribute name="Descripcion" column="descripcion" />
<attribute name="Saldo" column="saldo" />
<attribute name="TipoEstado" />
<attribute name="CodigoTipoEstado" column="codigotipoestado" />
<attribute name="Transacciones"/>
<attribute name="CreatedDate" column="CreatedDate" timestamp="true" />
<attribute name="ModifiedDate" column="ModifiedDate" timestamp="true" />
</class>
<class name="Transaccion" table="transaccion" database="erp" namespace="Finanzas.GestionBancosCaja">
<attribute name="CodigoTransaccion" column="codigotransaccion" find="true" key="primary" />
<attribute name="Fecha" column="fecha" />
<attribute name="Valor" column="valor" />
<attribute name="Descripcion" column="descripcion" />
<attribute name="Saldo" column="saldo" />
<attribute name="Cuenta"/>
<attribute name="CodigoCuenta" column="codigocuenta" />
<attribute name="CreatedDate" column="CreatedDate" timestamp="true" />
<attribute name="ModifiedDate" column="ModifiedDate" timestamp="true" />
</class>
<association fromClass="Finanzas.GestionBancosCaja.Cuenta" toClass="Finanzas.GestionBancosCaja.Transaccion" cardinality="oneToMany"
target="transacciones" retrieveAutomatic="lazy" deleteAutomatic="false" saveAutomatic="false"
inverse="false">
<entry fromAttribute="CodigoCuenta" toAttribute="CodigoCuenta" />
</association>
****************************************
now I am need execute the next instruction to view collection in a datagrid:
dim c as Cuenta= new Cuenta
c.codigoCuenta=1
c.find(c)
datagrid.dataSource=c.transacciones
''It is correct!!!!!! I am assume that has data in my database (Cuenta have 4 transaciones)
''But now,
''Begin transaction
dim t as Transaccion = new Transaccion
dim t1 as Transaccion= new Transaccion
.......
c.transacciones.add(t)
c.transacciones.add(t2)
c.transacciones(1).delete
if I execute commit, it is correct.
If I execute rollback, the collection is the same. I think that the collection will be restored to initial state.
*****************************************
the code of the transaction and cuenta is the following:
' Modelo esttico
Imports AToMSFramework
Imports Finanzas.GestionBancosCaja
Imports Finanzas.GestionCobros
Imports Finanzas.GestionPagos
Namespace GestionBancosCaja
' Clase base para crear cuentas bancarias o caja
Public Class Cuenta
Inherits CPersistentObject
' Clave unica de la cuenta
Private _codigoCuenta As Integer
' Descripcion corta de la cuenta
Private _descripcion As String
' Numero de la cuenta (si es bancaria, vacio si es caja)
Private _numero As String
' Saldo actual de la cuenta
Private _saldo As Decimal
'Estado en la que puede estar una cuenta
Private _tipoEstado As ERP.TipoEstado
Private _codigoTipoEstado As Integer
'Listado de pagos de una cuenta
Private _pagos As System.Collections.ArrayList
'Listado de Cobros de una Cuenta
Private _cobros As System.Collections.ArrayList
'Listado de transacciones de una cuenta
Private WithEvents _transacciones As TransaccionC
' Constructor
Public Sub New()
MyBase.New()
_transacciones = New TransaccionC
_transacciones.ContainerObject = Me
End Sub
Property Transacciones() As CPersistentCollection
Get
Return _transacciones
End Get
Set(ByVal Value As CPersistentCollection)
If Not CPersistentObject.Equals(_transacciones, Value) Then
_transacciones = Value
SetDirtyFlag()
End If
End Set
End Property
' Propiedad para manipular el _codigoCuenta
Public Property CodigoCuenta() As Integer
Get
Return _codigoCuenta
End Get
Set(ByVal Value As Integer)
If _codigoCuenta <> Value Then
_codigoCuenta = Value
SetDirtyFlag()
End If
End Set
End Property
' Propiedad para manipular el _descripcion
Public Property Descripcion() As String
Get
Return _descripcion
End Get
Set(ByVal Value As String)
If _descripcion <> Value Then
_descripcion = Value
SetDirtyFlag()
End If
End Set
End Property
' Propiedad para manipular el _numero
Public Property Numero() As String
Get
Return _numero
End Get
Set(ByVal Value As String)
If _numero <> Value Then
_numero = Value
SetDirtyFlag()
End If
End Set
End Property
' Propiedad para manipular el atributo _saldo
Public Property Saldo() As Decimal
Get
Return _saldo
End Get
Set(ByVal Value As Decimal)
If _saldo <> Value Then
_saldo = Value
SetDirtyFlag()
End If
End Set
End Property
'Propiedad para manipular el objeto tipo estado
Public Property TipoEstado() As ERP.TipoEstado
Get
If _tipoEstado Is Nothing Then
_tipoEstado = New ERP.TipoEstado
_tipoEstado.CodigoTipoEstado = Me._codigoTipoEstado
_tipoEstado.Retrieve(_tipoEstado)
End If
Return _tipoEstado
End Get
Set(ByVal Value As ERP.TipoEstado)
If Not CPersistentObject.Equals(_tipoEstado, Value) Then
_tipoEstado = Value
CodigoTipoEstado = _tipoEstado.CodigoTipoEstado
SetDirtyFlag()
End If
End Set
End Property
'Propiedad para manipular el atributo _codigoTipoEstado
Public Property CodigoTipoEstado() As Integer
Get
If _tipoEstado Is Nothing Then
Return _codigoTipoEstado
Else
Return _tipoEstado.CodigoTipoEstado
End If
End Get
Set(ByVal Value As Integer)
If Not _tipoEstado Is Nothing Then
If _tipoEstado.CodigoTipoEstado <> Value Then
_tipoEstado.CodigoTipoEstado = Value
SetDirtyFlag()
End If
End If
_codigoTipoEstado = Value
End Set
End Property
' Disminuir el saldo
Public Function decrementarCantidad(ByVal c As Integer) As Transaccion
Me._saldo = Me._saldo - c
'Generar la transaccion respectiva
Dim t As Transaccion = New Transaccion
t.CodigoTransaccion = ERP.GestorSerial.getSerial("Transaccion")
t.Valor = c
t.Saldo = Me.Saldo 'Ultimo saldo se asigana a transaccion
t.Descripcion = "Ingreso en la cuenta"
t.Fecha = Now
t.Cuenta = Me
Me.agregarTransaccion(t) 'Agregar Transaccion a la cuenta
Return t
End Function
' Incrementar el saldo de la cuenta
Public Function incrementarCantidad(ByVal c As Decimal) As Transaccion
Me._saldo = Me._saldo + c
'Generar la transaccion respectiva
Dim t As Transaccion = New Transaccion
t.CodigoTransaccion = ERP.GestorSerial.getSerial("Transaccion")
t.Valor = c
t.Saldo = Me._saldo 'Ultimo saldo se asigana a transaccion
t.Descripcion = "Ingreso en la cuenta"
t.Fecha = Now
t.Cuenta = Me
Me.agregarTransaccion(t) 'Agregar Transaccion a la cuenta
Return t
End Function
' Permite agregar una nueva transaccion a la coleccion que posee la cuenta
Public Sub agregarTransaccion(ByVal t As Transaccion)
Me._transacciones.Add(t)
End Sub
' Cambiar de estado un cuenta (Puede estar activada o desactivada).
Public Sub cambiarEstado(ByVal e As ERP.TipoEstado)
Me.TipoEstado = e
End Sub
' Recupera las transacciones asociadas a la cuenta.
Public Function listadoTransacciones() As CPersistentCollection
Return Me._transacciones
End Function
Public Overrides Function getNewObject() As CPersistentObject
Return New Cuenta
End Function
Public Overrides Function IsValid() As Boolean
Return True
End Function
End Class ' END CLASS DEFINITION Cuenta
End Namespace ' GestionBancosCaja
' Modelo esttico
Imports AToMSFramework
Namespace GestionBancosCaja
Public Class TransaccionC
Inherits CPersistentCollection
Public Overloads Sub Add(ByVal ol As Transaccion)
MyBase.Add(ol)
End Sub
Default Public Shadows ReadOnly Property Item(ByVal index As Integer) As Transaccion
Get
Return CType(list.Item(index), Transaccion)
End Get
End Property
Public Overrides ReadOnly Property AllowEdit() As Boolean
Get
Return True
End Get
End Property
Public Overrides Function AddNew() As Object
Dim ol As New Transaccion
If Not Me.ContainerObject Is Nothing Then
'Assign the order header reference if possible
'Dim p As Transaccion = Me.ContainerObject
ol.Cuenta = Me.ContainerObject
End If
list.Add(ol)
Return ol
End Function
Public Overrides ReadOnly Property AllowNew() As Boolean
Get
Return Not Me.ContainerObject Is Nothing
End Get
End Property
Public Overrides ReadOnly Property AllowRemove() As Boolean
Get
Return True
End Get
End Property
End Class
' Clase base para registro de movimientos en las diferentes cuentas.
Public Class Transaccion
Inherits CPersistentObject
' OID del objeto transaccion
Private _codigoTransaccion As Integer
' Fecha en la que se realiza la transaccion
Private _fecha As Date
' Cantidad que implica la transaccionn.
Private _valor As Decimal
' Saldo despues de incrementar o decrementar el saldo de la cuenta. Es mas informativo, para no tener que volver a recalcular el saldo en cierta fecha.
Private _saldo As Decimal
' Descripcion corta de la transaccion.
Private _descripcion As String
' Cuenta a la que pertenece
Private _cuenta As Cuenta
Private _codigoCuenta As Integer
' Constructor
Public Sub New()
End Sub
' Propiedad para acceder al atributo _codigoTransaccion
Public Property CodigoTransaccion() As Integer
Get
Return _codigoTransaccion
End Get
Set(ByVal Value As Integer)
If _codigoTransaccion <> Value Then
_codigoTransaccion = Value
SetDirtyFlag()
End If
End Set
End Property
' Propiedad para acceder al atributo _fecha
Public Property Fecha() As Date
Get
Return _fecha
End Get
Set(ByVal Value As Date)
If _fecha <> Value Then
_fecha = Value
SetDirtyFlag()
End If
End Set
End Property
' Propiedad para acceder al atributo _valor
Public Property Valor() As Decimal
Get
Return _valor
End Get
Set(ByVal Value As Decimal)
If _valor <> Value Then
_valor = Value
SetDirtyFlag()
End If
End Set
End Property
' Propiedad para acceder al atributo _saldo
Public Property Saldo() As Decimal
Get
Return _saldo
End Get
Set(ByVal Value As Decimal)
If _saldo <> Value Then
_saldo = Value
SetDirtyFlag()
End If
End Set
End Property
' Propiedad para acceder al atributo _descripcion
Public Property Descripcion() As String
Get
Return _descripcion
End Get
Set(ByVal Value As String)
If _descripcion <> Value Then
_descripcion = Value
SetDirtyFlag()
End If
End Set
End Property
'Propiedad para manipular el objeto Cuenta
Public Property Cuenta() As Cuenta
Get
If _cuenta Is Nothing Then
_cuenta = New Cuenta
_cuenta.CodigoCuenta = Me._codigoCuenta
_cuenta.Retrieve(_cuenta)
End If
Return _cuenta
End Get
Set(ByVal Value As Cuenta)
If Not CPersistentObject.Equals(_cuenta, Value) Then
_cuenta = Value
CodigoCuenta = _cuenta.CodigoCuenta
SetDirtyFlag()
End If
End Set
End Property
'Propiedad para manipular el atributo _codigoCuenta
Public Property CodigoCuenta() As Integer
Get
If _cuenta Is Nothing Then
Return _codigoCuenta
Else
Return _cuenta.CodigoCuenta
End If
End Get
Set(ByVal Value As Integer)
If Not _cuenta Is Nothing Then
If _cuenta.CodigoCuenta <> Value Then
_cuenta.CodigoCuenta = Value
SetDirtyFlag()
End If
End If
_codigoCuenta = Value
End Set
End Property
Public Overrides Function getNewObject() As CPersistentObject
Return New Transaccion
End Function
Public Overrides Function IsValid() As Boolean
Return True
End Function
End Class ' END CLASS DEFINITION Transaccion
End Namespace ' GestionBancosCaja
Saludos
Victor.
Your code was this:
'Begin transaction
dim t as Transaccion = new Transaccion
dim t1 as Transaccion= new Transaccion
.......
c.transacciones.add(t)
c.transacciones.add(t2)
c.transacciones(1).delete
Since it's not in the sample code do I assume that you are doing a CPersistenceBroker.StartTransaction() call before you add/delete items in the collection?
You might want to look at the CheckCacheAfterRollback4 unit test in CVS (in InheritedClasses/AtomsFrameworkTests.vb) since it does both the add and delete from the collection during a transaction and it works properly.
Hi.
I am doing a CPersistenceBroker.StartTransaction()
dim t as Transaccion = new Transaccion
dim t1 as Transaccion= new Transaccion
.......
c.transacciones.add(t)
c.transacciones.add(t2)
c.transacciones(1).delete
now, if I execute rollBack. The cache of the collection is incorrect. Because the collection don't have the same objects.
The unique diference is that I am using inherits of the CPersistentCollection how container.
Saludos
Victor
I don't think that should make a difference since I am only looking for objects that implement IList or IDictionary. If you inherit the CPersistentCollection then you will have an object that implements IList.
Can you do some debugging for me?
In CCacheEntry.vb set breaks at CopyCollections() and at RestoreCollections().
What _should_ happen is that when the transaction starts the collections in the object should be copied into new objects and placed in a "collection of collections" called m_collectionCollection.
When the transaction aborts RestoreCollections should copy the collections from m_collectionCollection over the collections in the object they came from.
If you can verify a few things it will help
1. That the collections are all being correctly copied in the first place. (Look at f.name to see what field is being copied).
2. That the restoreCollections method is being called
3. That the collections are being correctly copied back out of m_collectionCollection.
I appreciate the help on this one
- Richard.
Hi.
I have a few questions?
First:
My example is when a collection is empty.
When execute the method CopyCollection in a code:
m_collectionCollection.Add(f.Name, coll) is correct.
The value of the m_collectionCollection.count=3.
Now if I execute rollback of the transsaction, the code : RestoreCollections() is called.
In the method AbortChanges the instructions:
For Each ck In al
MyBase.Remove(ck)
Next
also are called. That is to say, the remove is executed.
Now, My object continues with the information in the collection, the previous one is not restored.
The following code in RestoreCollection:
If Not iListType Is Nothing OrElse Not iDicType Is Nothing Then
'We must restore this collection from the collection copy
'Just use a straight value assignment - no need to worry about cloning
f.SetValue(m_object.GetSourceObject, m_collectionCollection.Item(f.Name))
End If
It is never executed (f.SetValue), Why ???
Why to do the cache of all the collecciones of the objects in cache. I think that will be alone of the objects that are used in the transaction.
Saludos
Victor
>The value of the m_collectionCollection.count=3.
This indicates that there were 3 collection objects that were copied. This would mean that the Cuenta private fields:
Private _pagos As System.Collections.ArrayList
Private _cobros As System.Collections.ArrayList
Private WithEvents _transacciones As TransaccionC
were all copied correctly.
>In the method AbortChanges the instructions:
>For Each ck In al
>MyBase.Remove(ck)
>Next
This code is actually removing objects from the cache that were added during the transaction. In other words, if you begin a transaction, create and save an object and then abort the transaction then the object you created should be removed from the cache. This code won't affect collections of individual objects
>The following code in RestoreCollection:
>If Not iListType Is Nothing OrElse Not iDicType Is Nothing Then
>'We must restore this collection from the collection copy
>'Just use a straight value assignment - no need to worry about cloning
>f.SetValue(m_object.GetSourceObject, m_collectionCollection.Item(f.Name))
>End If
If RestoreCollections is called but f.SetValue() is never called then none of the fields for the object implement the IList or IDictionary interface.
I assume that m_object.GetSourceObject is the same object that the collections were copied from in CopyCollections(). I also assume that the m_collectionCollection.Count value is still 3.
If you set a break on the line
iListType = f.FieldType.GetInterface("IList", True)
in RestoreCollections() and then check the value of f.name you should see that the _pagos, _cobros and _transacciones fields are all checked to see if they implement the IList interface (iListType should not be nothing when the call completes). If that doesn't happen can you let me know what you find in the debug session.
By the way, I noticed that in class Cuenta you have
> Private WithEvents _transacciones As TransaccionC
But that the property exposes a different type
> Property Transacciones() As CPersistentCollection
It might be a good idea to change the property type to match that of the internal object
I hope we get to the bottom of this soon
- Richard.
Hi.
The problem is that in the following code:
Public Sub RestoreCollections()
Dim t, iEnumerableType, iListType, iDicType As Type
Dim f, fields() As FieldInfo
t = m_object.GetObjectType
fields = t.GetFields(BindingFlags.Instance Or BindingFlags.NonPublic Or BindingFlags.Public)
For Each f In fields
iListType = f.FieldType.GetInterface("IList", True)
iDicType = f.FieldType.GetInterface("IDictionary", True)
'iEnumerableType = f.FieldType.GetInterface("IEnumerable", True)
If Not iListType Is Nothing OrElse Not iDicType Is Nothing Then
'We must restore this collection from the collection copy
'Just use a straight value assignment - no need to worry about cloning
f.SetValue(m_object.GetSourceObject, m_collectionCollection.Item(f.Name))
End If
Next
End Sub
the atribute fields does not have the collection name, f.name doesn't have _transacciones, _pagos, _cobros.
The problem is that in the code:
t = m_object.GetObjectType
m_object does not have the object that possesses the collections.
This means that in the method AbortChanges of CCacheEntry doesn't have my object that possesses the collections or it does not fulfill certain conditions.
In the next code in method AbortChanges:
For Each x In Me
ce = x.Value
'Lets cancel deletes first - just means resetting transaction to None
If ce.TransactionType = CCacheEntry.CacheTransaction.Deleted AndAlso ce.PersistentObject.GetClassMap.RelationalDatabase Is reldb Then
ce.TransactionType = CCacheEntry.CacheTransaction.None
ce.RestoreCollections()
End If
'Now look for cache entries to be saved and restore them
If ce.TransactionType = CCacheEntry.CacheTransaction.Saved AndAlso ce.PersistentObject.GetClassMap.RelationalDatabase Is reldb Then
ce.RestoreObject()
ce.RestoreCollections()
ce.TransactionType = CCacheEntry.CacheTransaction.None
End If
Next
The instructions are never executed inside the sentence IF( if ce is an object with collections).
Which they are the conditions in order that these instructions are executed.
Saludos
victor
>The problem is that in the code:
>t = m_object.GetObjectType
>m_object does not have the object that possesses the collections.
m_object is an instance of the interface IPersistableObject and is designed to wrap around the actual object being saved. It can be either a CInjectedObject or a CPersistentObject depending on wether the object inherits from CPeristentObject or not.
the GetObjectType property will return the type of the object that m_object is wrapping. If you have a look at m_object.GetObjectType.Name you should get a value of "Cuenta"
Now something occurred to me when you said the code in the IF statements didn't execute. The code to restore the collections will only execute if the object has been saved or deleted inside a transaction.
There is no code to roll back the changes if the object is dirty (ie modified) but not yet saved.
For example
broker.StartTransaction()
myObject.Property = NewValue
myObject.Save()
broker.Rollback()
will restore myObject correctly, while
broker.StartTransaction()
myObject.Property = NewValue
broker.Rollback()
will leave myObject.Property in it's current (modified) state - however the cached copy of the object should still be in it's original state.
So I went and wrote a unit test that changed an object, aborted the transaction and checked the cached copy of the object.
I found that when the cache returned a copy of an object to the application it was only doing a shallow copy. Normally this is perfectly fine, however because collections are copied by reference, changing the collection in the application changed it in the cache.
I've just committed some changes on the v2_0 branch in CVS to fix the problem, and hopefully you will now be OK.
By the way, I would not recommend getting the code from the HEAD branch just at the moment as it's a bit messy and some parts are broken while I work on many-to-many associations.
- Richard.
Hi.
My problem was that was not save the principal object and save only objects the collections.
but, now all right.
I do not need to unload the last cvs.
Saludos
Victor