CSLA and NHibernate

CSLA and NHibernate

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


Slayer posted on Tuesday, August 04, 2009

Hi,

I built a c# application using csla and nhibernate. I know this topic has been discussed before, so please bare with me.

My nhibernate files are in a totally seperate assembly called DataAccess.
In my DataAccess assembly, I have a Customer class, which contains a collection of Orders. These classes are my DTOs which maps to the DB.

In a seperate assembly, my business object assembly, I reference the DataAccess assembly. This assembly also contains a Customer class, which contains a collection of Orders. I then use my DTOs from the DataAccess assembly to populate these objects.

One of my colleagues disagrees with me. He said that nhibernate should tap directly into my business objects.

I need some advice on this. I value his opinion - and I believe that if I can get his method to work, there might be a performance gain.

Thanks

JonnyBee replied on Tuesday, August 04, 2009

Hi,

Download the Csla contrib code from Codeplex and look at the ProjectTracker.NHibernate project.

I have no experience with NHibernate - so cannot give you any more guidance. 

/jonnybee

cberthold replied on Tuesday, August 04, 2009

If I were your colleague I would definately agree on your side.  CSLA is designed in such a way that it can work both as a 2 tier or 3 tier application, it enforces business rules, authorization, and a multitude of other things.  In general your tiers should always flow in a single direction. Your UI should call upon BO to enforce the business logic and ultimately save that to your DAL.  Your BO will in a lot of cases not map out to DAL code.  For example you have an inventory database and your pulling up a list of read only items.  Those items are in categories and you want to display the category and that categories class name with the description and inventory levels of your items.  you may be joining on 5 or 6 tables to do that.  Your BO should be asking for that information and filling it in.  If you tried to go the other way you would have both your user and your data telling you what data should be.  The other issue is that the properties of the CSLA BO (if using managed backing fields (recommended)) are protected which when reading and writing properties enforces business logic allowing access to particular fields.  The only way to bypass that logic (when you are reading and writing to the database using ReadProperty and LoadProperty) is from the DataPortal side.  If you tried to write to a field that was being enforced by a data rule and you were creating 10000 objects you would also suffer from all of those business rules firing over and over.  In some cases you might have an ExistsCommand which makes another database call to see that the description doesn't exist or something along those lines.  Those would call other database commands and it would go on and on.  Another issue you would face is who is going to call the DAL?  If the UI calls the DAL then you've defeated the purpose of the BO as you can no longer insulate the UI from calling the DAL directly.  If you have BO objects that call the DAL and return other BO objects you can't enforce all the business rules have been enforced properly and end up with a lot more objects and code.  It all ends up being BAD BAD BAD.  From CSLA standpoint you only stand to lose performance and fight circular references ultimately creating unmaintainable code.

tmg4340 replied on Wednesday, August 05, 2009

One other potential issue with directly connecting CSLA to NHibernate is that the "traditional" pattern in CSLA is to have private constructors.  IIRC, NHibernate requires a public default constructor to do its job.

The private-constructor concept is certainly not required - Rocky chose that route so that UI coders would be forced to use the factory methods.  And I think that in SL implementations CSLA BO's have public constructors because of reflection limitations in SL.  So there's certainly nothing stopping you from creating the necessary constructor.  But I do agree that can open a "hole" in your object model, though a small one.

Long-term, I think you will be much happier de-coupling your CSLA and NHibernate implementations.  Sure, it's an extra step, so there is a performance hit.  But I wouldn't worry too much about that unless you already are seeing performance problems.  If you directly tie NHibernate to your CSLA BO's, it's going to make managing change between the DB and your BO's harder.

HTH

- Scott

RockfordLhotka replied on Wednesday, August 05, 2009

I agree with what's been said here.

However, I should point out that in 3.6 and higher the ObjectFactory model does allow you to have the DAL (NHibernate) create and populate the business object graph directly. So if you do want to follow your colleague's advice you can go down that route.

You should be aware however, that no technology that loads property values will really work. Your technology needs to load field values.

This is because CSLA objects aren't dumb data containers, they are smart business objects. And that means that authorization, validation and business rules are triggered when properties are accessed - even by the DAL. The way to avoid this is to load fields, not properties. If NHibernate can load fields, then you should be set, otherwise it won't work well.

Finally, if you want to support Silverlight, you need to use managed backing fields (or write a bunch of serialization code). In that case you can't load fields either, but instead you must call the LoadProperty() method, and I very much doubt HNibernate can handle that scenario...

Kevin Fairclough replied on Wednesday, August 05, 2009

We are planning to map our CSLA objects directly to NHibernate, and use BypassPropertyChecks(obj) in the factory before the NHibernate repository fills the object.  NHibernate being mapped to the managed properties.  I was under the impression at the time BypassPropertyChecks allowed you to use the property setter.

This was working I think (although I haven't looked in a while)  The problem with collections remains where you need to copy into CSLA collection.

RockfordLhotka replied on Wednesday, August 05, 2009

Yes, ByPassPropertyChecks can help – but if your DAL technology creates the individual objects then it isn’t really a solution.

 

Kevin Fairclough replied on Thursday, August 06, 2009

We have this in our factory (Fetch):

public virtual T Fetch(Csla.SingleCriteria<T,S> criteria)
        {
            if (criteria != null)
            {
                var obj = (T)Activator.CreateInstance(typeof(T), true);
              
                using (BypassPropertyChecks(obj))
                using (Repository)
                {                   
                    obj = Repository.Get(criteria.Value);
                }

                MarkOld(obj);
                return obj;
            }
           
            return Fetch();
        }

Do you see any problem with this?

Repository.Get(...) uses an NHibernate Session to get the object so I guess it will create it also.

public virtual T Get(object id)
        {
            return Session.Get<T>(id);           
        }

I'm now doubting if this works correctly as I haven't began to look at the object state, i.e. rules broken etc.

RockfordLhotka replied on Thursday, August 06, 2009

Yes, you create obj and use BypassPropertyChecks, but then it looks like the Get() method creates a new obj, which wouldn’t be bypassing property checks, because it is a new object.

Kevin Fairclough replied on Thursday, August 06, 2009

Thanks Rocky,  NHibernate has a Load method which I think will do the trick here as it takes an "empty" object instance in according to the docs.

public virtual T Fetch(Csla.SingleCriteria<T,S> criteria)
        {
            if (criteria != null)
            {
                var obj = (T)Activator.CreateInstance(typeof(T), true);
              
                using (BypassPropertyChecks(obj))
                using (Repository)
                {                   
                    Repository.Load(obj, criteria.Value);
                }

                MarkOld(obj);
                return obj;
            }
           
            return Fetch();
        }

public virtual bool Load(object obj, object id)
        {
            return Session.Load<T>(obj,id);           
        }

Cheers
Kevin

Copyright (c) Marimer LLC