Add Only Entity

Posts   
 
    
ADF1969
User
Posts: 37
Joined: 28-Mar-2006
# Posted on: 04-Feb-2007 18:27:51   

I have a fairly complicated business object requirement, and was curious what would be the recommended method of solving it.

In summary, I need to have the ability for a specificy Entity to be Add-Only, meaning whenever a user "edits" a record, instead of the records "updating", I want it to create a new record based on the "current record", update the "current" record (by changing it's EndDate) and then save both records.

That's the summary, here is the specific data and some more details:

I am using SelfServicing with the latest version of the DLLs and Templates.

Here is a portion of the data structure: AccountEntity: the entity that contains the client account data Account.Contracts: IEntityCollection of ContractEntity records

ContractEntity: the entity that contains the contract begin/end date ContratEntity.ContractProducts: IEntity of ContractProductEntity records ContractEntity.DateBegin: effective start date of the Contract ContractEntity.DateEnd: effective end date of the Contrat

ContractProductEntity: the entity that contains the products for each Contract, including product code and product price.

To complicate matters a bit further, there are 4 custom properties that have been defined in the ContractEntity to allow access to specific ContractProductEntity records for the current Contract.

Here is the code for one of the 4 custom ContractProductEntity properties along with the 2 helper routines:


                ' Helper Routine to retrieve a product by Product Code
        Private m_productsHashtable_ProductCode As Hashtable = Nothing
        Public Function GetProductByProductCode(ByVal ProductCode As String, _
         Optional ByVal ReturnsNewIfNotFound As Boolean = False) As ContractProductEntity

            Dim eReturnProduct As ContractProductEntity = Nothing

            ' Scrub/Trap
            If (ProductCode Is Nothing OrElse ProductCode.Length = 0) Then
                logger.Warn("** ProductCode of '{0}' is Nothing or 0-Len. Returning Nothing.", ProductCode)
                Return Nothing
            End If

            logger.Trace("BEGIN: ProductCode = '{0}', ReturnsNewIfNotFound = '{1}'", _
             ProductCode, ReturnsNewIfNotFound)
            ' Process
            Try
                ' If We haven't built HT on TableKey, Build It
                If m_productsHashtable_ProductCode Is Nothing Then
                    ' Build HashTable
                    logger.Trace("Build the ProductCode Hashtable")
                    Dim eContractProduct As ContractProductEntity
                    Dim cContractProducts As ContractProductCollection
                    cContractProducts = Me.GetMultiContractProducts(False)
                    m_productsHashtable_ProductCode = New Hashtable(cContractProducts.Count)
                    For Each eContractProduct In cContractProducts
                        m_productsHashtable_ProductCode(eContractProduct.ProductCode) = eContractProduct
                    Next
                End If

                eReturnProduct = TryCast(m_productsHashtable_ProductCode(ProductCode), ContractProductEntity)
                If (eReturnProduct Is Nothing) Then
                    If (ReturnsNewIfNotFound) Then
                        ' Creating New ContractProduct
                        logger.Trace("ReturnsNewIfNotFound is True. Try creating New ContractProductEntity.")
                        Dim eNewContractProduct As New ContractProductEntity
                        With eNewContractProduct
                            .Contract = Me  ' Add it to the current contract
                            .ProductCode = ProductCode ' Set the ProductCode to the one requested                                                   
                            .Transaction = Me.Transaction
                        End With

                        ' Now add it to Hashtable
                        m_productsHashtable_ProductCode(eNewContractProduct.ProductCode) = eNewContractProduct
                    Else
                        logger.Warn("** No ContractProduct found with ProductCode of '{0}'. Returning Nothing.", ProductCode)
                        Return Nothing
                    End If
                End If

                eReturnProduct = TryCast(m_productsHashtable_ProductCode(ProductCode), ContractProductEntity)
                If (eReturnProduct Is Nothing) Then
                    logger.Warn("** No ContractProduct found with ProductCode of '{0}'. Returning Nothing.", ProductCode)
                    Return Nothing
                Else
                    Return eReturnProduct
                End If
            Catch ex As Exception
                logger.ErrorException("** Failed to get Product by ProductCode. Returning Nothing. **", ex)
            End Try
            Return Nothing
        End Function


                ' Helper Property for Hard-Coded ContractProducts
        Public Property ContractProductPrice(ByVal ProductCode As String) As Decimal
            Get
                Dim eContractProduct As ContractProductEntity = _
                 Me.GetProductByProductCode(ProductCode, True)
                If (eContractProduct Is Nothing) Then
                    logger.Error("Cannot retrieve and/or create ContractProduct with ProductCode of '{0}'", ProductCode)
                Else
                    Return eContractProduct.ProductPrice
                End If
            End Get
            Set(ByVal Value As Decimal)
                Dim eContractProduct As ContractProductEntity = _
                 Me.GetProductByProductCode(ProductCode, True)
                If (eContractProduct Is Nothing) Then
                    logger.Error("Cannot retrieve and/or create ContractProduct with ProductCode of '{0}'", ProductCode)
                Else
                    eContractProduct.ProductPrice = Value
                End If
            End Set
        End Property

                ' Hard-Code ContractProducts - these are created to simplify DataEntry
        <Divix.Data2.DivixField()> _
        Public Property PriceDOT() As Decimal
            Get
                Return ContractProductPrice(ContractProductEntity.gc_pc_DrugTestDOT)
            End Get
            Set(ByVal Value As Decimal)
                ContractProductPrice("DOTProduct") = Value
                Me.OnPropertyChanged("PriceDOT")
            End Set
        End Property

The requirements for "saving" a "Contract" are as follows (these are currently being checked in the OnSave() method):

1) If there are no Contracts for the current Account and the record IsNew = True, save the current Contract with the user specified DateBegin and a DateEnd of Date.MaxValue

2) If there are no Contracts for the current Account (except the Current one being saved) and the record IsNew = False, do the following:

a) Create a New Contract and copy all the properties from the one to be saved into it. This New Contract needs to then be "saved" b) change the current Contract's DateEnd to the DateBegin of the "New Contract" so they do not overlap. This "current" contract also needs to be saved. The "New Contract" will get a "DateEnd" of Date.MaxDate

3) If there are previous existing Contracts,

a) any Contract such that the DateEnd > Current Contract.DateBegin, change the DateEnd = CurrentContract.DateBegin - 1tick. b) any Contract such that the DateBegin > Current Contract.DateBegin, change the DateBegin = CurrentContract.DateBegin -1tick.

That pretty much sums it up. Basically, the user doesn't want to have to view a "list" of contracts, they want to just be able to update the "Contract Form" and have it ensure none of the Contracts overlap - this is largely to prevent the user from creating "overlapping contracts." And since the user needs the ability to specify "DateBegin"s that are in the past, I have to handle "updating" existing Contract records accordingly so when the user clicks "save" it doesn't create overlapping Contract records.

My questions are this: 1) Is the best place to "trap" and handle these updates in the ContractEntity.OnSave method? I currently am doing that, and for cases #1-#2 it works fine, for case #3 it is having issues.

2) Is there a way to "trap" for something like "Save Begin" so before the Save starts, I can determine if I want that to happen?

3) Is there a way to "abort" the save of the Current ContractEntity? (at the moment, what I am doing is updating all other ContractEntity records and saving them, and then just updating the "properties" for the current contract and then allowing it to "save" in the OnSave event.)

I can certainly post all the code, but it is a bit lengthy (especially with all the debug output that I have in there - I use NLog BTW and it rocks).

What I'm looking for here is just a bit of guidance as to the recommended way to approach this (to ensure I'm not doing it all wrong). And one other specification: I can't call the "Save" of Contracts from the Account; since the "ContractEntity" is edited on a form directly, the "ContractEntity.Save()" is what gets called, usually as: ContractEntity.Save(True).

Thanks for all your help - I normally have very little issues with updates/saves with LGP but this one is kicking my butt.

Andrew.

Chester
Support Team
Posts: 223
Joined: 15-Jul-2005
# Posted on: 04-Feb-2007 21:35:16   

I had a similar requirement on a past project, but we used business objects that wrapped the LLBLGen entity objects. You basically need to do the following:

Override the entity's Save() method. In that method you want to do something like this pseudo code:


if(this.IsNew)
   return base.Save();
else
{
//get a transaction to wrap all operations together
//store the PK of the current entity
//get a new instance of that entity for modifying, passing in the transaction
//update the date or whatever as your scenario requires
//save that instance
//mark the current instance as new
this.IsNew = true;
}

This is oversimplified but hopefully gives you something to get started with.