Problem with IsDirty

Problem with IsDirty

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


k2so posted on Thursday, June 19, 2008

Just changed from 3.0 -> 3.5, and the following problem arises.

After an update to a property of a child BO, MarkOld is invoked; IsDirty changes to false
Then I do another update immediately after, the IsDirty is back to true, but it should be false,
anyone knows the reason to this, and how to solve this problem?

RockfordLhotka replied on Monday, June 23, 2008

Can you be more specific?

You have UI code that does:

obj.Name = "xyz";
// here obj.IsDirty is false????
obj.Name = "abc";
// here obj.IsDirty is true (which you'd expect)?

Or do I misunderstand what you are saying?

k2so replied on Tuesday, June 24, 2008

The classes are implemented in a hybrid way at the moment, and I am not sure if this is causing the problem, but I will explain below using an insert case.

Here are the BO's:

Deal is a BusinessBase parent of Conditions
Conditions is a BusinessListBase of Condition
Condition is a BusinessBase

Condition's methods are implemented in the 3.5 way, utilizing the PropertyInfo, here's one example
        private static PropertyInfo<string> Param1Property =
            RegisterProperty<string>(typeof(Condition), new PropertyInfo<string>("Param1", "Parameter 1"));
        private string _param1 = ConditionContentProperty.DefaultValue;
        public string Param1
        {
            get
            {
                return GetProperty<string>(Param1Property, _param1);
            }
            set
            {
                SetProperty<string>(Param1Property, ref _param1, value);
            }
        }

But the Data Access is remained the 2.0 way, calling a stored proc on the sql server, for example
        internal void Insert(Deal deal)
        {
            if (!this.IsDirty) return;
            using (SqlConnection cn = new SqlConnection(Common.Connection.Database.ApplicationConfiguration))
            {
                cn.Open();
                using (SqlCommand cm = cn.CreateCommand())
                {
                    cm.CommandText = "addCondition";
                    cm.Parameters.AddWithValue("@dealNumb", deal.DealNumb);
                    _timestamp = DoInsertUpdate(cm);
                    MarkOld();
                }  // # 2
            }
        }

        private byte[] DoInsertUpdate(SqlCommand cm)
        {
            cm.Parameters.AddWithValue("@param1", _param1);
            cm.Parameters.Add("@newLastChanged", SqlDbType.Timestamp);
            cm.Parameters["@newLastChanged"].Direction = ParameterDirection.Output;
            cm.ExecuteNonQuery();
            return (byte[])cm.Parameters["@newLastChanged"].Value;
        }

Conditions basically just calls the insert and update in Condition as follow
        internal void Update(Deal deal)
        {
            this.RaiseListChangedEvents = false;
            foreach (Condition cond in DeletedList)
            {
                cond.DeleteSelf(deal);
            }
            DeletedList.Clear();

            foreach (Condition cond in this)
            {
                if (cond.IsNew) // # 1
                {
                    cond.Insert(deal);
                }
                else
                {
                    cond.Update(deal);
                }                       
            }
            this.RaiseListChangedEvents = true;
        }


And, Deal has the DP_insert/update like
        [Transactional(TransactionalTypes.TransactionScope)]
        protected override void DataPortal_Insert()
        {
            // insert this deal

            // update child objects
            _conditions.Update(this);
            MarkOld();
        }


There's a UI form DealForm.
Then there's UI control DealConditions, that is nothing more than a datagridview binded to a bindingsource binded to a new blank list of Conditions (if it's a new deal, else it fetches from the deal), _deal.DealConditions .

Here's the test case:
1. Open a new deal -> blank conditions datagrid
2. Added a new condition into the datagrid
3. Save, which is successfully saved onto the db
4. Save again without any changes

The problem arises here, it would invoke an insert again, instead of doing nothing. And, when checked from the breakpoint #1, IsNew and IsDirty both are true, which is why casuing it to insert again. (Similarly for an update, step 3 I updated successfully, then step 4 it updates again instead of doing nothing; IsDirty is true after the save)

So, I've gone back and re-test it, and set a breakpoint at #2 running up to step 3. MarkOld was invoked, and
IsNew and IsDirty both are false right after the insert. So, I am not sure what causes them to change back in between 3 and 4. Then, I did a step-out after step 3, I reached to this line in BusinessBase.Save()
        result = (T)DataPortal.Update(this);
where the IsDirty and IsNew were reset to true.

JoeFallon1 replied on Tuesday, June 24, 2008

I admit to not reading all the details - but the problem you are experiencing may be related to not updating the local variable of the BO.

e.g. calling mBO.Save is wrong.

You should have mBO = mBO.Save instead.

Joe

k2so replied on Tuesday, June 24, 2008

Thanks. That's right, that should be the problem, how careless...
it is referencing the original object.

JJLoubser replied on Wednesday, March 02, 2011

we are using csla.net 4 silverlight, in our case the IsDirty change very much, and the CanSave is not always what it must be, so we got mistakes with our properties set's, by debugging in BusinessBase.cs to show wich properties it was, but now sometimes when we enter a viewmodel again the bisiness object isBusy is true, I do not find the problem in businessBase.cs

 

cannot tel why is stays isBusy, there is still some cases where the CanSave is not what it must be...

 

any suggestions where to look?

 

businessbase - employee

children employeeDetails and employeeContactDetails and other...

employee links to EmployeeViewmodel that bind to EmployeeEdit.xaml

a child property looks like this:

 private bool IsLoadingEmployeeUserDetails = false;
        private static readonly PropertyInfo<EmployeeUserEC> EmployeeUserDetailsProperty = RegisterProperty<EmployeeUserEC>(p => p.EmployeeUserDetails);
        public EmployeeUserEC EmployeeUserDetails
        {
            get
            {
                if (!IsLoadingEmployeeUserDetails)
                {

                    if ((!FieldManager.FieldExists(EmployeeUserDetailsProperty) || ReadProperty(EmployeeUserDetailsProperty) == null))
                    {
                        IsLoadingEmployeeUserDetails = true;
                        ChildIsBusy = true;
#if SILVERLIGHT
                        if (!IsBusy)
                        MarkBusy();

                        if (IsNew)
                        {
                            //return EmployeeUserEC.NewEmployeeUserEC();

                            EmployeeUserEC.NewEmployeeUserEC(EmployeeID, (o, e) =>
                            {
                                if (e.Error != null)
                                    throw e.Error;
                                EmployeeUserDetails = e.Object;
                                IsLoadingEmployeeUserDetails = false;
                                if (!IsBusyLazyLoading)
                                MarkIdle();
                                ChildIsBusy = false;
                                OnPropertyChanged(EmployeeUserDetailsProperty);

                            }
                            );
                        }
                        else
                        {
                            EmployeeUserEC.GetEmployeeUser(EmployeeID, (o, e) =>
                               {
                                   if (e.Error != null)
                                       throw e.Error;
                                   EmployeeUserDetails = e.Object;
                                   IsLoadingEmployeeUserDetails = false;
                                   if (!IsBusyLazyLoading)
                                   MarkIdle();
                                   ChildIsBusy = false;
                                   OnPropertyChanged(EmployeeUserDetailsProperty);

                               }
                            );
                        }
#else
                        //if (!FieldManager.FieldExists(EmployeeUserDetailsProperty))
                        //   {
                        //     EmployeeUserDetails = DataPortal.Fetch<EmployeeUserEC>().Child;
                        //     OnPropertyChanged(EmployeeUserDetailsProperty);
                        //   }

#endif
                    }
                }
                try
                {
                    return GetProperty(EmployeeUserDetailsProperty);
                }
                catch (Exception eee)
                {
                    return null;
                }
            }
            set
            {
                LoadProperty(EmployeeUserDetailsProperty, value);
            }
        }

--------------------

it works fine, what is important to notice what we do is we build the tree collection over and over each time the User update and change something, each diffrent TreeViewListItem are a diffrent viewmodel and dependecyobject that are control in a diffrent xaml page, all works fine, but sometimes the isBusy stays true and the CanSave is wrong, we check all set properties all is fine now, here is the tree builder :

--------------------

xaml:

 

 <telerikNavigation:RadTreeView x:Name="radTreeView"
                                    BorderThickness="0"
                                    BorderBrush="Black"
                                    HorizontalAlignment="Stretch"
                                    VerticalAlignment="Stretch"
                                    ItemsSource="{Binding OrgStructList}"
                                    ItemTemplate="{StaticResource RootItem}"
                                    Width="300"
                                    IsDragDropEnabled="True"
                                    PreviewDragStarted="radTreeView_PreviewDragStarted"
                                    DragStarted="radTreeView_DragStarted"
                                    PreviewDragEnded="radTreeView_PreviewDragEnded"
                                    DragEnded="radTreeView_DragEnded"
                                    />

 <telerik:RadButton Name="SaveButton"
                               Height="Auto"
                               Content="Apply"
                               IsEnabled="{Binding Path=CanSave}"/>

              

                <telerik:RadButton Name="CancelButton" Height="Auto"  Margin="5,0,0,0"
                                   Content="Cancel"
                                   IsEnabled="{Binding Path=CanCancel}"/>

------------------

private ObservableCollection<TreeViewListItem> OrgStructListProperty;
    public ObservableCollection<TreeViewListItem> OrgStructList
    {
        get
        {
            if (Model.Company != null) //&& (ReadProperty(OrgStructureList_) == null))
                OrgStructListProperty = BuildTreeList();

            return OrgStructListProperty;
        }
        set
        {
            OrgStructListProperty = value;
        }

    }

-------------------------

    private ObservableCollection<TreeViewListItem> BuildTreeList()
    {

        StatusIsBusy = true;

        ObservableCollection<TreeViewListItem> _treeViewListCopy = new ObservableCollection<TreeViewListItem>();

        if (_treeViewList == null)
            _treeViewList = new ObservableCollection<TreeViewListItem>();
        else
        {

            _treeViewListCopy = _treeViewList;
            _treeViewList = new ObservableCollection<TreeViewListItem>();
        }
       
        //2. Build new Tree

        string imageURL = "/StaffSense;component/Images/Company.jpg";
        string unitsImageURL = "/StaffSense;component/Images/EmployeeUnits.jpg";
        string companyGroupImageURL = "/StaffSense;component/Images/CompanyGroup2.jpg";

        TreeViewListItem companyTreeListItem =
                new TreeViewListItem(_treeViewList, new CompanyItemViewModel { Model = Model.Company }, TreeViewListItemObjectType.Company, Model.Company.CompanyName, imageURL, Model.Company.CompanyID,0);
       

        _treeViewList.Add(companyTreeListItem);

        //Add company groups to company recursively
        foreach (var item in Model.Company.CompanyGroup)
        {
            TreeViewListItem treeViewListItem = new TreeViewListItem(companyTreeListItem.Children, new CompanyGroupItemViewModel { Model = item }, TreeViewListItemObjectType.CompanyGroup, item.Name, companyGroupImageURL, item.CompanyGroupID, 0);
            companyTreeListItem.Children.Add(treeViewListItem);
            HandleTreeItems(treeViewListItem.Children, item);
        }



        TreeViewListItem EmployeeUnitBoxTreeListItem =
                new TreeViewListItem(_treeViewList, new EmployeeUnitBoxItemViewModel { Model = Model.EmployeeUnitBox }, TreeViewListItemObjectType.EmployeeUnitBox, "Employee Units", unitsImageURL, -1,0);


        _treeViewList.Add(EmployeeUnitBoxTreeListItem);

        foreach (var item in Model.EmployeeUnitBox.EmployeeUnit)
        {
            TreeViewListItem treeEmployeeUnitViewListItem =
                new TreeViewListItem(EmployeeUnitBoxTreeListItem.Children, new EmployeeUnitItemViewModel { Model = item }, TreeViewListItemObjectType.EmployeeUnit, item.Name, unitsImageURL, item.EmployeeUnitID,0);
            EmployeeUnitBoxTreeListItem.Children.Add(treeEmployeeUnitViewListItem);
            HandleTreeItems(treeEmployeeUnitViewListItem.Children, item);
        }



        //3. Compare Tree's
        //4. Change New Tree: Add Structrue from old Copy Tree to new Tree like IsExpand properties
        foreach (var item in _treeViewList)
            foreach (var itemCopy in _treeViewListCopy)
            {

                if (item.Equals(itemCopy))
                {
                    //item.Parent_IsItemCollectionExpanded = itemCopy.Parent_IsItemCollectionExpanded;   // for companygroups
                    item.IsItemCollectionExpanded = itemCopy.IsItemCollectionExpanded;                 // for items
                    HandleIsExpandedTreeItems(item.Children, itemCopy.Children);
                    break;
                }
            }


        StatusIsBusy = false;

        return _treeViewList;

    }


    public void HandleTreeItems(ObservableCollection<TreeViewListItem> list, CompanyGroupEC parent)
    {

        string employeeImageURL = "/StaffSense;component/Images/Employee3.jpg";
        string AttendanceOfficerImageURL = "/StaffSense;component/Images/AttendanceOfficer2.jpg";
        string companyGroupImageURL = "/StaffSense;component/Images/CompanyGroup2.jpg";
        if (ListByMethod == 2)
        {
            //add employees
            foreach (EmployeeEC employee in parent.Employee)
            {

               
                TreeViewListItem treeViewListItem = new TreeViewListItem(list, new EmployeeItemViewModel { Model = employee }, TreeViewListItemObjectType.Employee, employee.Name, employeeImageURL, employee.EmployeeID,0);
                list.Add(treeViewListItem);


            }
        }
        if (ListByMethod == 3)
        {
            //add Attendance officers

            //sort:
            IEnumerable<AttendanceOfficerEC> subCollection = from ao in parent.AttendanceOfficer orderby ao.Priority select ao;



            foreach (AttendanceOfficerEC AttendanceOfficer in subCollection)
            {
              
                TreeViewListItem treeViewListItem = new TreeViewListItem(list, new AttendanceOfficerItemViewModel { Model = AttendanceOfficer }, TreeViewListItemObjectType.AttendanceOfficer, AttendanceOfficer.Name, AttendanceOfficerImageURL, AttendanceOfficer.EmployeeID, AttendanceOfficer.Priority);
                list.Add(treeViewListItem);

            }
        }

        //Always add subgroups
        if (parent.CompanyGroup.Count > 0)
        {
            foreach (CompanyGroupEC companyGroup in parent.CompanyGroup)
            {
               
                TreeViewListItem treeViewListItem = new TreeViewListItem(list, new CompanyGroupItemViewModel { Model = companyGroup }, TreeViewListItemObjectType.CompanyGroup, companyGroup.Name, companyGroupImageURL, companyGroup.CompanyGroupID,0);
                list.Add(treeViewListItem);
                HandleTreeItems(treeViewListItem.Children, companyGroup);
            }
        }


    }


    public void HandleTreeItems(ObservableCollection<TreeViewListItem> list, EmployeeUnitEC parent)
    {

        string employeeImageURL = "/StaffSense;component/Images/Employee3.jpg";
        string AttendanceOfficerImageURL = "/StaffSense;component/Images/AttendanceOfficer2.jpg";
        string unitsImageURL = "/StaffSense;component/Images/EmployeeUnits.jpg";
       
        if (ListByMethod == 2)
        {
            //add employees
            foreach (EmployeeEC employee in parent.Employee)
            {
               
                TreeViewListItem treeViewListItem = new TreeViewListItem(list, new EmployeeItemViewModel { Model = employee }, TreeViewListItemObjectType.Employee, employee.Name, employeeImageURL, employee.EmployeeID, 0);
                list.Add(treeViewListItem);


            }
        }
        if (ListByMethod == 3) //no AttendanceOfficer for units
        {
            ////add Attendance officers
            //foreach (AttendanceOfficerEC AttendanceOfficer in parent.AttendanceOfficer)
            //{
            //    TreeViewListItem treeViewListItem = new TreeViewListItem(list, new AttendanceOfficerItemViewModel { Model = AttendanceOfficer }, TreeViewListItemObjectType.AttendanceOfficer, AttendanceOfficer.Name, AttendanceOfficerImageURL, AttendanceOfficer.EmployeeID, AttendanceOfficer.Priority);
            //    list.Add(treeViewListItem);

            //}
        }

        //Always add subgroups
        if (parent.EmployeeUnit.Count > 0)
        {
            foreach (EmployeeUnitEC EmployeeUnit in parent.EmployeeUnit)
            {
               
                TreeViewListItem treeViewListItem = new TreeViewListItem(list, new EmployeeUnitItemViewModel { Model = EmployeeUnit }, TreeViewListItemObjectType.EmployeeUnit, EmployeeUnit.Name, unitsImageURL, EmployeeUnit.EmployeeUnitID, 0);
                list.Add(treeViewListItem);
                HandleTreeItems(treeViewListItem.Children, EmployeeUnit);
            }
        }


    }

 

JJLoubser replied on Wednesday, March 02, 2011

lol

it was a password field hidden with a invisibilityconverter when isCheckedbox was not check, the IsAsync validationrule run the password required, but it was not required, so I just add some functionality to fix the validatin problem, ...

RockfordLhotka replied on Tuesday, June 24, 2008

>3. Save, which is successfully saved onto the db
>4. Save again without any changes

And between 3 and 4 you rebind the UI to the result of Save() right?

 

Remember that Save() returns the resulting object, and the original object must be discarded. I suspect you are allowing the UI to continue to use the original (unupdated) object.

 

If you are using a local data portal configuration, this is a change in default behavior from 3.0 to 3.5 – it is one of the breaking changes noted in the change log – see the one about AutoCloneOnUpdate.

 

I recommend allowing the clone operation to occur (which is the default in 3.5) and changing your code accordingly. But if you want to continue to use the old approach (which is really broken, hence the change in 3.5) you can set AutoCloneOnUpdate to get the old behavior.

 

Rocky

 

Copyright (c) Marimer LLC