Live Views How To: Use Live Views to Create Non-GUI Applications as a Set of Declarative Rules Instead of Procedural Code
In addition to making GUI applications declarative, LiveLinq also enables a programming style (which we tentatively call view-oriented programming) that makes non-GUI, batch processing declarative too. See How to use live views in non-GUI code for the introductory description of this technique.
The LiveLinqIssueTracker demo application contains a Batch Processing form where this technique is demonstrated by implementing two actions:
(1) Assign as many unassigned issues to employees as possible. Looking at the feature to which a particular issue belongs, find an employee assigned to that feature, and, if that employee does not have other issues for that feature, assign that issue to that employee.
(2) Collect information on all open (not fixed) issues for a given employee (perhaps for the purpose of emailing that list to that employee).
We perform action (1) by defining the following view (we are giving LiveLinq to DataSet versions of the views here, Objects and XML versions are similar):
_issuesToAssign =
from i in issues
where i.AssignedTo == 0
join a in _dataSet.Assignments.AsLive()
on new { i.ProductID, i.FeatureID }
equals new { a.ProductID, a.FeatureID }
join i1 in issues
on new { i.ProductID, i.FeatureID, a.EmployeeID }
equals new { i1.ProductID, i1.FeatureID, EmployeeID = i1.AssignedTo }
into g
where g.Count() == 0
join e in _dataSet.Employees.AsLive()
on a.EmployeeID equals e.EmployeeID
select new IssueAssignment
{
IssueID = i.IssueID,
EmployeeID = a.EmployeeID,
EmployeeName = e.FullName
};
and performing the operation with simple code based on that view:
foreach (IssueAssignment ia in _issuesToAssign)
_dataSet.Issues.FindByIssueID(ia.IssueID).AssignedTo = ia.EmployeeID;
Action (2) is performed by defining the following view:
from i in _dataSet.Issues.AsLive()
join p in _dataSet.Products.AsLive()
on i.ProductID equals p.ProductID
join f in _dataSet.Features.AsLive()
on new { i.ProductID, i.FeatureID }
equals new { f.ProductID, f.FeatureID }
join em in _dataSet.Employees.AsLive()
on i.AssignedTo equals em.EmployeeID
where i.AssignedTo == employeeID && !i.Fixed
select i.IssueID;
If we need to perform the operations (1) and (2) just once, then there is no point in using live views. But suppose we are writing a program that needs to perform those actions (1) and (2) many times as steps of the overall algorithm it is executing. This is a common case, especially in server-side programming.
Without live views, we would either need to requery each time, which is very costly, or we would need to create and maintain some collections throughout our processing algorithm. Those collections would need to be maintained by manual code, often complicated, and written by different programmers and often containing bugs. Different people write different functions (actions (1) and (2) are, of course, just two small examples of such functions), they need to know what others are doing if they want to keep it consistent. All this is hard to maintain. When a new action or function is added a year after that, the logic that connects everything is by that time forgotten, the new action breaks that logic, and so the vicious cycle of too complicated programming goes on.
Compare this with how it can be done with live views:
Actions (1) and (2) are not actually procedures, they are declarative rules. Rule (1) states that this view contains the assignments to be made. Whatever changes are made to our data, this view will always contain the needed assignments, regardless of anything we add a year from now or at any other time. The logic is guaranteed to be correct because it is a declarative rule, not procedural logic.
And that rule (1) works fast. We can perform that action a thousand times over data that is always changing, and every time it will recompute only the required amount of data, only the amount that is affected by changes. It will perform only the needed incremental recomputations.
Same with rule (2): it is a declarative rule, and it executes fast without repeating calculations, recalculating only those parts that are affected by changes.
If a substantial part of our algorithm is programmed in this view-oriented way, as a set of rules like (1) and (2), then they all work together, their consistency is automatically maintained. Ideally, we can represent our entire algorithm with views/rules like that. If not, a part of it can be implemented like that and the rest can be done with the usual procedural code.