- Home
- LLBLGen Pro
- LLBLGen Pro Runtime Framework
Add Only Entity
Joined: 28-Mar-2006
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.
Joined: 15-Jul-2005
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.