Yet another question about class inheritance and CSLA

Yet another question about class inheritance and CSLA

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


Doug Ramirez posted on Thursday, June 21, 2007

Yes, this is indeed another question about class inheritance.  I've read a lot of the other posts that discuss this but I have yet to implement a very simple example on my own, so please bear with me.

What I am trying to accomplish is having a derived class (Child) save itself, but have its superclass (Parent) saved first.  In this example I would've expected to see the child's parent's data portal insert method called but it isn't.  I can force Parent.DataPortal_Insert() to be called by adding:

base.DataPortal_Insert();

to Child.DataPortal_Insert(), although I'm not sure if that will have an unintended consequence and isn't 'in the spirit' of CSLA.

Advice and especially code samples are welcomed.

Doug

// CSLA 2.1.4

using System;

using Csla;

 

namespace ConsoleApp

{

    class Program

    {

        public static void Main()

        {

            Child child = Child.NewChild();

            child.Save();

            Console.ReadLine();

        }

    }

 

    public class Parent : BusinessBase<Parent>

    {

        protected override void DataPortal_Insert()

        {

            Console.WriteLine("Parent.DataPortal_Insert called...");

        }

 

        protected override object GetIdValue() { return null; }

    }

 

    class Child : Parent

    {

        public static Child NewChild()

        {

            return DataPortal.Create<Child>();

        }

 

        private Child()

        {  /* Require use of factory methods */ }

 

        protected override void DataPortal_Create()

        {

            Console.WriteLine("Child.DataPortal_Create() called...");

        }

 

        protected override void DataPortal_Insert()

        {

            Console.WriteLine("Child.DataPortal_Insert() called...");

        }

    }

}

RockfordLhotka replied on Thursday, June 21, 2007

The data portal attempts to call the method on the leaf node (child) of any inheritance chain. It works up the chain toward the ultimate base if needed, but stops when it finds a method that it can call.

The process is identical to how overridden virtual methods are invoked.

So the data portal will always call the method on your actual type, not the base type, if it can.

Obviously you can call into the base type, just like you would when overriding a virtual method - there's no problem there.

Doug Ramirez replied on Thursday, June 21, 2007

Rocky, thanks for your reply.  I'm able to do what you've suggested via an explicit call into the superclass' DataPortal_Insert.  For example:

 

    class Child : Parent

    {

       

 

        protected override void DataPortal_Insert()

        {

            base.DataPortal_Insert();

            Console.WriteLine("Child.DataPortal_Insert() called...");

        }

    }

I'm still left wondering if overriding the subclass' Save method is a better approach, in the sense that the data portal isn't being 'manipulated' in a way that it wasn't intended.  I've tried to override Save and call the base's Save, but I must be doing something wrong becuase the data portal insert method on the parent isn’t being called.  For example, the following only fires the child's insert (Parent is the same as original post):

    class Child : Parent

    {

        public static Child NewChild()

        {

            return DataPortal.Create<Child>();

        }

 

        private Child() {}

 

        public override Parent Save()

        {

            return base.Save();

        }

 

        protected override void DataPortal_Create()

        {

            Console.WriteLine("Child.DataPortal_Create() called...");

        }

 

        protected override void DataPortal_Insert()

        {

            Console.WriteLine("Child.DataPortal_Insert() called...");

        }

    }

Again, advice and code snippets are appreciated tremendously.

Doug

 

JoeFallon1 replied on Thursday, June 21, 2007

Here is how I code generate my base class.

I break out the DataPortal_Insert into various Overridable methods so that when I derive a class from the Base class I can tweak the behavior by overriding only the parts I need to. If I do Override a method I can choose to perform the Base functionality first and then add more or I can skip the base functionality and just add new code. The transaction code is stuff I wrote too - you can replace it with normal ADO.Net transactions. I pass around MyUser (a custom Principal class) so that when I insert records to the DB I can get the userkey value from it and add it to the newly created record.

#Region " DataPortal_Insert "

Protected Overrides Sub DataPortal_Insert()
 
Dim tr As IDbTransaction = Nothing
 
Try
   
tr = Me.BeginTransaction()
    DoInsert(tr)
   
Me.EndTransaction(tr)
 
Catch ex As Exception
   
Me.RollbackTransaction(tr)
   
Throw
 
End Try
End Sub

Protected Overridable Sub DoInsert(ByVal tr As IDbTransaction)
 
Dim user As MyUser
  user =
DirectCast(Thread.CurrentPrincipal, MyUser)
  InsertData(tr, user)
  PostInsertData(tr, user)
  UpdateChildren(tr)
End Sub

Protected Overridable Sub InsertData(ByVal tr As IDbTransaction, ByVal user As MyUser)
  DAL.ExecuteNonQuery(tr, CommandType.Text, DAOInsert(someSQL
))

  'retrieve the newly generated Identity and update the PK in the BO so the child records will save correctly.
 
mKey = CInt(DAL.ExecuteScalar(tr, CommandType.Text, GetIdentity))
End Sub

Protected Overridable Sub PostInsertData(ByVal tr As IDbTransaction, ByVal user As MyUser)
 
'marker method that can be overridden in child class
End Sub

Protected Overridable Sub UpdateChildren(ByVal tr As IDbTransaction)
 
'marker method that can be overridden in child class
End Sub

#End Region

 

Joe

Doug Ramirez replied on Sunday, June 24, 2007

Joe:

Thanks for your reply.  That's a very clever implementation of the data access methods.  Do you do that for all of the CRUD operations?  Any particular reason you explicitly code the transaction scope (as opposed to using method attributes)?

Have you ever done anything similar with the Save, or Update, methods?  What about the factory methods for New operations?

Thanks again for you input.

Doug

RockfordLhotka replied on Sunday, June 24, 2007

Remember that the factory methods and Save() are client-side methods. You really can't affect data loading at that end, you can only do it through the DP_XYZ methods, or the methods/objects they call.

JoeFallon1 replied on Sunday, June 24, 2007

Doug,

Yes - I have similar code for the other CRUD operations. Since I code gen all of it, it is very simple to build. The hand coded classes often override things like FetchData (which is the pure dr code) so that I can call MyBase.FetchData(dr) to get the code gened properties filled and then fill any others that I happen to get loaded in the dr in my developer level class.

I also often override PostFetchData to grab other pieces of information for a BO. I had a Prefetch override for a while but never used it all so I ripped it out.

UpdateChildren and PostUpdateData often get overridden too.

I just made these all up because it made my coding easier in the long run. They aren't really "standard" CSLA constructs. But there is also nothing wrong with them and they work great for me.

Ido not use method attributes for 2 reasons.

1. I have to support SQL Server or Oracle. That is why I code to the interfaces of the ADO methods. Sometimes I get a SqlCommand and other times an OracleCommand. etc.

2. I also support older versions of SQL Server. I think 2005 is the only one that doesn't promote tranactions to distributed ones. I do not need Enterprise Services either. So I do not need the perf hit.

I have all of my Codesmith templates already set up to pass around the tr so it is easier and faster to just use it.

Joe

 

 

 

Copyright (c) Marimer LLC