Friday, November 18, 2005
« Where are all the CSLA .NET users? | Main | What I got out of Object Thinking »

I’ve posted a couple times before about what a business class will look like in the next edition of my business objects books and thus in CSLA .NET 2.0. One of those times Jason Bock (a fellow Magenicon) sent me an email asking why the DataPortal_Create/Fetch/Delete methods aren’t strongly typed.

 

If you look at the current approach and what I’ve posted thus far for the next edition you’ll see code like this:

 

Protected Overrides Sub DataPortal_Fetch(ByVal criteria As Object)

or

protected override void DataPortal_Fetch(object criteria)

 

Jason’s comment got me thinking. The DataPortal uses reflection to invoke this method, so why not be a bit more thorough in identifying the method’s parameters and invoke a strongly typed version like this:

 

Private Overloads Sub DataPortal_Fetch(ByVal criteria As Criteria)

or

void DataPortal_Fetch(Criteria criteria)

 

Where Criteria is the actual type of the criteria object provided when calling the DataPortal.Fetch() method back in the factory.

 

(the Overloads keyword is required because the base class still implements the default protected version)

 

As with many things, this sounded relatively trivial but turned out to be somewhat harder than expected. It is always the edge cases that make things hard… In this case the hard part is that parameters could be Nothing – in which case you don’t know what type they would be if they weren’t Nothing...

 

After working through that issue all is well.

 

This (in my opinion) is better than the alternative, which was to go with an interface-based approach and formalize the late-bound concept. That has its attraction, and people have altered CSLA .NET 1.0 along that line, but I really prefer strong typing if I can get it.

 

In particular this is nice for collections, where you may have multiple criteria classes for different views – now you can just implement a strongly-typed DataPortal_Fetch() method for each criteria class. Very clean and nice.

 

I’m still deliberating over whether to leave the default protected implementations in the base classes. They are nice because VS helps you override those methods, but they aren’t optimal. Given that VS has such nice snippet support it may be better to drop the protected defaults and just rely on snippets to insert the DataPortal_XYZ methods.

 

If you have an opinion, feel free to comment :)

Saturday, November 19, 2005 11:34:17 AM (Central Standard Time, UTC-06:00)
What about using ICriteria and then work with Generic factory methods?
Saturday, November 19, 2005 7:35:18 PM (Central Standard Time, UTC-06:00)
Rocky,
I pass criteria as Object today.

In the DataPortal_Fetch I then test for TypeOf Criteria and branch accordingly. Usually, I just define my datareader (and SQL) in each branch and then fill the BO accordingly.

I find this to be pretty clean.

Are you saying that if I use 4 different kinds of Criteria classes in the Shared factory methods that I will need to write 4 DataPortal_Fetch routines because the criteria will be strongly typed? I don't much care for that idea, if that is the case.

Can you give a coupe of examples of what the factory method and DP methods would look like using different criteria?

Thanks.



Joe Fallon
Sunday, November 20, 2005 11:01:17 AM (Central Standard Time, UTC-06:00)
Joe, the way it is right now you don't NEED to do anything different. That's my question - SHOULD you have to do something different? The new model allows this (notice the lack of casting/branching in the DataPortal_Fetch methods):

Public Class IdCriteria
Private mId As Integer = ""
Public ReadOnly Property Id() As Integer
Get
Return mId
End Get
End Property
Public Sub New(ByVal id As Integer)
mId = id
End Sub
End Class

Public Class NameCriteria
Private mName As String = ""
Public ReadOnly Property Name() As String
Get
Return mName
End Get
End Property
Public Sub New(ByVal name As String)
mName = name
End Sub
End Class

Public Shared Function GetPerson(ByVal id As Integer) As Person
Return DataPortal.Fetch(Of Person)(New IdCriteria(id))
End Function

Public Shared Function GetPerson(ByVal name As String) As Person
Return DataPortal.Fetch(Of Person)(New NameCriteria(name))
End Function

Private Sub DataPortal_Fetch(ByVal criteria As IdCriteria)
' ...
cm.Parameters.AddWithValue("@id", criteria.Id)
' ...
End Sub

Private Sub DataPortal_Fetch(ByVal criteria As NameCriteria)
' ...
cm.Parameters.AddWithValue("@id", criteria.Name)
' ...
End Sub


Again, today you can do the old model too, though obviously strongly-typed methods are called by preference. But if they don't exist then the protected method accepting an Object parameter is called.
Sunday, November 20, 2005 11:05:04 AM (Central Standard Time, UTC-06:00)
Michael, even with something like ICriteria (the framework already has CriteriaBase btw) you are still stuck doing if or select/switch statements to branch on the _actual_ type of the parameter, so that really doesn't solve the problem I want to solve.

Basically what I want is normal strongly typed method overloading - but in a scenario where the object on which the method is invoked is of an unknown type at design time. I want the same programming model you get with normal OOP even if you go through the DataPortal - and that's what I have achieved.

The only question is whether to leave the default loosely typed protected overridable/virtual methods in the base classes as a hint for the business developer?
Sunday, November 20, 2005 6:20:41 PM (Central Standard Time, UTC-06:00)
I like the strongly-typed solution better. Using the region (# region) you can group the criteria and the dataportal_xxx together, during debugging you don't have to jump around the class's code. And no if and case statements either; and no type checking of criteria (which will be needed if there was only one DataPortal_xxx method) which makes the code more readable. The business developer just needs to be educated about this approach.
slabanum
Monday, November 21, 2005 6:53:33 AM (Central Standard Time, UTC-06:00)
Stongly tped makes sense to me. There would be nothing to stop people working in the current manor anyway?
Tim Ensor
Monday, November 21, 2005 7:41:10 AM (Central Standard Time, UTC-06:00)
I'd also prefer strongly type criteria. But... are you really sure you want to make the sub private. There is a major performance hit for reflecting to private fields (see http://hyperthink.net/blog/CommentView,guid,14736081-2589-474a-b867-85fa1c33f4d7.aspx).

I suspect you might want to do some kind of performance testing on this before making this a general suggestion.
Ben Cline
Monday, November 21, 2005 9:36:51 AM (Central Standard Time, UTC-06:00)
Ben, the cost of reflection is well understood. This is why I use the approach I do - this is ONE reflection call and then your code can use native property or field access without reflection to do all the actual work. I've discussed this so many times I put it on the web site

http://www.lhotka.net/Articles.aspx?id=4320e7f2-dd14-4928-b552-4eb0cb82cd68

If you REALLY dislike reflection you can define and implement an IPersistableObject interface, effectively making the DataPortal_XYZ methods public and avoiding reflection. People have done this. I dislike it because it opens the door to abuse and forces the use of late binding (you have to pass parameters of some ambiguous type and then cast inside the method).
Monday, November 21, 2005 10:13:18 PM (Central Standard Time, UTC-06:00)
Rocky,
Sorry, I wasn't being clear enough. I don't have a problem with reflection. The issue is between reflecting to a public or a private routine. There is a major cost between THAT choice - not reflection where reflection is appropriate.

Reflecting to the appropriate object/criteria pair is the best way of solving the use case you approach with the csla. On the other hand I would argue making the

Private Sub DataPortal_Fetch(ByVal criteria As IdCriteria)

into

(NOTE: evil angle brackets changed to protect the comment uploader :-)
[System.ComponentModel.EditorBrowsable (ComponentModel.EditorBrowsableState.Never )] _
Public Sub DataPortal_Fetch(ByVal criteria As IdCriteria)

might be a good idea.

The idea being that what Mort doesn't see, doesn't bother him and for most programmers what doesn't show up in intellisense just doesn't exist. Purists can take the performance hit and make the procedure Private, I would take the slightly less elegant but more performant technique by making the Public procedure disappear in intellisense (using the attribute) but still hiding the routine from most users (programmers).


Ben Cline
Tuesday, November 22, 2005 5:27:48 AM (Central Standard Time, UTC-06:00)
I like the strongly typed way better.
Brett Veenstra
Tuesday, November 22, 2005 7:49:42 PM (Central Standard Time, UTC-06:00)
I see what you are saying Ben, but it is always important to step back and consider the bigger picture. Reflection in its raw form can be as much as 600% slower than direct method calls (by my testing). Yet if you embed that reflection in a web page or web service the overhead of the network, HTTP, IIS, ASP.NET and everything else is so great that the use of reflection virtually disappears.

So here are some perf tests with various DataPortals:

Local dataportal:
Private methods 54 ms
Public methods 47 ms
Difference 15%

AppDomainPortal:
Private methods 4950 ms
Public methods 4890 ms
Difference 1%

RemotingPortal:
Private methods 9301 ms
Public methods 9296 ms
Difference .01%

Local is in-proc (bypassing lots of DataPortal behaviors), AppDomainPortal is in the same process, but a different appdomain and thread, and RemotingPortal uses remoting against a web site.

Also, this test doesn't actually hit a database - it just loads 2 fields with hard-coded data. If you hit a database the difference becomes virtually unmeasurable because of the overhead of data acces... Also, the more data and/or child objects you load the less reflection matters. Remember there is ONE reflection invocation to trigger the loading of all data in your entire object graph.

So if you are not using a remote dataportal or a database, public methods could make a 15% perf difference. But odds are you are at least using a database, in which csae that overhead wipes out any use of reflection.
Thursday, November 24, 2005 7:30:13 AM (Central Standard Time, UTC-06:00)
Just a question (that may be idiotic since I am new to CSLA):

How can a remote portal call consume 9+ seconds? Especially with no db call? Network latency can't be that bad.

Dataportal calls I've tried are usually below 2 seconds for medium rowsets. I just don't understand the numbers.
John Ingres
Friday, November 25, 2005 6:03:20 PM (Central Standard Time, UTC-06:00)
Those numbers are for many calls - a few thousand if I recall correctly. The specific numbers aren't as important as the ratio. Run the same test on a faster box or across a different network and the actual numbers would vary radically - but you'd expect the ratio to remain relatively consistent.

Though if you get right down to it, even that's not certain. To REALLY understand this you'd need to profile at a lower level. Odds are the biggest cost in the appdomain/remoting scenarios is serialization - since my tests didn't actually leave my local box.

Also to be thorough you'd need to actually use a database. You'd need to test against large objects and small objects and large object graphs (lots of child objects) and small object graphs and every combination of the above - locally, remotely, with different network and server configurations.

In short, waaaay more effort than I have time for...
Sunday, November 27, 2005 11:29:11 PM (Central Standard Time, UTC-06:00)
With great interest I am reading the Eclipse Modeling Framework and their solution for the various issues related to persistence. I see definite issues to be learned with this framework that could be included in CSLA
Fritz Schenk
Tuesday, November 29, 2005 5:34:23 PM (Central Standard Time, UTC-06:00)
Steve, I had a look at your experiment reflecting against private vs. public fields. All I can say is that the 4.8 seconds (I assume seconds) vs .01 second performance difference may as well be zero if you have to do 100,000 iterations on a GHz + computer to get it.

Add in a single database or even local hard disk read (8 - 10 ms or more) to your test and the relative performance cost shrinks even further.

As Rocky said in response to your first comment, and it applies even more so to your next, this is ONE reflection call. Just one. Personally, I'll take the 0.000048 second hit on each call.
Thursday, December 08, 2005 3:41:48 PM (Central Standard Time, UTC-06:00)
How would this work with the [RunLocal] attribute? For example, if you have 5 strongly-typed DataPortal_Fetch methods, would you be able to mark each method individually? I'm assuming the GetMethod() routine in the data portal is updated accordingly in CSLA 2.0 - or has morphed into something completely different to account for generics...
Mark Peterson
Monday, December 12, 2005 12:50:58 PM (Central Standard Time, UTC-06:00)
The RunLocal attribute continues to work as today: short-circuiting the use of a remote network channel on a per-method basis. So yes, you mark each DataPortal_XYZ method to be local.

The same with transactions. You can choose between manual, enterprise services or system.transactions type transactional support on a per-method basis.
Tuesday, December 13, 2005 12:21:24 PM (Central Standard Time, UTC-06:00)
Rocky, a little bit out of topic, but you mention the option to choose between manual, enterprise services or system.transactions. Why you will want to use enterprise services for distributed
transactions now that system.transactions exists?

I personally remove the ServiceDataPortal completly from the framework.


Thanks
Mr.Underhill
Sunday, December 18, 2005 7:09:42 PM (Central Standard Time, UTC-06:00)
It is my understanding that System.Transactions won't (at least initially) support as many types of databases as COM+
Thursday, December 22, 2005 4:56:06 PM (Central Standard Time, UTC-06:00)
Just tring to see if the Strongly Typed (overloaded DataPortal_XXX) works and I get a "Ambiguous Method Match Error"... Just curious if you (Rocky) or anyone has tried it?
ward0093
Comments are closed.