Bidirectional associations - StackOverflow

Bidirectional associations - StackOverflow

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


Kevin Fairclough posted on Tuesday, January 20, 2009

Hi All,

How should I write a bidirectional relationship in CSLA? 

For the child I need to reference the correct parent not the collection.  I have Sample which has a collection of SampleResults of type SampleResult.  The SampleResult has a Sample which is the true parent (needed for true parent child in NHibernate).

I can fetch into the structure using NHibernate without a problem, but I get StackOverflow's when I try to MarkOld() in the ObjectFactory.  Is there a way to mark the Sample property on the SampleResult so that it doesn't get stuck in a circular ref? or how should I do this?

TIA
Kevin


Kevin Fairclough replied on Tuesday, January 20, 2009

I should mention I am using CSLA 3.6.0.0 with managed fields.

It gets stuck in IsDirty of FieldData

RockfordLhotka replied on Tuesday, January 20, 2009

Unfortunately this is a known issue. It is actually a broader issue, and I hadn't considered the MarkOld() aspect of it... The core issue is that there's no current way to mark a managed child reference as NotUndoable or NonSerialized.

http://www.lhotka.net/cslabugs/edit_bug.aspx?id=30

However, I don't necessarily consider this a bug (seriously). The reason is because Sample can not be a child of SampleResult, because SampleResult is a child of Sample. You can't be both father and son (or grandson) unless you are in a "Back to the Future" movie, and even then the idea is wierd :)

A child object can maintain a back reference to a parent, but that back reference is not a "child reference", it is just a reference.

Managed child references are "child references", not arbitrary references. In other words, when you put an object reference into a managed backing field, it is always a child reference. A parent reference can't be a child reference, and so you can't put a parent reference into a managed backing field.

Which is why the issue I listed earlier is an 'enhancement', not a 'bug'. At some point I might enhance the framework to allow managed backing fields to store aribtrary references - and of course you'll have to indicate the nature of that reference (containment/child, containment/aggregation, using, parent, etc).

In any case, the answer to your immediate problem is to not use a managed backing field, because this isn't a child reference. Instead, you'll need to use a traditional property syntax, with a private backing field that is marked as NonSerialized and NotUndoable.

vdhant replied on Tuesday, January 20, 2009

If this is the can (given that the link to the parent object is marked as NonSerialized) how would you recommend re-building the think to the parent object on the deSerialize? Just look at CSLA's parent property twice??

RockfordLhotka replied on Tuesday, January 20, 2009

Yes, it is no different than the pre-3.5 model, where this reference must be fixed up on deserialization.

 

Rocky

 

 

From: vdhant [mailto:cslanet@lhotka.net]
Sent: Tuesday, January 20, 2009 3:05 PM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] Bidirectional associations - StackOverflow

 

If this is the can (given that the link to the parent object is marked as NonSerialized) how would you recommend re-building the think to the parent object on the deSerialize? Just look at CSLA's parent property twice??


Kevin Fairclough replied on Wednesday, January 21, 2009

Thanks for the help.  One question, in order to set the reference back on the DeSerialize event,  I needed to expose the Parent on the collection (because of the protection level) , is this right?

SampleResults class

new internal Sample Parent
        {
            get { return (Sample)base.Parent; }
        }

SampleResult class

protected override void OnDeserialized(System.Runtime.Serialization.StreamingContext context)
        {
            Sample = ((SampleResults)Parent).Parent;           
            base.OnDeserialized(context);
        }

Thanks
Kevin


RockfordLhotka replied on Wednesday, January 21, 2009

Yes, that is one way to get the parent value, and should work.

 

Rocky

 

From: Kevin Fairclough [mailto:cslanet@lhotka.net]
Sent: Wednesday, January 21, 2009 9:12 AM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] RE: Bidirectional associations - StackOverflow

 

Thanks for the help.  One question, in order to set the reference back on the DeSerialize event,  I needed to expose the Parent on the collection (because of the protection level) , is this right?

SampleResults class

new internal Sample Parent
        {
            get { return (Sample)base.Parent; }
        }

SampleResult class

protected override void OnDeserialized(System.Runtime.Serialization.StreamingContext context)
        {
            Sample = ((SampleResults)Parent).Parent;           
            base.OnDeserialized(context);
        }

Thanks
Ke vin




Kevin Fairclough replied on Tuesday, July 06, 2010

Hi

I am using this technique to obtain Parent references.  This is working using a local dataportal but when I use WCFPortal the Parent property is null and this fails with a NullReferenceException.

Any ideas how to resolve this?

Regards

Kevin

CSLA 3.8.0.0

RockfordLhotka replied on Tuesday, July 06, 2010

The OnDeserialized method(s) are called when the serializer tells each object that it has been deserialized. That's out of our control - it is something the .NET framework's deserializers are doing.

This is one area of slight behavior difference between the BinaryFormatter and the NetDataContractSerializer - they don't appear to follow quite the same timing about when the call OnDeserialized.

It is wise, when using OnDeserialized, to assume the order is indeterminate - which is to say that you should never count on other objects being deserialized just because one specific object is deserialized. Or to put it another way - when the serializer calls OnDeserialized all it says is that your one object is deserialized, not the entire object graph.

In CSLA 4 I have (with mixed feelings) made the Parent property public on all the base classes, so it should be possible to walk from a leaf child node up through the object graph to the root.

But you are just re-exposing the existing protected Parent value right? That should be available once deserialization of the entire object graph is complete.

Kevin Fairclough replied on Tuesday, July 06, 2010

Thanks for the quick reply.

Unfortunately I'm having to store a reference to the "real" parent in the child, therefore I need to update that reference somewhere.   I was doing this in the OnSerialized override,  this is because NHibernate requires bidirectional to manage FK inserts.

Regards

Kevin

 

 

 

 

RockfordLhotka replied on Tuesday, July 06, 2010

Ahh, I see. You might avoid storing that reference though, and make the property algorithmic - just walk up the graph to find the "real" parent when requested. That's just a simple graph traversal - though it does presuppose that the CSLA Parent properties are accessible (which they aren't until CSLA 4).

But you can always make the Parent property accessible by adding a public property or method in your custom base classes - thus making it easy to walk the properties as needed.

Kevin Fairclough replied on Wednesday, July 07, 2010

Will walk for the getter, but NHibernate requires something to set so I have to do this:

 #region BiDirectional Item property required for not null foreign keys
        [NonSerialized]
        [NotUndoable]
        private Item _item;
       
        public Item Item
        {
            get
            {
                return (Item) ((ChildList) Parent).Parent;
            }
        }
#endregion

Thanks Rocky

 

ajj3085 replied on Wednesday, July 07, 2010

We are running into similar problems at work, but not using Csla, we have more POCO business objects with simplier validation behavior (basically the same thing MVC does in the model binder calling ValidatoinAttributes)..  It sounds like you're doing the same thing we are; putting the repository at the Business layer level, and having the UI talk directly to it to hydrate / dehydrate the BOs.  Something like this in an MVC controller perhaps:  var repo = GetRepo(); repo.Save(myBO) (or in your case, repo.Save(myCslaBO).

Given the problems we're htting, I'm not sure that's the way to go, and I find I'm again hitting problems which I have considered long solved by following the standard Csla pattern (where the BO talks to the DAL in the DP_xyz methods).  In other words, the issues are database design impacting the BOs and all the fragileness that entails.

For my own work, I am planning on implementing a repository pattern, but having the BO talk to the repoistory so that I can have my unit tests running without hitting the database, a problem I currently have and do need to solve.  I'm hoping this will give me the best of both words; full Csla goodness, while having unit tests injecting mocks to verify the BO talks to the repo correctly, without actually hitting the database.

Kevin Fairclough replied on Thursday, July 08, 2010

Hi Andy,

Our objects are loaded and saved via the ObjectFactory, so we do follow the standard Csla method.  Our objects are also saved via a repository, so I guess its similar to what you are doing but we have the extra hop over the DataPortal.  The ObjectFactory contains a Repository which does the data access work.

Our work has a based on both ProjectTrackerNHibernate in CSLA Contrib  and this.

Regards

Kevin

ajj3085 replied on Thursday, July 08, 2010

Looking at the NHibernate PTracker, that looks somewhat like what we are doing, passing Nhibernates ISession.Save the actual BO... except that instead of the BO doing that call our repository is. 

So I am wondering what your opinion is; to me it seems our design has too much of NHibernate leaking into the domain models, and trying to have Nhibernate persist the actual BO (vs. creating DTOs that exactly match the DB) is causing us more trouble than its worth.  Of course, NHibernate is still fairly new to me, so perhaps I'm not doing it right either.

Would you mind sharing your thoughts on this?  Has directly mapping the BO caused any issues for you, or is it working rather well?

Oh... and mapping a ReadOnly and BusinessObject.. you just tell NHibernate the same table name, and it should work, correct?

Thanks for any feedback!

Andy

Kevin Fairclough replied on Friday, July 09, 2010

It is a bit of a nightmare and it's our first project in .NET too so I'm not an expert.  We used the ObjectFactory to move most of NHibernate out of the BO, however we still need to collection/bag/set reference in there.  Read only properties need private/protected setters.

You have to be careful with lazy attibute and also loading collections is a pain.

You also have to make sure NHibernate doesn't cascade changes by using cascade="none" or leaving off the cascade attribute.  This means you have to use Csla's object state to determine what to send as changes to NHIbernate in order to persist correctly.  In our ObjectFactory.Update which is always the Root object we have a method to cascade through the graph telling Nhibernate to SaveOrUpdate or Delete the particular object.  We use the FieldManager to help with this.  We had to do this because NHibernate hasn't got the dirty state,  it can get it but that results in extra selects server side to determine if objects need updating.

RO objects are mapped again this hasn't caused any issues.

Were still learning this as we go and I'm not sure that we are doing it right.  We never really tried to DTO way, with a good mapper it might work better.

Kevin

 

 

 

 

ajj3085 replied on Friday, July 09, 2010

Well, at least your not alone in this... although at least you are able to use Csla, I think things would be less painful if we were.  But your feedback so far pretty much matches up with my experience.  Up until now I've always used DTOs in the DP_xyz methods, I have a feeling that would work out better than mapping BOs.

Copyright (c) Marimer LLC