StateMachine flow in a master-detail model
I need to ask a complicated scenario, so I'll try to explain by example.
Consider the following model :
public enum States { Created, Approved, Started, Completed }
public class Request {
States State {get; set;}
IEnumerable<RequestLine> Lines {get; set;}
}
public class RequestLine {
States State {get; set;}开发者_高级运维
Request Request {get; set;}
IEnumerable<WorkOrder> WorkOrders {get; set;}
}
public class WorkOrder {
States State {get; set;}
RequestLine RequestLine {get; set;}
IEnumerable<WorkOrderAction> Actions {get; set;}
}
public class WorkOrderAction {
States State {get; set;}
WorkOrder WorkOrder {get; set;}
}
So every record has a State, and they're all connected. When a WorkOrderAction is created I have to update WorkOrder state, then RequesTLine state, and then Request state. For every WorkOrderAction record I should check other children records State and update the parent record.
I can do this using database triggers, which I don't prefer. I use Devexpress XAF on application level so I can also code some logic there. But still I can't decide which approach is better.
Is there a common concept for linked state machines?
We only need to know the "other incompleted children".
- We can enumerate all them: foreach(parent.Children) if(child.State != Completed).However this will load them all so there would be a performance hit
- We can perform roundtrip to database using something like Execute(new BinaryOperator("State", Completed, NotEquals), AggreagateOperand.Count). This requires restructuring of the domain and the performance hit is less than the previous one.
- We can update the parent's 'IncompletedChildren' member (integer) when each children is saved/deleted/removed/added. A Parent object is always loaded so we can determine that the parent is Completed when "IncompletedChildren == 0", mark it as completed and force completion to its parent recursively. There is no additional loading to traffic (one 'integer' is not a "loading" imho). However this approach forces "OptimisticLoading" increment for each 'IncompletedChildren' update - it makes this approach unusable in some scenarios.
I would recommend the 2nd approach.
PS: In our projects/tasks system we have introduced calculated fields with expressions "Items[State <> Completed].Count. However this cannot work in reality. Most of the times at least in our system the Completed state didn't meant that everything is completed :)
The common practice doesn't really have anything to do with state machines. That is, state machines aren't special when it comes to enforcing data integrity rules.
If it has to work right for all users, then it makes sense to implement it in triggers. Only code controlled by the dbms can enforce data integrity rules for all users. (And all users includes the sleep-deprived DBA and her command-line tools.)
Not entirely sure I understand the question, but if you're asking about enforcing data integrity "cascading" (a cascading update), then I would consider setting up "deferrable" constraints, which will defer checking the constraint check until a commit is issued. You'd then do your updates to parent/child as needed and then commit.
Here's a link regarding deferrable constraints from Oracle.
精彩评论