Hi all
I’m currently implementing an application that uses the party/role concept. For instance I have a Party class, that has a collection of Addresses and a collection of ContactMethods. The Party class can represent either a person or an organization.
I have created IPerson and IOrganization interfaces which the Party class implements. For example, the IPerson interface has FirstName, MiddleName and LastName properties whereas the IOrganization interface only has an OrganizationName property.
All of these classes and interfaces are put into a separate assembly to make them as reusable as possible.
In my application I have several root classes such as Doctor, School, Institution and so on, that each has a Party object embedded using composition. Each of these classes implement either the IPerson or the IOrganization interface which passes the properties on to the embedded Party object. This is to save the front-end developer from having to worry about whether or not to fill out first-, middle and lastname or organizationname, depending on which type of Party he/she is dealing with.
All this works like a charm. However, if a rule is broken in the Party object or one of it’s Address or ContactMethod objects I can’t figure out how to get to these. They won’t be in the root objects BrokenRulesCollection. I could add an extra property, to the root object, that returns the Party object’s BrokenRulesCollection, but this, kind of, doesn’t “feel” right.
I thought of, instead of implementing the IPerson or IOrganization interface directly in my root object, I would have a property that returns the Party object, but still I would like to return it as either one of the interface types and then I’m stuck with the same problem.
Doing some investigation into the depths of the framework I could of course implement an IBrokenRules interface that both the IPerson and IOrganization interfaces inherits from. This actually leads to my question(s). Is this the right way to go or am I missing something entirely? Is it correct of me to go the interface route in this situation or shouldn’t I use interfaces and just return the plain business objects and let it be up the the front-end developer to figure out which properties to set?
Thanks in advance
Henrik
<quote>I could add an extra property, to the root object, that returns the Party object’s BrokenRulesCollection, but this, kind of, doesn’t “feel” right.</quote>
My view is that a master BO which contains other BOs should be able to list out all rules that are broken using a single call. This is why I implemented AllRules in my custom layer that inherits from Csla. It was posted in the old message board and I have been using it for a long time now.
It can be called like this:
=============================================================
AllRules.GetAllBrokenRulesString(mSomeBO)
=============================================================
<Serializable()> _
Public MustInherit Class MyBusinessBase(Of T As MyBusinessBase(Of T))
Inherits BusinessBase(Of T)
#Region " AllRules "
Private Shared BIZ_TYPE As Type = GetType(MyBusinessBase(Of T))
Private Shared COLL_TYPE As Type = GetType(MyBusinessListBase(Of Core.BusinessBase))
Public Function GetAllBrokenRulesString() As String
Dim sb As New Text.StringBuilder
Return GetBrokenRulesString(Me, sb)
End Function
Private Function GetBrokenRulesString(ByVal root As MyBusinessBase(Of T), ByRef sb As Text.StringBuilder) As String
Dim j, k As Integer
Dim t As Type = root.GetType()
Dim obj As MyBusinessBase(Of T)
Dim coll As MyBusinessListBase(Of Core.BusinessBase)
Dim strg As String = root.BrokenRulesCollection.ToString
If Len(strg) > 0 Then
sb.Append("(" & root.Table & ")" & vbCrLf & strg & vbCrLf)
End If
Dim fi As System.Reflection.FieldInfo() = t.GetFields((BindingFlags.Public Or BindingFlags.Instance Or BindingFlags.NonPublic))
For j = 0 To fi.Length - 1
If fi(j).FieldType.IsSubclassOf(COLL_TYPE) Then
coll = CType(fi(j).GetValue(root), MyBusinessListBase(Of Core.BusinessBase))
If Not coll Is Nothing Then
'this is a child collection, cascade the call
For k = 0 To coll.Count - 1
obj = CType(coll(k), MyBusinessBase(Of T))
If Not obj Is Nothing Then
GetBrokenRulesString(obj, sb)
End If
Next
End If
ElseIf fi(j).FieldType.IsSubclassOf(BIZ_TYPE) Then
obj = CType(fi(j).GetValue(root), MyBusinessBase(Of T))
If Not obj Is Nothing Then
GetBrokenRulesString(obj, sb)
End If
End If
Next
Return sb.ToString
End Function
#End Region
End Class
Hello Henrick,
I have to say that I agree with Joe, but I also wanted to mention that we're following the contact module design that you're talking about as well - I've seen different flavors of it but the terms contact mechanism and party roles are unmistakable.
Feel free to contact me if you'd ever like to discuss particular challenging areas (contact mechanisms and the sharing of them is a challenging aspect imo).
Regards,
Chris
Chris
Please see my reply to Joe Fallon for how I'm going to solve my problem.
This is my 6th application where I use the party/role concept. Some of my app's just use the party and it's address and contactmethod collections as simple datacontainers where each person or organization has it's own set og addresses and contactmethods. In only two of my applications have I implemented sharing of contact mechanisms and I agree with you, it's very challenging. It isn't trivial.
If I run into other challeging areas of these concepts I will take you up on your offer. You may also contact me, if there is any info I can give you.
I picked the party/role concept up from Len Silverston's book "The Data Model Resource Book" volume 1 & 2, which I can highly recommend. He presents a conceptual model og how to deal with people and organizations in a combined party concept, which roles these parties play and how they relate to each other.
/Henrik
Joe
Thank you for your suggestion. I have actually come across your function on the old messageboard, but had forgotten about it. I particularly like that the method is generic and can be implemented in BusinessBase.
The method will work perfectly for objects “acting-as” another object, by implementing it’s interface and hiding the actual object from the front-end developer.
For child-objects visible to the front-end developer I’m not sure that returning a single string with all broken rules for all child-objects will solve my problem. Many of my UI’s contains multiple tab pages and I would like to be able to pinpoint the exact fields that is causing validation errors. If I just return a string with all broken rules for the root and all child-objects I cannot distinguish them from each other. In this situation I will still rely on the “GetBrokenRulesCollection” of each child object.
Mike,
Turns out this was a tough issue for me too. I finally decided that the use of Generics was incorrect in this code. On a side note you can get COLL_TYPE to compile using this totally unintuitive trick:
Private Shared COLL_TYPE As Type = GetType(MyBusinessListBase(Of , ))
It never occured to me that you could *omit* the T, C parameters!
Anyway - it turns out that "generics are NOT polymorphic". Memorize that one! If you want to treat a bunch of various BOs as the same type then you can't use their generic base class - you need to "find" a common interface. That is one reason that Rocky has been adding so many interfaces to Csla 2.1. If you can't "find" one of his then you need to build your own.
I did not find one for collections so I built this one:
Public Interface IBusinessCollection
ReadOnly Property Item(ByVal index As Integer, ByVal useInterface As Boolean) As ImyBusinessObject
ReadOnly Property IsDirty() As Boolean
ReadOnly Property IsValid() As Boolean
ReadOnly Property Count() As Integer
End Interface
Then in my Base class for BusinessListBase I implemented the interface so that all of my concrete collections have it:
#Region " IBusinessCollection Implementation "
'useInterface is a fake parameter and is only used to change the method signature so we can Overload the Item method.
'It does not use the Boolean value at all.
'This way the Base Item method can still return a strongly typed child BO.
'This Item method returns the Interface version of the child BO as IEditableBusinessObject.
Public Overloads ReadOnly Property Item(ByVal index As Integer, ByVal useInterface As Boolean) As IMyBusinessObject Implements IBusinessCollection.Item
Get
Return MyBase.Item(index)
End Get
End Property
Public Overloads ReadOnly Property Count() As Integer Implements IBusinessCollection.Count
Get
Return MyBase.Count
End Get
End Property
Public Overloads ReadOnly Property IsDirty() As Boolean Implements IBusinessCollection.IsDirty
Get
Return MyBase.IsDirty
End Get
End Property
'override CSLA2 code to look like this - no short circuiting involved here.
'run through all the child objects and if any are invalid then the collection is invalid
'call IsValid on all children so that when we examine Broken Rules we get the right set of values.
'Rocky stopped looping after he found the first invalid child.
Public Overrides ReadOnly Property IsValid() As Boolean Implements IBusinessCollection.IsValid
Get
Dim result As Boolean = True
For Each child As C In Me
If Not child.IsValid Then
result = False
End If
Next
Return result
End Get
End Property
#End Region
I did something similar for Root BOs.
Public Interface IMyBusinessObject
Inherits IEditableBusinessObject
ReadOnly Property BrokenRulesCollection() As Validation.BrokenRulesCollection
Property Table() As String
Property IsOwner() As Boolean
ReadOnly Property OwnedObjects() As IList
ReadOnly Property OwnedCollections() As IList
End Interface
#Region " IMyBusinessObject Implementation "
Public Overrides ReadOnly Property BrokenRulesCollection() As Validation.BrokenRulesCollection Implements IMyBusinessObject.BrokenRulesCollection
Get
Return MyBase.BrokenRulesCollection
End Get
End Property
Public Property Table() As String Implements IMyBusinessObject.Table
Get
Return mTable
End Get
Set(ByVal value As String)
mTable = value
End Set
End Property
'set to True if the derived BO contains other BOs.
'Allows the derived classes to skip overriding of IsDirty and IsValid.
Public Property IsOwner() As Boolean Implements IMyBusinessObject.IsOwner
Get
Return mIsOwner
End Get
Set(ByVal value As Boolean)
mIsOwner = value
End Set
End Property
'Extract just the directly owned objects
Protected ReadOnly Property OwnedObjects() As IList Implements IMyBusinessObject.OwnedObjects
Get
If mOwnedObjects Is Nothing Then
GetOwnedFields()
End If
Return mOwnedObjects
End Get
End Property
'Extract just the directly owned collections
Protected ReadOnly Property OwnedCollections() As IList Implements IMyIBusinessObject.OwnedCollections
Get
If mOwnedCollections Is Nothing Then
GetOwnedFields()
End If
Return mOwnedCollections
End Get
End Property
'fill the 2 array lists once with the Root BOs and Editable Collections and use many times in Property gets above.
Private Sub GetOwnedFields()
mOwnedCollections = New ArrayList
mOwnedObjects = New ArrayList
Dim fields As FieldInfo() = Me.GetType().GetFields(BindingFlags.Instance Or BindingFlags.Public Or BindingFlags.NonPublic)
Dim field As FieldInfo
For Each field In fields
If GetType(IBusinessCollection).IsAssignableFrom(field.FieldType) Then
mOwnedCollections.Add(field.GetValue(Me))
ElseIf GetType(IMyBusinessObject).IsAssignableFrom(field.FieldType) Then
mOwnedObjects.Add(field.GetValue(Me))
End If
Next
End Sub
#End Region
Then I re-wrote the code for GetBrokenRulesString to use the 2 new interfaces instead of the generic code which did not work.
Public Overrides ReadOnly Property IsDirty() As Boolean
Get
If mIsOwner Then
Return MyBase.IsDirty OrElse CheckDirty(Me)
Else
Return MyBase.IsDirty
End If
End Get
End Property
Private Shared Function CheckDirty(ByVal root As MyBusinessBase(Of T)) As Boolean
Dim coll As IBusinessCollection
For Each coll In root.OwnedCollections
If Not coll Is Nothing Then
If coll.IsDirty Then
Return True
End If
End If
Next
Dim obj As IMyBusinessObject
For Each obj In root.OwnedObjects
If Not obj Is Nothing Then
If obj.IsDirty Then
Return True
End If
End If
Next
Return False
End Function
'we always want to know all broken rules so do not bail out of the logic early if the parent container object is invalid.
'Do NOT change the "And" to "AndAlso".
'Many BOs have code in IsValid to make a final decision on whether or not a rule is broken. If the code never gets called
'then when we list out AllRules.GetAllBrokenRulesString we may not be correct.
Public Overrides ReadOnly Property IsValid() As Boolean
Get
If mIsOwner Then
'Do NOT change the "And" to "AndAlso".
Return MyBase.IsValid And CheckValid(Me)
Else
Return MyBase.IsValid
End If
End Get
End Property
Private Shared Function CheckValid(ByVal root As MyBusinessBase(Of T)) As Boolean
Dim coll As IBusinessCollection
For Each coll In root.OwnedCollections
If Not coll Is Nothing Then
If Not coll.IsValid Then
Return False
End If
End If
Next
Dim obj As IMyBusinessObject
For Each obj In root.OwnedObjects
If Not obj Is Nothing Then
If Not obj.IsValid Then
Return False
End If
End If
Next
Return True
End Function
Public Function GetAllBrokenRulesString() As String
Dim sb As New Text.StringBuilder
Return GetBrokenRulesString(Me, sb)
End Function
Private Function GetBrokenRulesString(ByVal root As IMyBusinessObject, ByRef sb As Text.StringBuilder) As String
Dim i As Integer
Dim obj As IMyBusinessObject
Dim coll As IBusinessCollection
Dim brokenRules As String = root.BrokenRulesCollection.ToString
If Len(brokenRules) > 0 Then
sb.Append("(" & root.Table & ")" & vbCrLf & brokenRules & vbCrLf)
End If
For Each coll In root.OwnedCollections
'this is a child collection, cascade the call
For i = 0 To coll.Count - 1
obj = coll.Item(i, True)
If Not obj Is Nothing Then
GetBrokenRulesString(obj, sb)
End If
Next
Next
For Each obj In root.OwnedObjects
If Not obj Is Nothing Then
GetBrokenRulesString(obj, sb)
End If
Next
Return sb.ToString
End Function
Joe
When displaying a message to the user about a broken child, I use this linq code which loops through the child properties picking up the invalid business bases within the business list bases (will need a similar query for child business base within currentroot and possibily recursive if more than 2 levels):
var invalidobjs = from prop in CurrentRoot.GetType().GetProperties()
where
(
(prop.PropertyType.GetInterface(typeof (Csla.Core.ITrackStatus).Name) != null)
&&
(prop.PropertyType.GetInterface(typeof (IQueryable<CHILDOBJ>).Name) != null)
)
let value = (Csla.Core.ITrackStatus) prop.GetValue(CurrentRoot, null)
where ((!value.IsValid) && (value != null))
let obj = (IQueryable<CHILDOBJ>)value
from b in obj
where !b.IsValid
select b;
foreach (var invalidobj in invalidobjs)
{
var busobj = invalidobj as Csla.Core.BusinessBase;
if (busobj == null) continue;
View.DisplayBrokenRules(busobj.BrokenRulesCollection); // first invalid child
break;
}
Copyright (c) Marimer LLC