IsSavable Showing False after Marking IsNew on Dataporal_Fetch

IsSavable Showing False after Marking IsNew on Dataporal_Fetch

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


NickTower posted on Friday, April 13, 2007

Hi everyone.  This is my first post.  Thank you Rocky for the excellent framework.  I have read the VB2003 and part of the VB2005 BO books

I'm working with CSLA 2.1.4 in VS2005.

Background: I have is doing a Dataportal Fetch, where the object searches two databases for data.  If it doesn't find its data in the new database store, it searches the prior database store.

Issue: If the object gets its data from the old data store, I try the Me.MarkNew within the Dataportal_Fetch method, so I can simply get the object, and do a save, and have it persist itself to the new data store.

Within the Dataportal_Fetch method, IsSavable method shows true after I call Me.MarkNew, but back in the UI, the IsSavable property on my object is now false.

Any ideas?

Perhaps others will consider this an interesting issue.  It might be addressed in Rocky's book, which I will consult and post on the forum if I resolve this issue myself prior to anyone's replies.

Nickalis

NickTower replied on Friday, April 13, 2007

Update:

This is also related to IsNew, because my 'workaround' of changing a property after fetching the object still doesn't help, because the IsNew is showing false.

So the bottom line is I have to have the MarkNew method work within the DataPortal_Fetch.

Nickalis

ajj3085 replied on Friday, April 13, 2007

Hi and welcome,

I think this is yoru best bet; I know it will give you some more classes, but it should work out nicely.

You could have a static factory, which returns an instance of MyObjectBase, which is an abstract class.  You have a subclass (possibly internal) for each database you are going to search, and each subclass knows which is the appropriate database to get.

The factory knows to try to fetch from on subclass, and if nothing is returned attempts to fetch from the second subclass.  If nothing is again returned, call create on the first subclass.

This way, if the schema in either database changes, you need only change on class.  Also, you're working within the intent of the framework.  This topic has come up before, and the idea is that fetching should return a existing object only, not a new one.  You can probably come up with workarounds, but you'll be fighting the framework, which in the end will cause you some pain. 

There's a pretty comprehensive discussion of this topic.  Be sure to read all the way to the end; Rocky explains why he probably won't remove any automatic calling of MarkOld / MarkNew.

NickTower replied on Friday, April 13, 2007

My Solution:

In order to permit MarkNew to work in the Fetch method it seems I have to comment the following line from SimpleDataPortal.vb

  Fetch function, line 120 : MethodCaller.CallMethodIfImplemented(obj, "MarkOld")

This creates more work because (like in CSLA 1.x ), I'll have to call MarkOld after each Fetch from the new data store.

Nickalis

P.S. any comments I'd love to hear them!

NickTower replied on Friday, April 13, 2007

Thanks ajj3085 , I just posted and saw that you had posted a few minutes ago.

In answer to your post, I'm not actually returning a literal new object from the Fetch method, I'm returning an existing 'object' using data from a (soon to be) obsolete data store, when it doesn't exist yet in the new data store.

Nickalis

ajj3085 replied on Friday, April 13, 2007

Nick,

Yes, I see that now re-reading your post.  However your situation is still very similar; the object represents data that is 'new' to your system, which is by definition a new object.

What that means in Csla land is that you need to have IsNew set to true, so that your DataPortal_Insert runs.

I would still recommend the path I mentioned before, but instead of having two subclasses, you have on class.  The factory, if it doesn't find an existing object, calls DataPortal.Create on your BB class, and passes the criteria used to load from the obsolete data source.  Your DP_C will load from the old criteria.

You can go the route you suggest, but you're affecting every Csla based class in your system.  That seems to be a pretty dramatic change to the framework to make to accomadate one class.  Also, you'll have to keep your change in mind if your application is around long enough that you want to upgrade the version of Csla  you're using and the old data source is still around.

Think carefully on how to proceed; the factory method can be this simple:

public static MyObject GetObject( object criteria ) {
     MyObject result;
     result = DataPortal.Fetch<MyObject>( new FetchCriteria( criteria ) );
     if ( result == null ) {
          result = DataPortal.Create<MyObject>( new FetchOldDataCriteria( criteria ) );
     }
     return result;
}

Seems simplier and won't affect how you use the framework and your other classes built on top of the framework.

HTH
Andy

NickTower replied on Friday, April 13, 2007

Thanks for the replies Andy.  I'll keep that in mind.  One of the issues in our system is that the criterion changes depending on which system I'm drawing data from, so the Factory Method of yours, using a single criteria parameter, wouldn't work in this case.

For future posterity of the CSLA community, I'll summarize my problem and solution.

Problem:  All data must be migrated from an old db system to a new database system.

Solution:  What I call "On-Demand, Non-Destructive Migration" of data:

That means the initial fetch for all object data will be from the old system, and each object is  considered new to the new db, which means each object's first Save() must perform a Dataporal_Insert against the new database (not a Dataportal_update to the old system), since they don't yet exist in the new system.

After the each object has saved itself to the new db, the 2nd and subsequent Fetch calls must  retrieve it from the new system.

Using the CSLA 2.1.4 framework, this is an example Dataportal_Fetch I made, which follows Rocky's ProjectTracker Fetch call methodology, with one exception:  When the object Fetches data from the old system, it calls  Me.MarkNew, and when the object Fetches data from the new system, it calls Me.MarkOld.  This requires a change to the SimpleDataPortal class as mentioned previously in this thread.

BEGIN CODE ------------------------------------------------------------------------------

Private Overloads Sub DataPortal_Fetch(ByVal criteria As Criteria)

Debug.Print("=== Supplier.DataPortal_Fetch")

If Not criteria.Id.Equals(Guid.Empty) Then

'JM.NET Fetch

Using cn As New SqlConnection(Database.MasterListsConnection)

cn.Open()

Using cm As SqlCommand = cn.CreateCommand

With cm

.CommandType = CommandType.StoredProcedure

.CommandText = "getSupplier"

.Parameters.AddWithValue("@ID", criteria.Id)

'SP returns the Supplier object

Using dr As New SafeDataReader(.ExecuteReader)

If dr.Read() Then

Debug.Print("Against " & Database.MasterListsConnection)

Dim ThisSupplier As Supplier = Deserialize(dr.GetValue("Object"))

With ThisSupplier

mId = .Id

mFriendlyName = .FriendlyName

mCompanyName = .CompanyName

mAddress = .Address

mCity = .City

mStateProvince = .StateProvince

mPostalCode = .PostalCode

mCountry = .Country

mContact1 = .Contact1

mContact2 = .Contact2

mPhone = .Phone

mFax = .Fax

mNotes = .Notes

End With

dr.GetBytes("LastUpdated", 0, mTimestamp, 0, 8)

'After Fetching the object, Mark it as old

Me.MarkOld()

End If

End Using

End With

End Using

End Using

Else

'Try JM97 Fetch

If criteria.Name.Trim.Equals("") Then

Throw New ArgumentNullException("Blank Criteria")

Else

Using cn As New OleDb.OleDbConnection(My.Settings.PURCHConnectionString)

Dim cmd As New OleDb.OleDbCommand("Select * From Suppliers Where Supplier = '" & criteria.Name.Trim & "'", cn)

cn.Open()

Dim dr As New SafeDataReader(cmd.ExecuteReader())

While dr.Read

mId = Guid.NewGuid

mFriendlyName = dr.GetString(1)

mCompanyName = mFriendlyName

mAddress = dr.GetString(2)

mCity = dr.GetString(3)

mStateProvince = dr.GetString(4)

mPostalCode = dr.GetString(5)

mCountry = dr.GetString(6)

mContact1 = dr.GetString(7)

mContact2 = dr.GetString(8)

mPhone = dr.GetString(9)

mFax = dr.GetString(10)

mNotes = dr.GetString(11)

Exit While

End While

dr.Close()

End Using

'DEVNOTE: For all JM97 FETCH, YOU MUST MARK THE OBJECT AS NEW, SO IT WILL BE SAVABLE INTO JM.NET

Me.MarkNew()

End If

End If

End Sub

END CODE ------------------------------------------------------------------------------

I love this framework !!! Nickalis

 

McManus replied on Monday, April 16, 2007

Nick,

MarkOld() is called by the DataPortal. You could opt to call MarkNew() in the factory method, just after the call to DataPortal.Fetch().

You could have a private field/property MustMarkNew (or something like that) which would be set in the DataPortal_Create(). If the data came from the old datastore, you should set this property to true. Then, in the factory method, you can call MarkNew() on the object before it is returned to the client.

Using this solution, you don't have to change the CSLA framework.

Cheers,

Herman

NickTower replied on Monday, April 16, 2007

Herman,

You had me excited for a moment.

That's excellent... except the factory method is a shared method, and from it I couldn't access an instance field (mMarkNew) accessible to Dataportal_Fetch.

Or did I miss something?

Nick

ajj3085 replied on Monday, April 16, 2007

Nick,

You can call instance methods from a static member.

Just store the DP_F result into a variable, and call MarkNew on that variable.

public static MyObject Get( string id ) {
     MyObject result;

      result = DataPortal.Fetch<MyObject>( new IdCriteria( id ) );
      result.MarkNew();

      return result;
}

That's the basic code to acomplish what you need.

McManus replied on Monday, April 16, 2007

ajj3085:

public static MyObject Get( string id ) {
     MyObject result;

      result = DataPortal.Fetch<MyObject>( new IdCriteria( id ) );
      result.MarkNew();

      return result;
}

This is exactly what I meant.

Herman

ajj3085 replied on Monday, April 16, 2007

Yes I know; I provided the code sample to illistrate how the OP would call an instance method from a static method, as it seemed like he wasn't sure how to go about that.

JoeFallon1 replied on Monday, April 16, 2007

Nick,

For what it is worth - I also commented out that line of code in the framework.

It was a major change in behavior from CSLA 1.x. There was a long discussion about it in an old thread and I was out voted. In the original code the developer was resposnible for calling (just like they are still responsible for calling it for child objects.)

In my fetch method I do some testing and if the data is not in the DB I decided to return a New object instead of throwing an exception. This was debated earlier on in CSLA 1.x and seemed like a legitimate choice. So my templates and BOs have used this logic for 3-4 years now. The call to MarkOld in the framework code broke this. So I commented it out and simply call it inside my templates when needed.

Joe

 

 

 

ajj3085 replied on Monday, April 16, 2007

Joe,

Do you plan to changing the 'fetch or create' method?  Shortly after that thread and mostly as a result of it, I changed how I was doing some things, such as I no longer use properties to set state on load.

It seems easier for me to keep up to date with the latest Csla changes that way (I'm the lone developer here); was just curious if you thought about changing or if the cost of upgrading Csla is still fairly low for you.

Andy

JoeFallon1 replied on Monday, April 16, 2007

Andy,

I am not planning on changing fetch at this time.

I have about 63 Root BOs and 5 other developers working on a large application with a few hundred web pages. Making the change and then re-coding and re-testing each page to ensure that the Exception is caught would be a large effort with little payback.

I am hoping to upgrade all the BOs from 1.x to 2.x as soon as we put the current version to bed. That is also another large exercise that we never seem to find time for. New features and functions always come faster than maintenance and upgrade of framework code.

I have added Base classes bewteen CSLA 2 and my BOs so that many of my previous changes are now part of my Base classes and I do not have to comment out or modify too many things in the framework. I like that Rocky has allowed us to Override many methods now.

For example he shortcircuits the IsValid call on child objects whenever he finds the first broken rule. But I need all objects checked for validity so that prior rules get unbroken and I can build a complete list of broken rules. So my BusinessListBase class has this code:

Public Overrides ReadOnly Property IsValid() As Boolean 
 
Get
   
Dim result As Boolean = True
   
For Each child As C In Me
     
If Not child.IsValid Then
       
result = False
     
End If
   
Next
   
Return result
 
End Get
End Property

Joe

Copyright (c) Marimer LLC