Generics, shared factory methods, mustinherit base classes and constructors

Generics, shared factory methods, mustinherit base classes and constructors

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


Telarian posted on Friday, September 28, 2007

Hi all. I've been using csla for a little bit now and I feel I'm getting better at it. I still have a hard time wrapping my head around "behavior centric" thinking. I'm still not sure I completely get it. It never seems quite right. I continue to read and push on in the mean time.

Anyway I have a bunch of read only lookup type classes with exactly the same fields and behaviors. Basically the only things different are the names of the objects and the sprocs used to populate them. I'm trying to move all that code into a base class because right now it's all just repeated a bunch of times with different names.

I'm trying to create  a read only base, read only collection and read only list for each of them. So here's what I've done. Be warned that this is very long because I want you to have all of the relevant code. I apologize for the length.

I don't think highlighting is going to work like I want it to so if this comes out a bit difficult to read I apologize for that as well.

I've made some base classes that work great, such as:

Namespace Base

    <Serializable()> _
    Public MustInherit Class LookupListBase(Of T As LookupListBase(Of T))
        Inherits NameValueListBase(Of Integer, String)

#Region " Declarations "

        Protected mCommandText As String

#End Region

#Region " Business Methods "

        Public Shared Function DefaultLookup() As Integer

            Dim list As T = GetList()
            If list.Count > 0 Then
                Return list.Items(0).Key

            Else
                Throw New NullReferenceException( _
                  "No lookups available; default lookup can not be returned")
            End If

        End Function

#End Region

#Region " Factory Methods "

        Private Shared mList As T

        Public Shared Function GetList() As T

            If mList Is Nothing Then
                mList = DataPortal.Fetch(Of T) _
                  (New Criteria(GetType(T)))
            End If
            Return mList

        End Function

        ''' <summary>
        ''' Clears the in-memory SampleTypeList cache
        ''' so the list of sample types is reloaded on
        ''' next request.
        ''' </summary>
        Public Shared Sub InvalidateCache()

            mList = Nothing

        End Sub

        Protected Sub New()
            ' require use of factory methods
        End Sub

#End Region

#Region " Data Access "

        Private Overloads Sub DataPortal_Fetch(ByVal criteria As Criteria)

            Me.RaiseListChangedEvents = False
            Using cn As New SqlConnection(Database.Connection)
                cn.Open()
                Using cm As SqlCommand = cn.CreateCommand
                    cm.CommandType = CommandType.StoredProcedure
                    cm.CommandText = mCommandText

                    Using dr As New SafeDataReader(cm.ExecuteReader)
                        IsReadOnly = False
                        With dr
                            While .Read()
                                Me.Add(New NameValuePair( _
                                  .GetInt32("Id"), .GetString("Name")))
                            End While
                        End With
                        IsReadOnly = True
                    End Using
                End Using
            End Using
            Me.RaiseListChangedEvents = True

        End Sub

#End Region

    End Class

End Namespace


This one seems like it's going to work just great.

Then there's this one:

Namespace Base

    ''' <summary>
    ''' This is the base class from which basic cached readonly lookup collections
    ''' of readonly objects should be derived.
    ''' </summary>
    ''' <typeparam name="T">Type of the list class.</typeparam>
    ''' <typeparam name="C">Type of child objects contained in the list.</typeparam>
    <System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")> _
    <Serializable()> _
    Public MustInherit Class LookupsBase(Of T As LookupsBase(Of T, C, L), C As LookupBase(Of C), L As LookupListBase(Of L))
        Inherits CachedReadOnlyListBase(Of T, C)

#Region " Declarations "

        Protected mCommandText As String

#End Region

#Region " Business Methods "

        ''' <summary>
        ''' Remove a record based on the
        ''' record's id value.
        ''' </summary>
        ''' <param name="id">Id value of the record to remove.</param>
        Public Overloads Sub Remove(ByVal id As Integer)

            For Each item As C In Me
                If item.Id = id Then
                    Remove(item)
                    Exit For
                End If
            Next

        End Sub

        ''' <summary>
        ''' Get a record based on its id value.
        ''' </summary>
        ''' <param name="id">Id value of the record to return.</param>
        Public Function GetTypeById(ByVal id As Integer) As C

            For Each item As C In Me
                If item.Id = id Then
                    Return item
                End If
            Next
            Return Nothing

        End Function

#End Region

#Region " Authorization Rules "

#Region " Factory Methods "

        Public Shared Function GetLookups() As T

            If Not CheckCache() Then mList = DataPortal.Fetch(Of T)(New Criteria)
            Return mList

        End Function

        Protected Sub New()

            Me.AllowNew = False

        End Sub

#End Region

#Region " Data Access "

        <Serializable()> _
        Protected Class Criteria
            ' no criteria
        End Class

        Protected Overrides Sub DataPortal_OnDataPortalInvokeComplete( _
          ByVal e As Csla.DataPortalEventArgs)

            If ApplicationContext.ExecutionLocation = _
              ApplicationContext.ExecutionLocations.Server Then
                ' this runs on the server and invalidates
                ' the LienTypeList cache
                LookupListBase(Of L).InvalidateCache()
            End If

        End Sub

        Private Overloads Sub DataPortal_Fetch(ByVal criteria As Criteria)

            Me.RaiseListChangedEvents = False
            Using cn As New SqlConnection(Database.OPASConnection)
                cn.Open()
                Using cm As SqlCommand = cn.CreateCommand
                    cm.CommandType = CommandType.StoredProcedure
                    cm.CommandText = mCommandText

                    Using dr As New SafeDataReader(cm.ExecuteReader)
                        With dr
                            While .Read()
                                Me.Add(LookupBase(Of C).GetLookup(dr))
                            End While
                        End With
                    End Using
                End Using
            End Using
            Me.RaiseListChangedEvents = True

        End Sub

#End Region

    End Class

End Namespace

This one seems like it's going to work just great as well.

But then we come to this one...

Imports System.Data.SqlClient

Namespace Base

    ''' <summary>
    ''' This is a base class from which lookup classes
    ''' can be derived.
    ''' </summary>
    ''' <remarks>
    ''' This base class only supports data retrieve, not updating or
    ''' deleting. Any lookup classes derived from this base class
    ''' should only implement readonly properties. This lookup base
    ''' class is cached.
    ''' </remarks>
    ''' <typeparam name="T">Type of the lookup class.</typeparam>
    <Serializable()> _
    Public MustInherit Class LookupBase(Of T As LookupBase(Of T))
        Inherits ReadOnlyBase(Of T)

#Region " Factory Methods "

        Protected Sub New(ByVal dr As Csla.Data.SafeDataReader)

            With dr
                mId = .GetInt32("Id")
                mName = .GetString("Name")
                mDescription = .GetString("Description")
            End With

        End Sub

        Friend Shared Function GetLookup(ByVal dr As Csla.Data.SafeDataReader) As T

            Return New LookupBase(Of T)(dr)

        End Function

#End Region

#Region " Business Methods "

        Private mId As Integer
        Private mName As String = ""
        Private mDescription As String = ""

        Public ReadOnly Property Id() As Integer
            <System.Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
            Get
                CanReadProperty(True)
                Return mId
            End Get
        End Property

        Public ReadOnly Property Name() As String
            <System.Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
            Get
                CanReadProperty(True)
                Return mName
            End Get
        End Property

        Public ReadOnly Property Description() As String
            <System.Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
            Get
                CanReadProperty(True)
                Return mDescription
            End Get
        End Property

        Protected Overrides Function GetIdValue() As Object

            Return mId

        End Function

#End Region

#Region " Authorization Rules "

#Region " Overrides "

        Public Overrides Function ToString() As String
            Return mName
        End Function

#End Region

    End Class

End Namespace

ALMOST everything seems great with this one as well but then you come to a sticking point. As you can see these classes are built almost exactly as a normal class in csla is built. Only difference for the most part is that I've used the generics for everything in order to facilitate all of this being inherited by derived classes. I've also added a protected variable in which the derived classes will store their sproc name.

The derived classes look something like this:

Namespace Admin.Type

    <Serializable()> _
    Public Class SampleTypeList
        Inherits Base.LookupListBase(Of SampleTypeList)

#Region " Constructors "

        Private Sub New()
            ' require use of factory methods
            mCommandText = "getSampleTypeList"
        End Sub

#End Region

    End Class

End Namespace

Namespace Admin.Type

    ''' <summary>
    ''' Used to maintain the list of sample types
    ''' in the system.
    ''' </summary>
    <Serializable()> _
    Public Class SampleTypes
        Inherits Base.LookupsBase(Of SampleTypes, SampleType, SampleTypeList)

#Region " Constructors "

        Private Sub New()

            MyBase.New()
            mCommandText = "getSampleTypes"

        End Sub

#End Region

    End Class

End Namespace

Imports System.Data.SqlClient, OPAS.Library.Base

Namespace Admin.Type

    <Serializable()> _
    Public Class SampleType
        Inherits LookupBase(Of SampleType)

#Region " Factory Methods "
Smile [:)]
        Friend Shared Function GetSampleType(ByVal dr As Csla.Data.SafeDataReader) As SampleType
            Return New SampleType(dr)
        End Function

        Private Sub New(ByVal dr As Csla.Data.SafeDataReader)
            MyBase.New(dr)
        End Sub

Smile [:)]
#End Region

    End Class

End Namespace

So basically my problem is this. I can't put the shared factory method for the main read only object into the derived object because I can't declare a shared method as mustoverride or overrides. If I could do that it would be easy. I also can't put it in the base object because then I can't create a new instance of the object in the factory method, which is the whole point of the method.

You can't do this in the base class:

#Region " Factory Methods "

        Protected Sub New(ByVal dr As Csla.Data.SafeDataReader)
            With dr
                mId = .GetInt32("Id")
                mName = .GetString("Name")
                mDescription = .GetString("Description")
            End With
        End Sub

        Friend Shared Function GetLookup(ByVal dr As Csla.Data.SafeDataReader) As T
Smile [:)]            Return New LookupBase(Of T)(dr)
        End Function


#End Region


and this in the derived class:

#Region " Factory Methods "

        Friend Shared Function GetSampleType(ByVal dr As Csla.Data.SafeDataReader) As SampleType
            Return New SampleType(dr)

        End Function

        Private Sub New(ByVal dr As Csla.Data.SafeDataReader)

            MyBase.New(dr)
        End Sub

#End Region


because you can't use "New" on a mustinherit class.

You can't do this on the base class:

#Region " Factory Methods "

        Protected Sub New(ByVal dr As Csla.Data.SafeDataReader)
            With dr
                mId = .GetInt32("Id")
                mName = .GetString("Name")
                mDescription = .GetString("Description")
            End With
        End Sub


Smile [:)]        Friend MustOverride Shared Function GetLookup(ByVal dr As Csla.Data.SafeDataReader) As T


#End Region


and this on the derived class:

#Region " Factory Methods "

        Friend Shared Function GetLookup(ByVal dr As Csla.Data.SafeDataReader) As SampleType
            Return New SampleType(dr)
        End Function

        Private Sub New(ByVal dr As Csla.Data.SafeDataReader)

            MyBase.New(dr)
        End Sub

#End Region


because you can't use Mustoverride or Overrides on a shared method.

I figured I should stop banging my head against the wall and ask.

The other two classes only work because the factory methods don't try to instantiate the object themselves. They let the data portal do that for them and I'm pretty sure the trick may be that the data portal uses reflection to overcome this obstacle but I'm not sure. This is my next avenue of investigation but I thought I should take the time to ask the community for advice before I go much further.

I will have to wonder if it's worth it to use reflection rather than just duplicating the code. I'm not real concerned about performance but still it seems like a dangerous road.

Thanks for your help,

Tory

Telarian replied on Friday, September 28, 2007

Ya most of the highlighting I did, doesn't show up in the finished product. It's there still when I go to edit it, but it doesn't show up in the post. The font size changes do though, so at least there's that.

Telarian replied on Friday, September 28, 2007

Well I changed my highlighting to big fonts and smiley faces and that seems to work.

jhw replied on Saturday, September 29, 2007

I went down this same road not long ago. The advice I got from Rocky was don't do it. Keep your object inheritance based only on behavior. In this day and age of code generators, snippets and templates, don't bother with inheritance for data. I followed his advice and I totally agree with him now.

Heath

Telarian replied on Monday, October 01, 2007

Ya, I've read that advice from Rocky. Since all of the behavior is the same across these objects, I'm pretty sure that's exactly what I'm doing. If you look at how simple the end result objects become I think you'll see what I mean.

That said. I really am still not sure when I'm getting those concepts and when I'm not so maybe I'm wrong.

SethSpearman replied on Monday, October 08, 2007

I was looking at this post because I was also trying to create base classes for my biz objects.  I too am just learning CSLA.

My question is this.  My interest in this lies in the fact that if I use base classes I can gen the source at will using CodeSmith.  (In fact, I plan to use active code generation so the source will be built every time I build if the template or metadata has changed).  But, of course, I don't want to overwrite the custom user code when that happens.  So I thought I could solve this problem using base classes. 

 

If base classes are not recommended then what is the best method for achieving this goal?  I thought I would prefer base classes over partial classes but I am willing to be convinced otherwise.

 

Thanks in advance for you help.

 

Seth B Spearman

DavidDilworth replied on Tuesday, October 09, 2007

@Seth:  From a code generation perspective partial classes are the way to go.   But this can be used in conjunction with abstract base classes if needed to provide common functionality/behaviour.

I had this thought the other day about inheritance which I will put up for debate/comment.

"Inheritance is essentially a technical concept, not a business concept.  It can be a very useful tool when building technical things like application frameworks.  It proves to be a lot less useful as a tool when building a specific business application."

JoeFallon1 replied on Tuesday, October 09, 2007

I have Base classes which inherit from Csla and all of my BOs then inherit from MY base classes.

I also use Codesmith to Generate the first level of a BO and then I inherit that and write manual code for the rest of the BO. So I do NOT use partial classes - I use an inheritance chain.

I have not had any issue with it in the 4 years I have been doing it.

I believe if you use partial classes you run into a different set of problems that need to be overcome. Like the inability to override a method. I think you have to provide lots of hooks in your code gen partial class in order to overcome this limitation. It has been discussed before on this forum so a Search should help. But since I don't use it I don't really recall all the details.

Joe

 

DavidDilworth replied on Wednesday, October 10, 2007

Joe,

Not sure if this is necessarily the right thread, but here goes anyway.

So what do you codegen in your "first level" BO?  I'm guessing maybe it's data related stuff taken from your DB schema (like fields, properties etc).

If that's the case, then that's v.similar to what we do, except that our CodeSmith generation produces a partial class and we then hand code the rest of the class to finish the BO (i.e. complex object graphs, additional functionality etc).

We also have our own base classes that we inherit from, not from the CSLA ones.

I'm guessing the end result might be similar, we just arrive at it a different way.

JoeFallon1 replied on Wednesday, October 10, 2007

Apologies for hijacking the thread.

David,

I code gen "everything" in the first level BO that I can get from the DB schema. Fields, Properties, Validation Rules, Factory Methods, DataPortal_XYZ methods.

When I inherit from the gen level I almost always have an empty class (initially.) Then I can add functionality as needed.

I tend to agree - we get to the same end using different techniques.

Maybe you can comment on the overriding of  a method issue for partial classes and how you resolved it with hooks?

Joe

 

 

DavidDilworth replied on Wednesday, October 10, 2007

I assume you mean overriding a method from an abstract base class to implement a piece of functionality that is common across all BOs.

I guess we've taken the view that there's kind of a black and white situation.  You either codegen the method, or you hand craft it.

So if we "know" what the implementation is going to be then it goes in the codegen partial.  If we don't then we leave it for the hand crafted partial.  But that only works up to a point.

So the next step we took was to add some extra metadata into the DB schema.  What we did is add some flags in our metadata that can be used to turn off/on certain parts of the code generation process.  For example, don't generate a property getter/setter for a column in the schema that is a foreign key column (because you can't tell if it's mapping a one-to-many or many-to-one).

This does have the obvious impact of making the templates more complex, but we solved that by writing the "metadata logic" handling code in a C# DLL and we call that from the templates.  That way the templates look less cluttered and the logic is done in a programming language where we find it easier to code the kind of relationships we are modelling.

We've had some more time to expand our BOs into an application and we understand better now the relationships we typically require.  We just need to spend a bit of time going back to our metadata concept and improve it so that we could in theory generate more of the code automatically.

Looking at it another way, we would try and move code that we repeatedly hand craft into the code-generated partial class instead.

It's trying to get the balance between investing time in improving the code generation versus knowing that we can easily cut-and-paste/snippet the bits of code we hand craft.

JoeFallon1 replied on Wednesday, October 10, 2007

Interesting.You basically decided to avoid the issue. And then built a solution that meets your needs. I get it.Bottom line for me is that it is standard inheritance works more clearly (in my mind) than partial classes where you need to worry about code generating a method and then can't override it so you either have to provide hooks or avoid the issue.

 

Telarian replied on Tuesday, October 09, 2007

I feel I should point out that I am using inheritance here in order to consolidate behavior. Yes I end up consolidating some data handling along the way but that's not really the point. These classes do everything exactly the same way.

Seth,

I see nothing wrong with subclassing the csla objects into your own base objects if you have some universal behavior you would like to add to them. It doesn't show in the code that I've posted because they are read only objects, but I have done exactly that in order to add auditing to my business objects. They all keep track of who changed them and when. This seems perfectly proper to me. It's not something that has to be done with code generation or partial classes.

Partial classes do seem like a great way to keep your generated code separate from your individual customizations. I have to admit that it seems strange to me to use inheritance for keeping generated code separate from your custom code. It seems like it would introduce a lot more problems. That said, I don't do code gen and know little about it so don't listen to me.

Telarian replied on Tuesday, October 09, 2007

By the way... Does anyone actually know the answer to my question? I've had a lot of people talking about whether or not I should be using inheritance. Not a lot of answers to the question.

Maybe I should make a more succinct thread...

SethSpearman replied on Wednesday, October 10, 2007

Joe and Tel et al,

For the record, I have decide to do what you say and continue with my plan 'A' and gen base classes and custom code the inherited classes.  There is a lot of evidence that this is very common and is a recommended approach by many code-gen experts including Kathleen Dollard and is also recommended in the CodeSmith help file.

Tel, since I feel I 'hijacked' your question, and you didn't get the answer you wanted, and after reviewing the code, I was wondering why you just don't exclude the shared factory methods altogether from the base classes and implement them in the inherited classes only.  That way you don't declare them as must override or overrides etc and it should work. 

That is what I am having to do with my CSLA bases classes per this thread and what was recommended in several code snippets I found on google.

Just a thought.  I realize that I may not understand your question completely.

Thank you all for your help.

Seth

Telarian replied on Wednesday, October 10, 2007

ah No problem. Actually you've probably brought more exposure to my question which is a good thing.

Actually the reason I don't do that is because then I can't use the one object from within the other. I would have to use the more specific derived classes which I can't do because I'm working in an abstract class.

Telarian replied on Wednesday, October 10, 2007

How very disappointing. The first time I find something that stumps me and apparently I'm not alone. I tried shadowing the Shared factory method rather than overriding it (this is allowed with a shared method where override is not). That gives the strangest error. Visual Studio doesn't really show me what it's talking about but when it goes into the debugger it does list a null reference exception and Me as being equal to nothing. That can't be good.

Telarian replied on Friday, October 12, 2007

What if I were to offer magic charms in return for some sound advice?

err... Beans! Yes beans. It must be beans. I shall offer 3 magic beans in return for help on this issue.

JoeFallon1 replied on Saturday, October 13, 2007

Here is one piece of advice:

I think the original question got lost in this thread. Try re-wording it and posting a new thread.

Joe

DavidDilworth replied on Monday, October 15, 2007

Magic charms!  Beans!  I like the sound of that! Gimme, gimme, gimme!!!

Joe makes a good point about starting a new thread - sorry we kind of hijacked this one and took it down a slightly different route.

However, FWIW, here's my thoughts on the original question.  I think you have absolutely found a crossroads point where you need to make a decision for yourself.

You either:

(1) Accept the fact you can't do what you really want to do with a shared (static) method and just accept the fact that you "know" you have to write a specific factory method in each derived class.  It's a project design decision to follow the factory pattern and you can write concrete code where needed.

(2) Go down the reflection route and write the code in the abstract base base that "knows" there will be a specific method signature that can be called dynamically.  You then write abstract code and must use reflection techniques.

You are correct in your assumption that Rocky went down route (2) as fundamentally that is how the Data Portal works. There are specific method signatures (i.e. DataPortal_xyz) that are expected in the derived class.

Copyright (c) Marimer LLC