An IIR (EditableRoot) has a SourceId property (Integer) which must exist. This is enabled through the use of Validation Rules in the IIR:
Private Shared Function SourceIdMustExistRule(Of T As IIR)(ByVal target As T, ByVal e As Csla.Validation.RuleArgs) As Boolean
Dim SourceInfoListInstance As SourceKeyValueCollection = SourceKeyValueCollection.GetIIRSourceKeyValueCollection
If SourceInfoListInstance.ContainsKey(target.ReadProperty(SourceIdProperty)) = True Then
Return True
Else
e.Description = "A Source with that Id does not exist (Id: " & target.ReadProperty(SourceIdProperty) & ")"
Return False
End If
End Function
SourceKeyValueCollection is currently a non-cached (will be cached soon) read-only key-value collection of SourceId -> SourceName items.
This all works fine.
Problem:
I want to allow the users to edit the list of sources, but I don't want them to be able to delete any sources if they're in use in an IIR. This rule should be easy to implement in the Validation Rules of an editable Source item. If the source isn't used it can be deleted.
I created a SourceList (BusinessListBase) with child SourceListItems (BusinessBase) as per p179 in BO 2008.
CRUD works well and the items work as expected, except limiting the ability to cancel delete if the SourceListItem's Id is in use in an IIR.
SourceList Implementation:
Imports Csla.Data
Imports System
Imports System.Collections.Generic
Imports Csla
Imports Csla.Security
<Serializable()> _
Public Class SourceList
Inherits BusinessListBase(Of SourceList, SourceListItem)
#Region "Authorization Rules"
Protected Overrides Function AddNewCore() As Object
Dim obj As SourceListItem = SourceListItem.NewSourceListItem
Me.Add(obj)
Return obj
End Function
Public Function GetDefault() As SourceListItem
For Each Item As SourceListItem In Me
If Item.IsDefault = True Then Return Item
Next
Return Nothing
End Function
Protected Shared Sub AddObjectAuthorizationRules()
Csla.Security.AuthorizationRules.AllowEdit(GetType(SourceList), My.Resources.QAManagers, "Administrators")
End Sub
#End Region
#Region "Factory Methods"
Public Shared Function NewSourceList() As SourceList
Return DataPortal.Create(Of SourceList)()
End Function
Public Shared Function GetSourceList() As SourceList
Return DataPortal.Fetch(Of SourceList)()
End Function
' Require use of factory methods
Private Sub New()
AllowNew = True
End Sub
#End Region
#Region "Data Access"
Private Overloads Sub DataPortal_Fetch()
RaiseListChangedEvents = False
Using ctx = ContextManager(Of LinqDAL.IIRTrackerDBLinqDataContext).GetManager(LinqDAL.Database.IIRData)
Dim data = (From o In ctx.DataContext.IIRSources Select o Order By o.Name Ascending).ToArray
For Each obj As LinqDAL.IIRSource In data
Me.Add(SourceListItem.GetSourceListItem(obj))
Next
End Using
RaiseListChangedEvents = True
End Sub
Protected Overloads Sub DataPortal_Update()
Me.Child_Update()
End Sub
#End Region
End Class
SourceListItem Implementation:
Imports Csla.Security
Imports Csla
Imports Csla.Data
<Serializable()> _
Public Class SourceListItem
Inherits BusinessBase(Of SourceListItem)
#Region " Business Methods "
Private Shared IdProperty As PropertyInfo(Of Integer) = RegisterProperty(New PropertyInfo(Of Integer)("Id", "Id"))
''' <Summary>
''' Gets and sets the Id value.
''' </Summary>
Public Property Id() As Integer
Get
Return GetProperty(IdProperty)
End Get
Set(ByVal value As Integer)
SetProperty(IdProperty, value)
End Set
End Property
Private Shared NameProperty As PropertyInfo(Of String) = RegisterProperty(New PropertyInfo(Of String)("Name", "Name"))
''' <Summary>
''' Gets and sets the Name value.
''' </Summary>
Public Property Name() As String
Get
Return GetProperty(NameProperty)
End Get
Set(ByVal value As String)
SetProperty(NameProperty, value)
End Set
End Property
Private Shared IsDefaultProperty As PropertyInfo(Of Boolean) = RegisterProperty(New PropertyInfo(Of Boolean)("IsDefault", "IsDefault"))
''' <Summary>
''' Gets and sets the IsDefault value.
''' </Summary>
Public Property IsDefault() As Boolean
Get
Return GetProperty(IsDefaultProperty)
End Get
Set(ByVal value As Boolean)
SetProperty(IsDefaultProperty, value)
End Set
End Property
#End Region
#Region " Validation Rules "
Protected Overrides Sub AddBusinessRules()
ValidationRules.AddRule(Of SourceListItem)(AddressOf Csla.Validation.CommonRules.StringRequired, NameProperty)
ValidationRules.AddRule(Of SourceListItem)(AddressOf OnlyOneDefaultSourceListItem, IsDefaultProperty)
ValidationRules.AddRule(Of SourceListItem)(AddressOf UniqueNameRule, NameProperty)
End Sub
Private Shared Function UniqueNameRule(Of T As SourceListItem)(ByVal target As T, ByVal e As Csla.Validation.RuleArgs) As Boolean
Dim Parent As SourceList = DirectCast(target.Parent, SourceList)
If Parent IsNot Nothing Then
For Each item As SourceListItem In Parent
If item.Name = target.ReadProperty(NameProperty) AndAlso Not (ReferenceEquals(item, target)) Then
e.Description = "There is another IIR Source List Item with that Name (Current: " & target.Name & ", In Database: " & item.Name & "). Please choose another Id"
Return False
End If
Next
End If
Return True
End Function
Private Shared Function OnlyOneDefaultSourceListItem(Of T As SourceListItem)(ByVal target As T, ByVal e As Csla.Validation.RuleArgs) As Boolean
Dim Parent As SourceList = DirectCast(target.Parent, SourceList)
If Parent IsNot Nothing Then
If target.IsDefault = True Then
For Each item As SourceListItem In Parent
If item.IsDefault = True AndAlso Not (ReferenceEquals(item, target)) Then
e.Description = "There is another IIR Source set as default"
Return False
End If
Next
End If
End If
Return True
End Function
#End Region
#Region " Authorization Rules "
Protected Overrides Sub AddAuthorizationRules()
AuthorizationRules.AllowWrite(IdProperty, "IIR_QAManagers", "Administrators")
AuthorizationRules.AllowWrite(NameProperty, "IIR_QAManagers", "Administrators")
AuthorizationRules.AllowWrite(IsDefaultProperty, "IIR_QAManagers", "Administrators")
End Sub
Protected Shared Sub AddObjectAuthorizationRules()
Csla.Security.AuthorizationRules.AllowEdit(GetType(SourceListItem), "IIR_QAManagers", "Administrators")
End Sub
#End Region
#Region " Factory Methods "
Friend Shared Function NewSourceListItem() As SourceListItem
Return DataPortal.CreateChild(Of SourceListItem)()
End Function
Friend Shared Function GetSourceListItem(ByVal childData As LinqDAL.IIRSource) As SourceListItem
Return DataPortal.FetchChild(Of SourceListItem)(childData)
End Function
Private Sub New()
'Require use of factory methods
End Sub
#End Region
#Region " Data Access "
Protected Overrides Sub Child_Create()
'TODO: load default values
'omit this override if you have no defaults to set
MyBase.Child_Create()
End Sub
Private Sub Child_Fetch(ByVal childData As LinqDAL.IIRSource)
With childData
LoadProperty(IdProperty, .Id)
LoadProperty(NameProperty, .Name)
LoadProperty(IsDefaultProperty, .IsDefault)
End With
End Sub
Private Sub Child_Insert()
Using ctx = ContextManager(Of LinqDAL.IIRTrackerDBLinqDataContext).GetManager(LinqDAL.Database.IIRData)
Dim data = New LinqDAL.IIRSource
With data
.Name = ReadProperty(NameProperty)
.IsDefault = ReadProperty(IsDefaultProperty)
End With
ctx.DataContext.IIRSources.InsertOnSubmit(data)
ctx.DataContext.SubmitChanges()
LoadProperty(IdProperty, data.Id)
End Using
End Sub
Private Sub Child_Update()
Using ctx = ContextManager(Of LinqDAL.IIRTrackerDBLinqDataContext).GetManager(LinqDAL.Database.IIRData)
Dim data = (From o In ctx.DataContext.IIRSources Where o.Id = ReadProperty(IdProperty) Select o).Single
With data
.Name = ReadProperty(NameProperty)
.IsDefault = ReadProperty(IsDefaultProperty)
End With
ctx.DataContext.SubmitChanges()
End Using
End Sub
Friend Sub Child_DeleteSelf()
Using ctx = ContextManager(Of LinqDAL.IIRTrackerDBLinqDataContext).GetManager(LinqDAL.Database.IIRData)
Dim data = (From o In ctx.DataContext.IIRSources Where o.Id = ReadProperty(IdProperty) Select o).Single
ctx.DataContext.IIRSources.DeleteOnSubmit(data)
ctx.DataContext.SubmitChanges()
End Using
End Sub
#End Region
End Class
What I've tried so far:
-
I couldn't see anything in the book for allowing optional deleting of child items.
-
I first thought I could override Remove(object) on the parent SourceList, but the only thing I can do is override RemoveItem(integer) which isn't the same.
-
Then I wondered if I could override Delete() on the child SourceListItem, but this method is never called:
Public Overrides Sub Delete()
CanExecuteMethod("Delete", True)
MyBase.Delete()
End Sub
for completeness here is the CanExecuteMethod:
Public Overrides Function CanExecuteMethod(ByVal methodName As String) As Boolean
If methodName = "Delete" Then
Return False ' just to get it to work ;-)
End If
Return MyBase.CanExecuteMethod(methodName)
End Function
-
I then tried adding a validation rule which I thought would run when the item is saved:
Private Shared Function CanDeleteRule(Of T As SourceListItem)(ByVal target As T, ByVal e As Csla.Validation.RuleArgs) As Boolean
If target.IsDeleted AndAlso SourceUsed.SourceUsed(target.ReadProperty(IdProperty)) Then
e.Description = "Source used in at least one IIR and cannot be deleted."
Return False
End If
Return True
End Function
but what property do I bind it to? I tried binding it to IsDeleted which didn't work and then tried calling CheckRules() in an overloaded Save() but that doesn't work either. :(
-
The only thing that has worked to far is to do:
Friend Sub Child_DeleteSelf()
If SourceUsed.SourceUsed(ReadProperty(IdProperty)) Then Throw New InvalidOperationException("Cannot delete a source if it used elsewhere.")
SourceUsed is a working and tested CommandBase:
Imports Csla
Imports Csla.Data
<Serializable()> _
Public Class SourceUsed
Inherits CommandBase
#Region " Authorization Rules "
Public Shared Function CanExecuteCommand() As Boolean
Return True
End Function
Private _id As Integer = 0
Private _used As Boolean = False
Public ReadOnly Property Used As Boolean
Get
Return _used
End Get
End Property
#End Region
#Region " Factory Methods "
Public Shared Function SourceUsed(ByVal id As Integer) As Boolean
Dim result As SourceUsed = DataPortal.Execute(Of SourceUsed)(New SourceUsed(id))
Return result.Used
End Function
Private Sub New(ByVal id As Integer)
_id = id
End Sub
#End Region
#Region " Server-side Code "
Protected Overrides Sub DataPortal_Execute()
Using ctx = ContextManager(Of LinqDAL.IIRTrackerDBLinqDataContext).GetManager(LinqDAL.Database.IIRData)
Dim data = (From o In ctx.DataContext.IIRs Where o.SourceId = _id)
If data.Count > 0 Then _used = True
End Using
End Sub
#End Region
End Class
Other forum posts I tried however gave no joy:
This got close, suggesting a CanExecuteMethod which asks if it can be deleted (just for the UI developer - not implemented in object rules), but doesn't really do a Validation Rule - is this really the best way?
http://forums.lhotka.net/forums/p/7134/34125.aspx