Design question - handling record not found in BO.GetBO

Design question - handling record not found in BO.GetBO

Old forum URL: forums.lhotka.net/forums/t/3598.aspx


griff posted on Wednesday, September 26, 2007

Hi

I am new to CSLA so please bear with me.

I know how to use editable BO and my understanding is in general you have a valid primary key (say  taken from a grid listing valid records) with which to pass into BO.GetBO(myID).

I am having a brain block of how to implement CSLA best when most times you don't expect to find a value in the db each time e.g. scanning in an ISBN into a text box....sometimes the ISBN exists in the db othertimes its a new entry.

So would I do a..... if BO.Exists(ISBN) then...... first to see if the ISBN exists then use either BO.NewBO or BO.GetBO(ISBN) accordingly

or

would I use the BO.GetBO(ISBN) each time and handle inside the DataPortal_Fetch(crit.ISBN) the scenario of not finding a record.....but if I do this how do I implement the DataPortal_Create..can/do I call from within DataPortal_Fetch.

Or is there another way.....Any advice would be welcome.

Richard

 

 

 

 

jhw replied on Wednesday, September 26, 2007

I would use a command obect to see if the isbn exists, then fetch or create as required.

Heath

 

JoeFallon1 replied on Wednesday, September 26, 2007

Richard,

I had this exact question 4 years ago when I first started with CSLA. At the time a partial consensus was formed to go ahead and do the check for existence and branch accordingly.

In the intervening years, Rocky decided to change CSLA to automatically call MarkOld() on root BOs as they return through the DataPortal from a Fetch. I discussed my objections with him at the time since it broke my code. This call "saved" the developer one line of code in each root BO. But it does not change the child behavior where the developer still needs to add it. I thought this was inconsistent and would lead to problems. But the CSLA community did not object as strenuously and the change was made. Therefore I have to comment out the calls for MarkOld and MarkNew in the framework.

Interestingly, Rocky got bitten by this implementation while working on WPF in the most recent version. It turns out his problem is identical to my original objection - there are times as a developer when I want the object to be New and other times I want it to be Old. And I need this to happen before returning through the DataPortal - e.g. when calling ValidationRules.CheckRules after a fetch - some rules may depend on if the BO is New or Old. So I have the code for MarkOld() in all my root BOs, just like I always used to. And if I happen to branch and return a New root BO then the framework code which I commented out does not burn me by changing it back to Old.

I think the current community consensus is to throw an Exception from DP_Fetch and handle it in the UI. e.g. the UI asks for a fetch of an ISBN, an exception is thrown because it is not in the DB and the UI catches it and then asks for a New BO by calling DP_Create.

4 years ago we decided to do the check in the BO layer and return a New object if it was not in the DB.

Here is how my Data Access region is currently laid out:

#Region " Data Access "

Protected Overridable Sub SetDefaults()
  IsOwner =
False
 
Table = "SomeBO"
 
mTstamp.Date = Date.Now
End Sub

#Region "DataPortal_Create"

<RunLocal()> _
Protected Overridable Overloads Sub DataPortal_Create(ByVal criteria As Object)
 
If TypeOf criteria Is CriteriaCode Then
   
Dim crit As CriteriaCode = DirectCast(criteria, CriteriaCode)
    mCostcode = crit.Code
 
Else
   
Throw New ApplicationException("Invalid criteria passed to DataPortal_Create method.")
 
End If

  SetDefaults()
  MarkNew()
  ValidationRules.CheckRules()
End Sub

#End Region

#Region " DataPortal_Fetch "

Protected Overrides Sub DataPortal_Fetch(ByVal criteria As Object)
 
If TypeOf criteria Is CriteriaCode Then
   
Dim crit As CriteriaCode = DirectCast(criteria, CriteriaCode)
   
If SomeDAO.RecordDoesNotExist(crit.Code) Then
     
Me.DataPortal_Create(criteria)
   
Else
     
DoFetch(criteria)
   
End If
 
Else
   
Throw New ApplicationException("Invalid criteria passed to DataPortal_Fetch method.")
 
End If
End Sub

Protected Overridable Sub DoFetch(ByVal criteria As Object)
  SetDefaults()
 
Dim cn As IDbConnection = Nothing
 
Try
   
cn = DAL.CreateConnection
    cn.Open()
    FetchData(criteria, cn)
    PostFetchData(criteria, cn)
    FetchChildren(criteria, cn)
 
Finally
   
cn.Close()
 
End Try

  MarkOld()
  ValidationRules.CheckRules()
End Sub

Protected Overridable Sub FetchData(ByVal criteria As Object, ByVal cn As IDbConnection)
  
Dim dr As SafeDataReader
 
If TypeOf criteria Is CriteriaCode Then
   
Dim crit As CriteriaCode = DirectCast(criteria, CriteriaCode)
    dr =
New SafeDataReader(DAL.ExecuteReader(cn, CommandType.Text, SomeDAO.Select(crit.Code))) 
 
Else
   
Throw New ApplicationException("Invalid criteria passed to FetchData method.")
 
End If

 
Try
   
With dr
     
If .Read() Then
       
mVar1 = Trim(.GetString("var1"))
        mVar2 = Trim(.GetString("var2"))
        mVar3 = Trim(.GetString("var3"))
        mVar4 = Trim(.GetString("var4"))
      End If
   
End With
 
Finally
   
dr.Close()
 
End Try

End Sub

Protected Overridable Sub PostFetchData(ByVal criteria As Object, ByVal cn As IDbConnection)
  'marker method that can be overridden in child class
End Sub

Protected Overridable Sub FetchChildren(ByVal criteria As Object, ByVal cn As IDbConnection)
 
'marker method that can be overridden in child class
End Sub

#End Region

Note: I store all of my Criteria classes in a separate file and re-use them across all BOs that need them.

e.g.

<Serializable()> _
Public Class CriteriaCode
 
Inherits Csla.CriteriaBase

 
Public MethodName As String = ""
 
Public Code As String = ""

 
Public Sub New(ByVal BOtype As Type, ByVal methodName As String, ByVal code As String)
   
MyBase.New(BOtype)
   
Me.MethodName = methodName
   
Me.Code = code
 
End Sub

End Class

Note: I use the methodName parameter to branch in the DP if I have many factory methods fetching data and they use the same Criteria class. (If they use different Criteria clsases I can branch on TypeOf criteria.)

e.g.

Public Shared Function GetMyBO(ByVal mycode As String) As MyBO
 
Return DataPortal.Fetch(Of MyBO)(New CriteriaCode(GetType(MyBO), "GetMyBO", mycode))
End Function

Joe

 

 

 

ajj3085 replied on Wednesday, September 26, 2007

Just keep in mind though that this isn't a supported scenario, and as Csla does more "pluming" (like calling MarkClean, MarkOld, etc) this may introduce more pain.

griff replied on Wednesday, September 26, 2007

<Just keep in mind though that this isn't a supported scenario> 

which scenario do you mean, ajj3085?

1. BO.Exists and branch

2. Raise error in DP_Fetch and handle in UI

3. Handle branching in DP_Fetch as per Joe

ajj3085 replied on Wednesday, September 26, 2007

Sorry.  I meant Joe's implementation.  You'll notice that he said he had to modify the framework to remove calls to MarkOld, etc. so that DP_F would return a new object if one didn't exist.

You can do #1, but if the object in question does exist, you end up doing two database (and DataPortal) calls per load.  So #2 is the option most recommend.

Copyright (c) Marimer LLC