MarimerLLC / cslaforum

Discussion forum for CSLA .NET
https://cslanet.com
Other
31 stars 6 forks source link

Basic Loading Children Question (CSLA.NET v4.0.30319) #40

Open MarkOverstreet opened 9 years ago

MarkOverstreet commented 9 years ago

I have my first application in progress using CSLA.NET and I thought I had a good handle on the basics of loading children but I have strange bug happening where somehow the DataPortal_Update is being called even though I don't call the save method any where in my code yet.

Anyway, I think the first issue is that when I retrieve my parent and load the first child, the object goes to an IsDirty state of true. My understanding is that I need to call the DataPortal.CreateChild method to create all children and I thought I read in the eBooks that doing this would not mark my object as dirty. However here is my code for the my parent object and you can see when the line this.Claimant = DataPortal.CreateChild(); executes isdirty becomes true. So my question is how to tell the parent that I just loaded from the database and everything is in sync? I tried Markold(); but that did not seem to change the flags and besides I thought that wasn't necessary.

I have a parent object (Complaints) created during a fetch like this private void DataPortal_Fetch() { // get all the compliants methods from the database. TComplaints complaints = TComplaint.SelectAll();

        if (complaints != null)
        {
            foreach (var item in complaints)
            {

                this.Add(DataPortal.Fetch<Complaint>(item));
            }
        }

        complaints = null;  //cleanup
    }

In the Fetch of an individual Complaint object I have this:

    private void DataPortal_Fetch(TComplaint data)
    {
        using (BypassPropertyChecks)
        {
            this.DateReceived = data.DateReceived;

            // -->> AT THIS POINT IF I CHECK THE OBJECT IT IS NOT DIRTY (IsDirty = false)

            this.Claimant = DataPortal.CreateChild<Claimant>();  create child not overridden just using default

            // -->> AT THIS POINT IF I CHECK THE OBJECT IT IS DIRTY (IsDirty = true)

          }
    }
ChristopherHlusak commented 9 years ago

Hello - I'm not entirely sure I follow the collection of root objects being fetched through DataPortal_Fetch like that, but the problem with individual claimants being dirty seems like an easy enough one to answer:

When you have a root object with a child object, the root object is dirty if itself is dirty (IsSelfDirty) or if any of its child objects are dirty. When you establish a Claimant child via CreateChild, you're creating a new object - and new objects are inherently dirty... they represent data that isn't stored in the database yet by definition.

If you were to use "FetchChild", the child could load as not dirty and thus your root (Complaint) won't be dirty.

jonnybee commented 9 years ago

DataPortal.Fetch is a "root" method. To fetch children your should rather use DataPortal.FetchChild(dalobject)

and implement Child_Fetch method with that signature in the child object.

All "new" objects is considered "Dirty" when created as they are not stored in the database. This goes whether you call DataPortal.Create or DataPortal.CreateChild. A new object is per definition DIRTY.

Also - NEVER SET A VARIABLE = NULL AND THINK YOU ARE DOING CLEANUP!!!! YOU ARE ONLY MESSING WITH THE GARBAGE COLLECTOR!!!!

MarkOverstreet commented 9 years ago

Thanks to both of you. I tried FetchChild but maybe I"m doing something conceptually wrong because I got an error so let me post some of the code in question and hopefully you guys can help before I go too far down the wrong path...

Here is my root Complaints collection: public class Complaints : DynamicListBase

Here is my Complaint individual object: public class Complaint : BusinessBase

And the individual Complaint object has this method:

    public static async Task<Complaints> GetAllAsync()
    {
        // This object calls the DataPortal and  
        // uses reflection to call the DataPortal_Fetch 
        // method in the Complaints class.  
        return await DataPortal.FetchAsync<Complaints>();
    }

Calling this method above fires the Complaints DataPortal_Fetch as seen here: private void DataPortal_Fetch() { // get all the compliants methods from the database. TComplaints complaints = TComplaint.SelectAll();

        if (complaints != null)
        {
            foreach (var item in complaints)
            {
                this.Add(DataPortal.Fetch<Complaint>(item));
            }
        }
    }

As each complaint is accessed from the database collection, it calls the DataPortal_Fetch on the individual Complaint object which looks like this:

    private void DataPortal_Fetch(TComplaint data)
    {
        using (BypassPropertyChecks)
        {
            // set properties in the business obect from the supplied object from database if they are named the same
            DatabaseHelper.CopyProperties(data, this, string.Empty);

            // otherwise manually set them if they are different

            // at this point we have the Complaint record from the database but some information is in other tables
            // so we need to retrieve those here...

            TClaimant person = TClaimant.SelectOne(new TClaimantPrimaryKey(data.TClaimantKey));

            this.Claimant = DataPortal.FetchChild<Claimant>(person);
         }
     }

The line this.Claimant = DataPortal.FetchChild(person); fails with an error (ChildDataPortal.Fetch failed on the server) and here was how I tried to define it in the Claimant child...

    private void DataPortal_Fetch(TClaimant person)
    {
        this.FirstName = person.FirstName;
        this.LastName = person.LastName;
        this.SocialSecurityNumber = person.SocialSecurityNumber;
    }
ChristopherHlusak commented 9 years ago

Ah - try Child_Fetch(TClaimant person) in your child object.

MarkOverstreet commented 9 years ago

Okay thanks. This stuff is cool but confusing at times. It worked but now I am back to having an isdirty = true after I load the child and is that because I didn't wrap the property setters in a

using (BypassPropertyChecks)

MarkOverstreet commented 9 years ago

I just wrapped that code in using (BypassPropertyChecks) and yes indeed that fixed the IsDirty issue. I may just fumble through this yet!

Thanks again. I certainly appreciate the help.

jonnybee commented 9 years ago

I also recommend a video from NDC2014 with Nicole Calinoiu :Finalization and disposition in .NET: https://vimeo.com/97519508

Very good talk on IDisposable and cleanup.

JasonBock commented 9 years ago

"Also - NEVER SET A VARIABLE = NULL AND THINK YOU ARE DOING CLEANUP!!!! YOU ARE ONLY MESSING WITH THE GARBAGE COLLECTOR!!!! "

Setting an object reference to null doesn't "mess" with the GC. All it does is remove a reference to the object you were referring to. That's it. It doesn't release memory associated with that object reference, most of the time it's not necessary to do this, and when people do it, they're doing it for the wrong reasons or incorrect assumptions. But doing "myObjectReference = null;" doesn't hurt anything, other than your hands for typing code that you didn't need to do :).

jonnybee commented 9 years ago

Not quite so Jason, but some may consider it an edge case. In some cases it may make the runtime keep the object around for longer than required.

using (var someVariable = new ...)
{

    someVariable = null; 
} 

If you set the variable to null it will not be disposed by your code as one might expect.

If you did this to ConnectionManger then CSLA might keep the object around longer than it is required as it is no longer your code that determines the lifetime scope - it is the GC.

My wording might be incorrect in that it confuses the GC - but it could lead to some nasty and faulty behavior and is absolutely unnecessary.

JasonBock commented 9 years ago

Setting things to null can lead to weird, wrong, bad, etc. behavior. Most of the time I never do it - in fact if I see it in code with a local variable in a method I'll remove it - but there may be cases where someone may want to set a reference to null (e.g. a field on an object because the object referenced by the field is no longer needed). While I rarely run into that case, it's still one that is legitimate.