AutoCloneOnUpdate set to True (default) causing object references not to update properly

AutoCloneOnUpdate set to True (default) causing object references not to update properly

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


bmmathe posted on Wednesday, February 16, 2011

I have a need to update a list of objects when a parent object gets saved that is not a child list.  This might be confusing but basically the root object needs to modify and save other root objects without making the list a child because I don't want the Add to call an insert or a remove to call a dataportal delete.  There might be another design flaw here but that's not really question.

In the root DataPortal_Update I am updating a list of other objects then iterating over that list and calling Save() (in the data portal context so it's part of a transaction).

The problem is, when the root object is returned, the list that contains the other root objects has not been updated.  For example, there is an object in the list that has a property called Color.  Before the save the Color property is set to Red.  In the DataPortal_Update method, this property of the object that is in the list is changed from Red to Green then the object is saved like this:

couch.Color = Colors.Green;

couch = couch.Save();

I can see that the object is updated with the Green property properly however if I put a breakpoint at the bottom of my overridden Save() method, the object's property is still Red.  Why is this?

Thanks,

Brett Mathe

 

-------UPDATE-------

Chris Hardwick sent me a message telling me to turn off CslaAutoCloneOnUpdate which did in fact solve the issue below.  Now my question is, why?  There is no exception in the DP_Update which should cause the object graph to rollback to it's previous state.  Why are the object references on the root object properties not being updated with the changes made to the objects in the DP_Update?  Please see below for a complete example.  Currently I have CslaAutoCloneOnUpdate set to false because I need the changes to persist from the DP_XXX but now I lose other important features.

I am currently using 3.8.2 by the way.

 

-------Final Update-------

I found the root issue which was buried in a user control deep in the UI.  On a Save() call someone didn't set the result from Save() back to the reference.

Doh!  Once I set the reference equal to the result that fixed everything after the save further up the stack.  I did turn back on Auto Cloning :)

bmmathe replied on Wednesday, February 16, 2011

I even tried taking the object out of the list and making it a child of the root object.

I call save on the root, the child object updates but the properties are not set correctly when the object returns to the client!

I even put a breakpoint on the internal setter for the property and it is not being called.  It's almost like the child object is being "undone" when it returns.

I added a Guid property and set it to Guid.NewGuid() in the constructor.  Before the Save call the Guid is the exact same when it returns so it seems to be the same object reference.

chrisghardwick replied on Wednesday, February 16, 2011

Can you provide a more complete example of the problem?

bmmathe replied on Wednesday, February 16, 2011

I have a list of PaymentCouponList : BLB of PaymentCoupon : BB

I have a Receipt : BB that can apply one or more of the existing payment coupons.

There are several rules that dictate which coupons can be used and the user is responsible for making some of these decisions therefore there is a POCO called PaymentCouponManager.  The payment coupon manager fetches the list of coupons for this customer and builds a List<PaymentCoupon> of useable coupons in highest priority by interating over each of the customer's agreements to determine if the coupon is usable for that agreement.

        private void DetermineUseableCoupons()

        {

            _UseableCouponsQueue = new List<PaymentCoupon>();

 

            foreach(Agreement.Agreement agreement in _Agreements)

            {

                foreach (PaymentCoupon coupon in _Coupons)

                {

                    if (coupon.RedeemingReceiptId == 0 && agreement.CanUseCoupon(coupon))

                    {

                        PaymentCoupon newCoupon = coupon.Clone();

                        newCoupon.SetRedeemingValue(agreement);

                        _UseableCouponsQueue.Add(newCoupon);

                    }

                }

            }

The user is prompted to either redeem or ignore each coupon starting with the highest priority.  If a coupon is chosen to be redeemed a Receipt : BB is created and that coupon needs to be applied to the receipt.  This is in the UI code.

 

        private bool UseCoupons()

        {

            bool couponsUsed = false;

            PaymentCoupon coupon = null;

            while((coupon = CouponManager.GetNextCoupon()) != null)

            {

                string msg = string.Format("Do you wish to use your {0} for Agreement {1} in the amount of {2:c}?",

                                            FreeTimeTenderTypeList.GetDisplayName(coupon.FreeTimeTenderTypeId),

                                            coupon.RedeemingAgreementNumber,

                                            coupon.RedeemingAmount);

                wpfMessageBox popup = new wpfMessageBox("You have a coupon!", msg);

                popup.AddDialogButton("No", ADialogResult.No);

                popup.AddDialogButton("Yes", ADialogResult.Yes, true);

                popup.Owner = Window.GetWindow(this);

 

                if (popup.ShowDialog() == ADialogResult.Yes)

                {                   

                    AgreementPaymentViewModel agreementModel = CustomerPaymentViewModel.AgreementPaymentViewModels.Single(p => p.AgreementModel.AgreementId == coupon.RedeemingAgreementId);

                    if (agreementModel != null)

                    {

                        Receipt couponReceipt = Receipt.NewReceipt();

                        couponReceipt.AgreementPayments.Add(agreementModel.AgreementModel.GetPayment(coupon.RedeemingAmount, new List<AgreementFee>()));

                        couponReceipt.ApplyCoupon(coupon, agreementModel.AgreementModel);

                        couponReceipt.Save();                       

                    }

 

                    couponsUsed = true;

                }               

            }

 

            if (couponsUsed)

            {

                this.DataContext = IndividualCustomer.GetIndividualCustomer(CustomerPaymentViewModel.CurrentCustomer.CustomerId);

            }

            return couponsUsed;

        }

 

Here is the ApplyCoupon method on the Receipt object:

        public void ApplyCoupon(PaymentCoupon coupon, Agreement.Agreement agreement)

        {

            AppliedCoupons.Add(coupon);

            ReceiptTender newTender = ReceiptTender.NewReceiptTender(coupon);

            Tenders.Add(newTender);

 

            AgreementPayments.Single(p => p.AgreementId == agreement.AgreementId).ApplyCoupon(coupon, newTender);        

        }

 

The AppliedCoupon property is currently setup like this:

 

        private static PropertyInfo<List<PaymentCoupon>> AppliedCouponsProperty = RegisterProperty<List<PaymentCoupon>>(c => c.AppliedCoupons);

        public List<PaymentCoupon> AppliedCoupons

        {

            get

            {

                if (!(FieldManager.FieldExists(AppliedCouponsProperty)))

                {

                    List<PaymentCoupon> appliedCoupons = new List<PaymentCoupon>();

                    SetProperty<List<PaymentCoupon>>(AppliedCouponsProperty, appliedCoupons);

                }

                return GetProperty<List<PaymentCoupon>>(AppliedCouponsProperty);

            }

        }

And the DataPortal_Insert method on the Receipt does the ADO.Net call to save the receipt then does this:

            if (AppliedCoupons.Count > 0)

            {

                for (int x = 0; x < AppliedCoupons.Count; x++)

                {

                    PaymentCoupon coupon = AppliedCoupons[x];

                    coupon.PaymentCouponStatusType = PaymentCouponStatusType.Redeemed;

                    coupon.RedeemingReceiptId = ReceiptId;

                    coupon = coupon.Save();

                }

            }

 

At the end of the Save() I interrogate the coupon object I just updated and the properties I set are not reflected.

The database however does show that the RedeemingReceiptId did get updated correctly.

 

The PaymentCouponManager does not see the updated property in this method:

        public PaymentCoupon GetNextCoupon()

        {

            _CurrentIndex++;

            PaymentCoupon coupon = null;

            if (_CurrentIndex < UsableCouponsQueue.Count - 1)

            {

                coupon = UsableCouponsQueue[_CurrentIndex];

                if (coupon != null)

                {

                    // check to see if this coupon has already been redeemed

                    // if it has move to the next coupon

                    if (UsableCouponsQueue.SingleOrDefault(c => c.RedeemingReceiptId != 0 && c.PaymentCouponId == coupon.PaymentCouponId) != null)

                    {

                        coupon = GetNextCoupon();

                    }

                    else

                    {

                        // the current coupon has not been redeemed

                        // check to see if the agreement can still use this coupon, if not get next coupon

                        Agreement.Agreement agreement = _Agreements.SingleOrDefault(c => c.AgreementId == coupon.RedeemingAgreementId);

                        if (agreement != null && !agreement.CanUseCoupon(coupon))

                        {

                            coupon = GetNextCoupon();

                        }

                    }

                }

            }

 

            return coupon;

        }

Copyright (c) Marimer LLC