Imports System.IO Imports System.Runtime.Serialization.Formatters.Binary 'Imports System.Configuration ''AyaNova Imports Imports System.Threading Imports CSLA.Security ''' ''' This is the base class from which most business collection ''' objects will be derived. ''' ''' ''' ''' To create a collection of business objects, inherit from this ''' class. The business objects contained in this collection must ''' inherit from , and the objects ''' must be marked as child objects. ''' ''' Please refer to 'Expert One-on-One VB.NET Business Objects' for ''' full details on the use of this base class to create business ''' collections. ''' ''' _ Public MustInherit Class BusinessCollectionBase Inherits CSLA.Core.SortableCollectionBase Implements ICloneable Implements Serialization.ISerializationNotification #Region " Contains " ''' ''' Used to see if the collection contains a specific child object. ''' ''' ''' Only the 'active' list of child objects is checked. ''' Business collections also contain deleted objects, which are ''' not checked by this call. ''' ''' A reference to the object. ''' True if the collection contains the object. Public Function Contains(ByVal Item As BusinessBase) As Boolean 'Return list.Contains(Item) Dim element As BusinessBase For Each element In list If element.Equals(Item) Then Return True End If Next Return False End Function ''' ''' Used to see if the collection contains a reference to a ''' child object that is marked for deletion. ''' ''' ''' This scans the list of child objects that have been marked ''' for deletion. If this object is in that list, the method ''' returns True. ''' ''' A reference to the object. ''' True if the collection contains the object. Public Function ContainsDeleted(ByVal Item As BusinessBase) As Boolean Dim element As BusinessBase For Each element In deletedList If element.Equals(Item) Then Return True End If Next Return False End Function #End Region #Region " IsDirty, IsValid " ''' ''' Returns True if this object's data has been changed. ''' ''' ''' ''' When an object's data is changed, CSLA .NET makes note of that change ''' and considers the object to be 'dirty' or changed. This value is used to ''' optimize data updates, since an unchanged object does not need to be ''' updated into the database. All new objects are considered dirty. All objects ''' marked for deletion are considered dirty. ''' ''' Once an object's data has been saved to the database (inserted or updated) ''' the dirty flag is cleared and the object is considered unchanged. Objects ''' newly loaded from the database are also considered unchanged. ''' ''' ''' If any child object within the collection is dirty then the collection ''' is considered to be dirty. If all child objects are unchanged, then the ''' collection is not dirty. ''' ''' ''' A value indicating if this object's data has been changed. Public ReadOnly Property IsDirty() As Boolean Get ' any deletions make us dirty If deletedList.Count > 0 Then Return True ' run through all the child objects ' and if any are dirty then the ' collection is dirty Dim Child As BusinessBase For Each Child In list If Child.IsDirty Then Return True Next Return False End Get End Property ''' ''' Returns True if the object is currently valid, False if the ''' object has broken rules or is otherwise invalid. ''' ''' ''' ''' By default this property relies on the underling ''' object to track whether any business rules are currently broken for this object. ''' ''' You can override this property to provide more sophisticated ''' implementations of the behavior. For instance, you should always override ''' this method if your object has child objects, since the validity of this object ''' is affected by the validity of all child objects. ''' ''' ''' If any child object within the collection is invalid then the collection ''' is considered to be invalid. If all child objects are valid, then the ''' collection is valid. ''' ''' ''' A value indicating if the object is currently valid. Public ReadOnly Property IsValid() As Boolean Get ' run through all the child objects ' and if any are invalid then the ' collection is invalid Dim Child As BusinessBase For Each Child In list If Not Child.IsValid Then Return False Next Return True End Get End Property #End Region #Region " IsSavable " 'Added by JOHN, not in original ''' ''' Returns True if this object is both dirty and valid. ''' ''' ''' An object is considered dirty (changed) if ''' returns True. It is ''' considered valid if ''' returns True. The IsSavable property is ''' a combination of these two properties. It is provided specifically to ''' enable easy binding to a Save or OK button on a form so that button ''' can automatically enable/disable as the object's state changes between ''' being savable and not savable. ''' ''' A value indicating if this object is new. Public Overridable ReadOnly Property IsSavable() As Boolean Get Return IsDirty AndAlso IsValid End Get End Property #End Region #Region " Begin/Cancel/ApplyEdit " ''' ''' Starts a nested edit on the object. ''' ''' ''' ''' When this method is called the object takes a snapshot of ''' its current state (the values of its variables). This snapshot ''' can be restored by calling ''' or committed by calling . ''' ''' This is a nested operation. Each call to BeginEdit adds a new ''' snapshot of the object's state to a stack. You should ensure that ''' for each call to BeginEdit there is a corresponding call to either ''' CancelEdit or ApplyEdit to remove that snapshot from the stack. ''' ''' See Chapters 2 and 4 for details on n-level undo and state stacking. ''' ''' This method triggers the copying of all child object states. ''' ''' Public Sub BeginEdit() If Me.IsChild Then Throw New _ NotSupportedException("BeginEdit is not valid on a child object") End If CopyState() End Sub ''' ''' Cancels the current edit process, restoring the object's state to ''' its previous values. ''' ''' ''' Calling this method causes the most recently taken snapshot of the ''' object's state to be restored. This resets the object's values ''' to the point of the last ''' call. ''' ''' This method triggers an undo in all child objects. ''' ''' Public Sub CancelEdit() If Me.IsChild Then Throw New _ NotSupportedException("CancelEdit is not valid on a child object") End If UndoChanges() End Sub ''' ''' Commits the current edit process. ''' ''' ''' Calling this method causes the most recently taken snapshot of the ''' object's state to be discarded, thus committing any changes made ''' to the object's state since the last ''' call. ''' ''' This method triggers an ApplyEdit in all child objects. ''' ''' Public Sub ApplyEdit() If Me.IsChild Then Throw New _ NotSupportedException("ApplyEdit is not valid on a child object") End If AcceptChanges() End Sub #End Region #Region " N-level undo " Friend Sub CopyState() Dim Child As BusinessBase ' we are going a level deeper in editing mEditLevel += 1 ' cascade the call to all child objects For Each Child In list Child.CopyState() Next ' cascade the call to all deleted child objects For Each Child In deletedList Child.CopyState() Next End Sub Friend Sub UndoChanges() Dim Child As BusinessBase Dim Index As Integer ' we are coming up one edit level mEditLevel -= 1 If mEditLevel < 0 Then mEditLevel = 0 ' Cancel edit on all current items For Index = List.Count - 1 To 0 Step -1 Child = CType(list.Item(Index), BusinessBase) Child.UndoChanges() ' if item is below its point of addition, remove If Child.EditLevelAdded > mEditLevel Then list.Remove(Child) Next ' cancel edit on all deleted items For Index = deletedList.Count - 1 To 0 Step -1 Child = deletedList.Item(Index) Child.UndoChanges() ' if item is below its point of addition, remove If Child.EditLevelAdded > mEditLevel Then deletedList.Remove(Child) ' if item is no longer deleted move back to main list If Not Child.IsDeleted Then UnDeleteChild(Child) Next End Sub Friend Sub AcceptChanges() Dim Child As BusinessBase ' we are coming up one edit level mEditLevel -= 1 If mEditLevel < 0 Then mEditLevel = 0 ' cascade the call to all child objects For Each Child In list Child.AcceptChanges() ' if item is below its point of addition, lower point of addition If Child.EditLevelAdded > mEditLevel Then Child.EditLevelAdded = mEditLevel Next ' cascade the call to all deleted child objects For Each Child In deletedList Child.AcceptChanges() ' if item is below its point of addition, lower point of addition If Child.EditLevelAdded > mEditLevel Then Child.EditLevelAdded = mEditLevel Next End Sub #End Region #Region " Delete and Undelete child " Private Sub DeleteChild(ByVal Child As BusinessBase) ' mark the object as deleted Child.DeleteChild() ' and add it to the deleted collection for storage deletedList.Add(Child) End Sub Private Sub UnDeleteChild(ByVal Child As BusinessBase) ' we are inserting an _existing_ object so ' we need to preserve the object's editleveladded value ' because it will be changed by the normal add process Dim SaveLevel As Integer = Child.EditLevelAdded list.Add(Child) Child.EditLevelAdded = SaveLevel ' since the object is no longer deleted, remove it from ' the deleted collection deletedList.Remove(Child) End Sub #End Region #Region " DeletedCollection " ''' ''' A collection containing all child objects marked ''' for deletion. ''' Protected deletedList As New DeletedCollection ''' ''' Defines a strongly-typed collection to store all ''' child objects marked for deletion. ''' _ Protected Class DeletedCollection Inherits CollectionBase ''' ''' Adds a child object to the collection. ''' ''' The child object to be added. Public Sub Add(ByVal Child As BusinessBase) list.Add(Child) End Sub ''' ''' Removes a child object from the collection. ''' ''' The child object to be removed. Public Sub Remove(ByVal Child As BusinessBase) list.Remove(Child) End Sub ''' ''' Returns a reference to a child object in the collection. ''' ''' The positional index of the item in the collection. ''' The specified child object. Default Public ReadOnly Property Item(ByVal index As Integer) As BusinessBase Get Return CType(list.Item(index), BusinessBase) End Get End Property End Class #End Region #Region " Insert, Remove, Clear " ''' ''' This method is called by a child object when it ''' wants to be removed from the collection. ''' ''' The child object to remove. Friend Sub RemoveChild(ByVal child As BusinessBase) list.Remove(child) End Sub ''' ''' Sets the edit level of the child object as it is added. ''' Protected Overrides Sub OnInsert(ByVal index As Integer, ByVal value As Object) If Not ActivelySorting Then ' when an object is inserted we assume it is ' a new object and so the edit level when it was ' added must be set CType(value, BusinessBase).EditLevelAdded = mEditLevel CType(value, BusinessBase).SetParent(Me) MyBase.OnInsert(index, value) End If End Sub ''' ''' Marks the child object for deletion and moves it to ''' the collection of deleted objects. ''' Protected Overrides Sub OnRemove(ByVal index As Integer, ByVal value As Object) If Not ActivelySorting Then ' when an object is 'removed' it is really ' being deleted, so do the deletion work DeleteChild(CType(value, BusinessBase)) MyBase.OnRemove(index, value) End If End Sub ''' ''' Marks all child objects for deletion and moves them ''' to the collection of deleted objects. ''' Protected Overrides Sub OnClear() If Not ActivelySorting Then ' when an object is 'removed' it is really ' being deleted, so do the deletion work ' for all the objects in the list While list.Count > 0 list.RemoveAt(0) 'DeleteChild(CType(list(0), BusinessBase)) End While MyBase.OnClear() End If End Sub #End Region #Region " Edit level tracking " ' keep track of how many edit levels we have Private mEditLevel As Integer #End Region #Region " IsChild " Private mIsChild As Boolean = False ''' ''' Indicates whether this collection object is a child object. ''' ''' True if this is a child object. Protected ReadOnly Property IsChild() As Boolean Get Return mIsChild End Get End Property ''' ''' Marks the object as being a child object. ''' ''' ''' ''' By default all business objects are 'parent' objects. This means ''' that they can be directly retrieved and updated into the database. ''' ''' We often also need child objects. These are objects which are contained ''' within other objects. For instance, a parent Invoice object will contain ''' child LineItem objects. ''' ''' To create a child object, the MarkAsChild method must be called as the ''' object is created. Please see Chapter 7 for details on the use of the ''' MarkAsChild method. ''' ''' Protected Sub MarkAsChild() mIsChild = True End Sub #End Region #Region " Clone " ''' ''' Creates a clone of the object. ''' ''' A new object containing the exact data of the original object. Public Function Clone() As Object Implements ICloneable.Clone Dim buffer As New MemoryStream Dim formatter As New BinaryFormatter Serialization.SerializationNotification.OnSerializing(Me) formatter.Serialize(buffer, Me) Serialization.SerializationNotification.OnSerialized(Me) buffer.Position = 0 Dim temp As Object = formatter.Deserialize(buffer) Serialization.SerializationNotification.OnDeserialized(temp) Return temp End Function #End Region #Region " Data Access " ''' ''' Saves the object to the database. ''' ''' ''' ''' Calling this method starts the save operation, causing the all child ''' objects to be inserted, updated or deleted within the database based on the ''' each object's current state. ''' ''' All this is contingent on . If ''' this value is False, no data operation occurs. It is also contingent on ''' . If this value is False an ''' exception will be thrown to indicate that the UI attempted to save an ''' invalid object. ''' ''' It is important to note that this method returns a new version of the ''' business collection that contains any data updated during the save operation. ''' You MUST update all object references to use this new version of the ''' business collection in order to have access to the correct object data. ''' ''' You can override this method to add your own custom behaviors to the save ''' operation. For instance, you may add some security checks to make sure ''' the user can save the object. If all security checks pass, you would then ''' invoke the base Save method via MyBase.Save(). ''' ''' ''' A new object containing the saved values. Public Overridable Function Save() As BusinessCollectionBase If Me.IsChild Then Throw New NotSupportedException("Can not directly save a child object") End If If mEditLevel > 0 Then Throw New Exception("Object is still being edited and can not be saved") End If If Not IsValid Then Throw New Exception("Object is not valid and can not be saved") End If If IsDirty Then Return CType(DataPortal.Update(Me), BusinessCollectionBase) Else Return Me End If End Function ''' ''' Override this method to load a new business object with default ''' values from the database. ''' ''' An object containing criteria values. Protected Overridable Sub DataPortal_Create(ByVal Criteria As Object) Throw New NotSupportedException("Invalid operation - create not allowed") End Sub ''' ''' Override this method to allow retrieval of an existing business ''' object based on data in the database. ''' ''' An object containing criteria values to identify the object. Protected Overridable Sub DataPortal_Fetch(ByVal Criteria As Object) Throw New NotSupportedException("Invalid operation - fetch not allowed") End Sub ''' ''' Override this method to allow insert, update or deletion of a business ''' object. ''' Protected Overridable Sub DataPortal_Update() Throw New NotSupportedException("Invalid operation - update not allowed") End Sub ''' ''' Override this method to allow immediate deletion of a business object. ''' ''' An object containing criteria values to identify the object. Protected Overridable Sub DataPortal_Delete(ByVal Criteria As Object) Throw New NotSupportedException("Invalid operation - delete not allowed") End Sub ''' ''' Returns the specified database connection string from the application ''' configuration file. ''' ''' ''' The database connection string must be in the appSettings section ''' of the application configuration file. The database name should be ''' prefixed with 'DB:'. For instance, DB:mydatabase. ''' ''' Name of the database. ''' A database connection string. 'Protected Function DB(ByVal DatabaseName As String) As String ' Return System.CXXXonfiguration.ConfigurationManager.AppSettings("DB:" & DatabaseName) 'End Function #End Region #Region " DumpState " Friend Sub DumpState() Dim Child As BusinessBase Debug.WriteLine("BusinessCollectionBase!Count:" & list.Count) Debug.WriteLine("BusinessCollectionBase!DeletedCount:" & deletedList.Count) Debug.WriteLine("BusinessCollectionBase!mIsChild:" & mIsChild) Debug.WriteLine("BusinessCollectionBase!mEditLevel:" & mEditLevel) Debug.Indent() For Each Child In list Child.DumpState() Next Debug.Unindent() End Sub #End Region #Region " ISerializationNotification " ''' ''' This method is called on a newly deserialized object ''' after deserialization is complete. ''' Protected Overridable Sub Deserialized() _ Implements CSLA.Serialization.ISerializationNotification.Deserialized Dim child As Serialization.ISerializationNotification For Each child In list child.Deserialized() Next For Each child In deletedList child.Deserialized() Next End Sub ''' ''' This method is called on the original instance of the ''' object after it has been serialized. ''' Protected Overridable Sub Serialized() _ Implements CSLA.Serialization.ISerializationNotification.Serialized Dim child As Serialization.ISerializationNotification For Each child In list child.Serialized() Next For Each child In deletedList child.Serialized() Next End Sub ''' ''' This method is called before an object is serialized. ''' Protected Overridable Sub Serializing() _ Implements CSLA.Serialization.ISerializationNotification.Serializing Dim child As Serialization.ISerializationNotification For Each child In list child.Serializing() Next For Each child In deletedList child.Serializing() Next End Sub #End Region #Region "AyaNova related convenience items" ''Get the user object so ''we can check rights / get ID value Public ReadOnly Property CurrentUserID() As Guid Get Dim CurrentUser As Security.BusinessPrincipal = CType(Thread.CurrentPrincipal, BusinessPrincipal) Return CurrentUser.ID End Get End Property '' Get security access right level from current identity Public Function GetRight(ByVal RightName As String) As Int32 Return CType(Thread.CurrentPrincipal, BusinessPrincipal).Right(RightName) End Function #End Region End Class