Recommendation needed how to implement a special delete behaviour
 
CapableObjects Forums
Home       Members    Calendar    Who's On
Welcome Guest ( Login | Register )
        


123»»»

Recommendation needed how to implement a... Expand / Collapse
Author
Message
Posted 2010-02-26 12:19:40
Supreme Being

Supreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme Being

Group: Forum Members
Last Login: 2010-05-15 22:36:10
Posts: 188, Visits: 451
Hi everybody,

I stuck with 2 requirements in business logic how to bring them together. Imagine 2 classes with 1..* association:

 A 1 ----- * B

First requirement: cascade deletion - delete all Bs when A is to be deleted

The second one: for an instance of A at least one instance of B must exist (please don't come with constraints: I avoid them in general)

My problem: I have defined cascade deletion rule from A to B and PreDelete and SingleLinkModified for clas B like this:

public void PreDelete() {if (a!= null && a.b.Count == 1) throw new Exception("At least one item must exist");}

void ISingleLinkCatcher.SingleLinkModified(IAssociationEnd ae, IEcoObject oldValue, IEcoObject newValue) {

if (ae.LoopbackIndex == Eco_LoopbackIndices.A)

A oldA = (A)oldValue;

if (newValue == null && oldA.b.Count == 0)

{  this.a = oldA ;  throw new Exception("At least one item must exist"}  };  }

When an instance of A is about to be deleted, it tries to delete all associated Bs, but the last can't be deleted because of the second rule. THe only solution I found is to use additional bool property "IsAboutToDelete" on class A that is set to true in A.PreDelete and checked in SingleLinkModified. Only if it is false the exception would be thrown.

Perhaps I miss a purer ECO solution here? Glad to hear of alternatives.

/Efim



/Efim
Post #4909
Posted 2010-02-27 07:20:21
Supreme Being

Supreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme Being

Group: Forum Members
Last Login: 2010-06-30 19:29:39
Posts: 104, Visits: 314
Why don't you want to use the DeleteAction property on an association end?  It has the options of <Default>, Allow, Prohibit, and Cascade.  I like the Default because then the Composition and Aggregation settings control the delete action.

I have been setting this property in my models, but I have not verified if it actually works.  I just assumed it did.  If it doesn't, please let us know what the problems are.

Brian

Post #4913
Posted 2010-02-28 19:57:09
Supreme Being

Supreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme Being

Group: Forum Members
Last Login: 2010-05-15 22:36:10
Posts: 188, Visits: 451
Hi Brian,

I use AggregationKind, it is effectively the same as DeleteAction in ECO context. If you reread my posting you could see there 2 contradictory requirements: 1. Delete all subitems when item is about to be deleted (cascade deletion); 2. For each item it should exist at least 1 subitem. If ECO implemented cascade deletion in another order, I would have no problem: at first delete item, then subitems. But apparently the subitems are deleted at first, and that leads to collision with the second requirement.

/Efim

Post #4920
Posted 2010-03-01 00:26:40
Supreme Being

Supreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme Being

Group: Forum Members
Last Login: 2010-06-30 19:29:39
Posts: 104, Visits: 314
After reading more closely, I see you are using the cascading delete functionality built into ECO.  In your situation you have two rules:  First, The cascading delete composition implies the details (b) must have a master (a).  Second, that the master must have at least one detail.  These two rules contradict one another during deletion.  It is similar to circular foreign key constraints in a database.

You must suppress one of the rules in order to accomplish a deletion.  So I would say your approach of suppressing the second rule is the best solution.

Brian

Post #4922
Posted 2010-03-01 11:02:20
Supreme Being

Supreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme Being

Group: Forum Members
Last Login: 2010-05-15 22:36:10
Posts: 188, Visits: 451
Nevertheless I'm not happy with it. The first rule concerns deletion whereas the second rule is for existing items and I would prefer to separate these. 

/Efim
Post #4923
Posted 2010-03-02 00:25:55
Supreme Being

Supreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme Being

Group: Forum Members
Last Login: 2010-06-30 19:29:39
Posts: 104, Visits: 314
It takes a little bit of explaining, but here is an alternative approach.

One of the nicest interfaces in .NET is the IDataErrorInfo.  It is a rather simple interface, but what it provides is enormous.  It basically provides an interface for communicating errors between bound objects and controls on a form.  Winforms and WPF controls use this interface if the object bound to implements it.  Unfortunately, ASP.NET does not, but you can create your own validators to do so.  That is what I have done.

I implement most of my business rules/constraints in IDataErrorInfo's indexer property.  I do this by implementing the interface on a base class for all my eco objects and overriding behavior subclasses.  I create a virtual GetPropertyError method that subclasses override to check for errors.  I have used this interface this way in past non-eco projects with great success.  From my current eco project I'm working on, here is the base class implementation (note, it is not completely finished yet):

        #region IDataErrorInfo

 

        [XmlIgnore]

        string IDataErrorInfo.Error { get { return(GetErrors()); } }

        [XmlIgnore]

        string IDataErrorInfo.this[string columnName] { get { return(GetPropertyError(columnName)); } }

 

        public virtual string GetPropertyError(string propertyName)

        {

            return "";

        }

 

        public virtual string GetErrors()

        {

            StringBuilder sb = new StringBuilder();

            //todo: add code to loop through properties and return error

            //todo: add code to check contraints

            var oclService = AsIObject().ServiceProvider.GetEcoService<IOclService>();

            //todo: this is a first stab at getting constraint errors, it has not been tested

            foreach ( IConstraint constraint in AsIObject().UmlType.Constraints ) {

                IElement oclResult = oclService.Evaluate(AsIObject(), constraint.Body.Body);

                if ( oclResult.GetValue<bool>() ) {

                    sb.Append(constraint.Description);

                    sb.Append(Environment.NewLine);

                }

            }

            return sb.ToString();

        }

 

        #endregion

 

Note, that an empty string from the GetPropertyError represents a non-error condition.  The GetErrors method returns all errors that exist on the object.  I have more work to do here, but the beauty is that I only need to do this once and everything will use it.  I also plan to add a similar GetErrors method to my EcoSpace that will loop through all modified objects and call each objects GetErrors method to get all errors that exist from a user’s changes.  If any errors exist, I will not allow the changes to be saved.  On the form the user gets an error message that contains the same text that GetErrors returns.  Also, each control that has a corresponding error gets an indicator showing where the error exists.  By using this pattern you do not have to do any coding on your forms to get full error handling.  The coding is done once in the validation controls that I mentioned above.

Here is how I validate that the FirstName and LastName are required in my Person object:

        public override string GetPropertyError(string propertyName)

        {

            string result = "";

            switch ( propertyName ) {

                case "FirstName": {

                        if ( string.IsNullOrEmpty(FirstName) ) {

                            result = GetRequiredPrompt(propertyName);

                        }

                        break;

                    }

                case "LastName": {

                        if ( string.IsNullOrEmpty(LastName) ) {

                            result = GetRequiredPrompt(propertyName);

                        }

                        break;

                    }

            }

            return result;

        }

 

This is rather simple, but I use this for very complex validation as well.

In your situation you could check for at least one detail (B) in your master (A) object’s GetPropertyError method.  There is a lot of benefit to not throw exceptions when enforcing constraints.  It raises havoc with data binding in both winforms and wpf.  By allowing changes to happen and putting the object into an error state eliminates the deadlock situations.

Hope this is understandable.  It is hard to explain all this in a forum entry.

Brian

 

 

Post #4939
Posted 2010-03-02 12:31:25
Supreme Being

Supreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme Being

Group: Forum Members
Last Login: Today @ 11:08:06
Posts: 256, Visits: 2 317
efim (2010-02-26)
please don't come with constraints: I avoid them in general


And that's the problem :-)

You are putting the invariant checking on the property level, which is not really the best way to do it. For example if you had a class with StartDate and EndDate, and on the properties you enforced an invariant that EndDate >= StartDate then you always have to do this

Then you will have to write code like this to ensure you don't throw an exception
EndDate = SomeEndDate;
StartDate = SomeStartDate;

and then when you want to move the dates earlier you would have to do this to ensure you don't throw the exception
StartDate = NewStartDate;
EndDate = NewEndDate;

and to move the dates later you would have to do
EndDate = NewEndDate;
StartDate = NewStartDate;

Basically you are forcing the user to enter data in a specific order, in this case the user of your class is more code but it could be a user interface. You wouldn't write code like that, would you? I hope not!

But that is what you are doing here. What you should do is to put constraints on the "A" class and make sure that your EcoSpace always validates objects before performing an UpdateDatabase. That would solve your problem immediately and save you from encountering other similar problems in the future.


====
Pete
Post #4943
Posted 2010-03-03 16:37:32
Supreme Being

Supreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme Being

Group: Forum Members
Last Login: 2010-05-15 22:36:10
Posts: 188, Visits: 451
I can't agree with the validation point "update database". It would mean only the objects in database are valid, but what if these are not yet saved and the user rely on the valid state? Normally an  invariant should be validated after each public method call, not only Update DB, because of that I don't like invariants.

/Efim
Post #4958
Posted 2010-03-03 22:39:40
Supreme Being

Supreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme Being

Group: Forum Members
Last Login: 2010-06-30 19:29:39
Posts: 104, Visits: 314
efim (2010-03-03)
I can't agree with the validation point "update database". It would mean only the objects in database are valid, but what if these are not yet saved and the user rely on the valid state? Normally an  invariant should be validated after each public method call, not only Update DB, because of that I don't like invariants.

If you display the invalid state / errors to the user, the user would know not to rely on the state.  If you implement Peter's example of enforcing a start date to be before an end date by thowing an exception, you will have many problems.  Not only will you put undo constraints on the user's work flow, but you will not be able to use data binding on your forms.  I know it will not work in Winforms.  I don't think it will work in WPF either.  It might work in ASP.NET because data binding occurs on a form submit rather than granularly for each property.

Brian

Post #4960
Posted 2010-03-04 11:41:29
Supreme Being

Supreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme BeingSupreme Being

Group: Forum Members
Last Login: 2010-05-15 22:36:10
Posts: 188, Visits: 451
Hi Brian,

I avoid constraints whenever possible because they cry after something bad happened. I need then some means to go back to a valid state, and it is not always good if user should do it. I avoid user interactions in business logic concerns whenever possible. I certainly understand there are scenarios where it is inevitable or preferrable. Perhaps Peter's example is one of them.

Meanwhile I imagine a suitable solution for my case. I would implement cascade deletion by myself, all in an in-memotry transaction (pseudocode):

class A

void Predelete() {

try

  Begin Transaction;

  this.IsAboutToDelete = true;

  <Delete subitems of B>

  commit

except

  rollback

  this.IsAboutToDelete = false;

end 

This way I guarantee always valid state of the model.

BTW, thank you for the tip to use IDataError. I didn't closely use it before. I don't want however to use too specific .Net features in the business logic layer, because it belongs closer to PIM and it can be more difficult to migrate it in the future to other platforms.

I don't understand in what concern throwing exception can be problem for data binding?

}

/Efim

Post #4964
« Prev Topic | Next Topic »

123»»»

Reading This Topic Expand / Collapse
Active Users: 0 (0 guests, 0 members, 0 anonymous members)
No members currently viewing this topic.
Forum Moderators: HansKarlsen, Jonas Hogstrom

Permissions Expand / Collapse

All times are GMT +1:00, Time now is 4:22

Powered By InstantForum.NET v4.1.4 © 2010
Execution: 0,203. 8 queries. Compression Disabled.