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