Csla.ObjectFactory III – What’s wrong with UpdateChildren()

Derick Bailey’s post about Csla vs DDD brought out a simple but very interesting question :

Which one is correct?
Person.AddCertificate(), or Person.Certificates.Add()?

Almost the same? But it turns out the 2nd one is NOT “correct”, at least, not in DDD way.

That’s the problem in CSLA. Always thinking in table, data-centric design/dev. An extreme example, FieldManager.UpdateChildren().

How about save? Person.UpdateEmail() or Person.Email.Update(). [Email.Child_Update() in classic CSLA way.]

CSLA is not an ORM tool/framework, but BO carries all the dirty flags, in fact it’s DataPortal doing all the relationship maintenance work. RootBO.Save() is handling all the updates of its children, through FieldManager.UpdateChildren() jumping into each childBO’s internal private Child_XYZ methods. Handy, “clean”, but it’s not “correct”.This is the exactly same problem when I was trying to switch to the new Csla.ObjectFactory way: I had to keep an extra set of Protal methods in child BO, those child portal method,  to work with Parent BO’s FieldManager.UpdateChildren() method.

What if we ignore FieldManager completely? Just let Person.Save() directly call DataPortal.Update<EmailBO>(person.Email) ? Or DataPortal.Update<TelephoneBOCollection>(person.Telephones) ? Then child BO can still use  the general AbstractFactory which only have root level CRUD methods.

The direction Rocky made in ObjectFactory looks right, child CRUD should be done by root BO.  The thing is, it’s not backwards completable. It’s trying to retire FieldManager.UpdateChildren()!

The update route should be RootBO.Save -> Root Factory.Update -> Child Factory.Update, don’t call child BO’s internal Child_XYZ anymore.

Currently for legacy reason I still keep the following code in my ChildBO to bump calls from parent to child’s own factory, those UpdateChildren() in root BO can be alive for awhile, until I move them to root factory, which should be the newly strongly-typed customized UpdateChildren().


private void Child_DeleteSelf(){ DataPortal.Delete(this);}

private void Child_Insert(IParent parent){
     this.ParentId = parent.Id;
     DataPortal.Update(this);
}

private void Child_Update(){
     var newBO = DataPortal.Update(this);
     DataMapper.Map(newBO, this);
}

It might just looks like a wrapper trick, but those DataPortal calls are actually in Factory class now which indeed is in an external true service layer.

In my app, it’s one AbstractFactory for all BOs, both root and child.

I do have a problem now, where should I put transaction scope attribute? It shouldn’t be a big deal, possible solutions:

  1. Create some extra methods in factory not in transaction Scope dedicated for child methods,
  2. Unit of Work pattern?

Look back Ryan’s ProjectTrackerHybrid code, all the child propertis are not registered! Which means they are not in fieldmanager at all. It’s ORM’s job to handle the realationship UpSet (Update/Insert).

About these ads

Csla.ObjectFactory Part II – Difficulties on Child BO

I was wondering  why CslaFactory only needs to implement CRUD, no child methods at all. Other developers post the  same question on forum.

Rocky’s answer made sense, it your own Facotory’s responsibility to handle all those relationship. My specific problem on this is, FiledManager.UpdateChildren() is not available any more.

First, the FieldManager property in BO is protected, this can be fixed by adding new method in our BaseBO to make it public. The root BO changed to CslaFactory then. But, next challenge is, this UpdateChild is not going through DataPortalSelector->DataPortal_XYZ route, it still uses Reflection to look up the ‘Child_XYZ” method right in your child BO, not in CslaFactory class at all!

You have to manage your own update route by looping through all the children from your root BO! Forget the powerful FieldManager.UpdateChildren().

Relationship is built in DAL. ?? When start using DTO, I know it is the dead weakness. The word around is pulling relationship up to Csla BO and rely on those magic UpdateChildren methods. Now, updateChildren is gone, I have to create a real business explicit UpdateChildren.


    public class AbstractObjectFactory<BO, TRepository, DTO> : ObjectFactory, IBusinessBaseServerFactory<BO>
        where BO : MyBusinessBase<BO>
        where TRepository : IRepository<DTO>
        where DTO : class, new()
    {
        protected IRepository<DTO> _repository = StructureMap.ObjectFactory.GetInstance<TRepository>();

        public virtual BO NewBO()
        {
            return (BO)Activator.CreateInstance(typeof(BO), true);
            //            return new BO();
        }

        public virtual void InsertChildren(BO bo){}
        public virtual void UpdateChildren(BO bo){}
        public virtual void DeleteChildren(BO bo){}

        public virtual BO Create()
        {
            var obj = NewBO();
            MarkNew(obj);
            obj.GetValidationRules().CheckRules();
            return obj;
        }

        public virtual BO Insert(BO bo)
        {
            var data = _repository.Insert(bo.BuildDTO<DTO>());
            if (data == null) throw new DataException("Repository Insert return null.");
            bo.LoadFromDTO<DTO>(data);

            InsertChildren(bo);
            // FieldManagar().UpdateChildren(bo) is not through CslaFactory.
//            bo.GetFieldManagar().UpdateChildren(bo);

            return bo;
        }

...

    public class StakeholderFactory : AbstractObjectFactory<StakeholderBO, IStakeholderRepository, StakeholderDTO>
        , IStakeholderFactory
    {
        public override void InsertChildren(StakeholderBO bo)
        {
            // Update Alias Name Child
            var factory = new StakeholderAliasNameFactory();
            for (int i = 0; i < bo.AliasNames.Count; i++)
            {
                bo.AliasNames[i] = factory.Insert(bo.AliasNames[i]);
            }
        }

This looks weird. And my solution of mocking Parent for testing update Child is useless then, because that assume root BO is calling FiledManager.UpdateChildren. One solution is to  re-write those update children code into my CslaFactory class. More coding, does it look more clear? I’m not sure. One of my root BO has 7+ children, I don’t think it’s worth to move.

Another quick dirty fix might be, still leave those Child_XYZ methods in Child BO for legacy purpose, inside put DataPortal_XYZ to redirect call to CslaFactory. This solution can keep parent update code same as before, still using UpdateChildren(), also the Child BO will look more like a switchable BO.

Enough, now I fully understand Ryan’s statement: Csla is great, but we need relationship.

Look how Ruby and NH dealing with relationship: has_many, belongs_to. Simple and clear.

OrcaScript setting version number into exe

From Sybase’s manual, OrcaScript can set version info into exe,


set exeinfo property <fileversion | fileversionnum | productversion |    productversionnum> versionString
set exeinfo property <companyname | productname | copyright | description>    propertyString

Here is a post talking about how to read this exe info from PB through API . To use this code in PB 9 or later, you should change it to ansi or GetFileVersionInfoSizeW() andGetFileVersionInfoW() API functions.

Because OrcaScript should be able to pass variable through ‘/D’ switch, but my experience is that there are some keywords you should avoid, like ‘version’ (undocumented)

My ant task to pass args.

<project name="build" default="build"
       basedir="../">

    <target name="build" depends="delete_last_build">
        <exec executable="orcascr100.exe" failonerror="true" >
            <arg line='/D version="1.2.3.4" ${basedir}/build/build.orca' />
         </exec>        

        <antcall target="deploy" />
    </target>

    <target name="delete_last_build">
        <delete>
            <fileset dir="${basedir}/Executable" includes="*.exe"  />
            <fileset dir="${basedir}/Executable" includes="*.pbd"  />
        </delete>
    </target>

    <target name="deploy">
        <move todir="${basedir}/Executable">
            <fileset dir="${basedir}/" includes="*.exe"  />
            <fileset dir="${basedir}/" includes="*.pbd"  />
        </move>
    </target>

 </project>

My orca:

start session

set lib_list = “C:\workspaces\pbunit\pb102\pbunit.pbl;”
set lib_list += “C:\workspaces\pbunit\pb102\pbunitfunc.pbl;”
set lib_list += “C:\workspaces\pbunit\pb102\pbunitui.pbl;”

set main_pbl = “C:\workspaces\pbunit\pb102\pbunitui.pbl”

;**********************************************************************
; Set up the Application
;**********************************************************************
set liblist lib_list
set application main_pbl “pbunit”

build application FULL

;**********************************************************************
; Now build the exe and PBD
;**********************************************************************

set exeinfo property companyname “Frank Mao”
; The variable name should avoid keywords, like “version”
set exeinfo property fileversion version_num
build executable “pbunit.exe” “pbunit.ico” “pbunit.pbr” “yyy”

build Library “pbunit.pbl” “” PBD
build Library “pbunitfunc.pbl” “” PBD
build Library “pbunitui.pbl” “” PBD

end session

The challenge for me is, I have to pass both fileversion and fileversionnum to orca, one is string, the other one is 4 integers. If I set version number is this way,

set exeinfo property fileversion  “7.1.0.41″
set exeinfo property fileversionnum  “7,1,0,42″
set exeinfo property productversionnum  “7,1,0,43″
set exeinfo property productversion  “7.1.0.44″

I got

pbunit_version1

Notice the one with coma being converted to dots. fileversionnum really means 4 parts of fileversionnum, must be in the format of “1,2,3,4″.

Or I can just pass the version in coma way, the File version shown on the top is in dot format, while the one in “Other version information” will still in coma way. Who cares? And how many people can tell the difference?

Strange. File version and File version number are totally different concept. They can be different.

Bruce Armstrong’s 5 years old article is still very usesful for orca users.

Manage version number through CC.Net

Learned this from Jeremy Miller’s StroyTeller, he really is a god-father in DotNet Community.

First, create a commonAssembly.cs cotains AssemblyVersionAttribute, copyrighte, company information and put it in solution folder.

Second, add this file as a ‘link’ to each project, remove or comment out the old AssemblyInfo.cs. At least I comment out the AssemblyFileVersion attribute, so it automatically take the version from AssemblyVersion.

Third, inside your cruise.build, creata a version task by dynamically get CCNetLable from cruisecontrol then generate the correct CommonAssemblyInfo.cs.

Build. You get the consistent version number in both source-code repository and exe/dll!

I got the famous “Assembly XX.dll must be strong signed in order to be marked as a prerequisite” problem after this change. The easy solution is, manually clean up bin and obj folder. Thanks to this post. I should have try this first.

To get exe version info in CSharp

Assembly.GetExecutingAssembly().GetName().Version.ToString()

Or:

Assembly.GetEntryAssembly().GetName().Version.ToString()

Orcascript support setting version as well, I will post how-to later. Maybe with EAServer deploy tricks.

Csla.ObjectFactory

This is  a new feature from Csla 3.6, which is trying to make Csla more TDD friendly.

Benefits:

DataPortal_XYZ methods can be moved into your own ObjectFactory class, BO will look a little bit thinner. Yes, you can now make BO contructor to be public, ( in standard csla way BO’s ctor should be private), if you are tired of using Activator.CreateInstance.

Because now you have full control to this ObjectFactory, and this class is not static, you can mock it when unit-testing UI/Presenter. But, the default usage of ObjectFactory doesn’t support Interface. Fortunately,  Rocky allow you to create your own FactoryLoader, by setting it into your app.conifg to redirect:

<appSettings>
<add key=”CslaObjectFactoryLoader” value=”YourNameSpace.YourFactoryLoader, YourAssembly” />

Ryan Kelley is using this tech to create his GenericFactoryLoader in his ProjectTrackerHybrid demo.

We have lots of Stored Procedures to deal with, so create a base AbstactObjectFactory is very hard. So I changed Ryan’s GenericFactoryLoader to make it not taking generic interface.

Here is what I tried:


        public virtual Type GetFactoryType(string factoryConnectionString)
        {
            if (factoryConnectionString == null)
                throw new ArgumentNullException("factoryConnectionString");

            parser.Parse(factoryConnectionString);

            var factoryTypeName = parser.FactoryType;

            // parser doesn't work , use csla default ObjectFactoryLoader
            if (string.IsNullOrEmpty(factoryTypeName))
                return new ObjectFactoryLoader().GetFactoryType(factoryConnectionString);

            return Type.GetType(factoryTypeName);
        }

        public virtual object GetFactory(string factoryName)
        {
            var factoryType = GetFactoryType(factoryName);
            if (factoryType.IsInterface)
                return StructureMap.ObjectFactory.GetInstance(factoryType);

            return new ObjectFactoryLoader().GetFactory(factoryName);

        }

[CslaFactory("Factory Type=MyFactory.ICityFactory")]
// Classic way to declare ObjectFactory
//    [CslaFactory("MyFactory.CityFactory, MyFactory")]
[Serializable()]
public class CityBO :BusinessBase<CityBO>
{...}

Struggling with WinForm menusty

Hide menustrip in child form.

From MSDN How to: Set Up Automatic Menu Merging for MDI Applications

Step 5, Set the Visible property of the target MenuStrip to false.

Otherwise there will be a gap left by this empty menuStrip.

Merge index can’t jump

MSDN How to: Insert a MenuStrip into an MDI Drop-Down Menu (Windows Forms) (MatchOnly for menu (root), and Insert for menu items.)

See the example, no gap between each menu items merge index. No Gap! There is different than PB and VB.

No menustrip inheritance?

MS forces you to use merge menus between mdi parent and child.

Linq Left join

Scenario: Search all Stakeholder regardless if they have a current effective address. Thanks to this post.


var qry = from data in dbx.Stakeholder_UVs
           join addressData in dbx.Stakeholder_Address_UVs
               on data.Stakeholder_ID equals addressData.Stakeholder_ID into tempAddresses
               from addressData in tempAddresses
                .Where(x => x.Effective_Date < DateTime.Today)
                .Where(x => x.Expired_Date > DateTime.Today)
                .Where(x => x.Address_Role  == "Mailing")
                .DefaultIfEmpty() ;

Or filter in table instead of temp data.


var qry = from data in dbx.Stakeholder_UVs
           join addressData in dbx.Stakeholder_Address_UVs
                .Where(x => x.Effective_Date < DateTime.Today)
                .Where(x => x.Expired_Date > DateTime.Today)
                .Where(x => x.Address_Role  == "Mailing")
               on data.Stakeholder_ID equals addressData.Stakeholder_ID into tempAddresses
               from addressData in tempAddresses.DefaultIfEmpty() ;

But, what if more than one addresses appear? Here I added a filter to get the Max addressId back, which should be the recently added:

 
            var qry =  from data in dbx.Stakeholder_UVs
                       join addressData in dbx.Stakeholder_Address_UVs
                            .Where(x => x.Effective_Date < DateTime.Today)
                            .Where(x => x.Expired_Date > DateTime.Today)
                            .Where(x => x.Address_ID == 
                                (dbx.Stakeholder_Address_UVs)
                                .Where(a=>a.Stakeholder_ID == x.Stakeholder_ID)
                                .Max(a=> a.Address_ID))
                           on data.Stakeholder_ID equals addressData.Stakeholder_ID into tempAddresses
                           from ad in tempAddresses