I had a pretty common problem with regards to inserting into database tables. As you probably know, depending on your data and foreign keys, you need to insert data in a specific order, for instance, you first need to insert invoice record, and only after that you can insert invoice items.
This problem has been already solves so many times, and in different ways (some ORMs inspect your tables and determine dependencies and what goes first, others rely on you to define the order by defining dependencies, and some of them just leave it to you to do the updates in a proper order).
I faced this problem before, when developing an SPA, and the problem with SPAs is that you may not necessarily do DB operations when doing the UI operations (which would mean you force operations in a particular order), instead doing a Flush after you are done editing an invoice (for instance) and sending a set of data which need to be updated or inserted.
To make matters worse, if you code generate your data it can get pretty complicated, or you just do the whole thing with custom coding, circumventing that nice smart code you spent so much time generating.
It got me thinking on how to go about it and force a particular execution order, and i decided to make a custom class which would do it.
Some code samples:
public static void Save(this Template myTemplate) { foreach (TemplateField subTemplateField in myTemplate.TemplateFields) { subTemplateField.TemplateGuid = myTemplate.TemplateGuid; result += subTemplateField.Save(); } foreach (TemplateTable subTemplateTable in myTemplate.TemplateTables) { subTemplateTable.TemplateGuid = myTemplate.TemplateGuid; result += subTemplateTable.Save(); } }
I needed that TemplateTables do a Save before TemplateFields, since TemplateFields hold a reference to TemplateTables. The problem is that this code is generated, so if i wanted to change the order of execution, i would have to copy all this code, modify it and keep it updated whenever i code generate again. Very very daunting.
So i thought of a ExecOrder class. How does it work?
I have this factory method which gets the proper ExecOrder object for a proper class and method:
public ExecOrder GetExecOrder(string classMethodName) { switch (classMethodName) { case "TemplateService.Save": return new ExecOrder("TemplateTables"); } return new ExecOrder(); }
See that “TemplateTables” argument? It tells execOrder that should be the first code that needs to be executed, and the rest of the code sections can execute after that.
If you need a particular order for 3 code snippets (say “TemplateTables”, “TemplateFields” and then “TemplateSubFields”) you would initialize it with
return new ExecOrder("TemplateTables","TemplateFields");
The third one is omitted because you don’t really need it in the argument list if you just wanted it executed AFTER the first 2. So how do you apply it to the previous code?
public static void Save(this Template myTemplate) { var execOrder = GetExecOrder("Templates.Save"); while (execOrder.CheckDone()) { if (execOrder.Check("TemplateFields")) foreach (TemplateField subTemplateField in myTemplate.TemplateFields) { subTemplateField.TemplateGuid = myTemplate.TemplateGuid; result += subTemplateField.Save(); } if (execOrder.Check("TemplateTables")) foreach (TemplateTable subTemplateTable in myTemplate.TemplateTables) { subTemplateTable.TemplateGuid = myTemplate.TemplateGuid; result += subTemplateTable.Save(); } } }
The execOrder on first CheckDone always returns true, it makes a first pass through the code and updates it’s list on what needs to be executed and in what order. It adds to list the tokens that are not on it. It also executes the first command in list, and any other that is aligned with the list. It makes as many passes as needed to execute everything from the list in the correct order, but if something’s not in the loop, but it is on the list (a dummy token) it will skip it as well.
If the list is empty it will just execute everything as it comes. It is important to have all the code inside the loop tokenized (inside an if block), otherwise the code will be executed at every pass.
And there you have it, i’m not sure this kind of code falls into best practice (though often you don’t have much choice, so you make the most of it:), but it’s pretty good for controlling the flow and very robust (it tolerates additional and missing tokens). You could use it for firing triggers and events in a custom order (for different clients or uses), or whatever else you see fit.
And lastly, the code for ExecOrder:
public class ExecOrder { private List<string> _tokenOrder; private int _current = 0; private bool _isCompleted; public ExecOrder(params string[] tokenOrder) { if (tokenOrder != null && tokenOrder.Length > 0) _tokenOrder = tokenOrder.ToList(); } public bool Check(string token) { if (_tokenOrder == null) { _isCompleted = true; return true; } if (_tokenOrder.Count > _current && _tokenOrder[_current] == token) { _current++; _currentNotFound = false; return true; } if (!_tokenOrder.Contains(token)) { _tokenOrder.Add(token); } return false; } private bool _currentNotFound; public bool CheckDone() { if (_currentNotFound) _current++; _currentNotFound = true; return _isCompleted || (_tokenOrder==null && _current>0) || (_tokenOrder != null && _current >= _tokenOrder.Count); } }
Leave a Comment