Bindable LINQ: Three categories of queries
Update: SyncLINQ has been released under the name Bindable LINQ, and is available from CodePlex
Yesterday I blogged about Real State and Derived State in desktop applications. There were three ideas I put forward:
- All applications have derived state. Even manually adding controls to a form at runtime based on some data creates derived state.
- Most of the time, especially with LINQ, converting real state into derived state is a one-off operation.
- Keeping derived state in sync with real state is the cause of most application complexity, the most bugs, and requires a lot of code.
SyncLINQ exists to make it easier to create derived state, and more importantly, to keep it synchronized with the real state that it was created from.
Today I’ll outline the three categories of SyncLINQ queries which help with the management of derived state. I’ll also outline some of my plans around dependency management, and give some examples around how they can be put together.
Iterators
Iterators convert one or more collections of objects into a derived collection of objects. Examples of iterator queries are:
- Where
- Select
- OrderBy
- Union
- GroupBy
These queries exist in LINQ as well as in SyncLINQ. The difference between them is that the LINQ versions return an object which implements IEnumerable<T>, while the SyncLINQ versions return objects implementing both IEnumerable<T> and INotifyCollectionChanged.
In SyncLINQ, if the source collection raises collection changed events, SyncLINQ will automatically process the changes and raise the events again, keeping the derived state in sync with the real state.
Aggregates
Aggregates convert one or more collections of objects into a single derived object. Most aggregates result in numeric values, but they can be other types. Examples of aggregates are:
- Sum
- Min
- Max
- First
- Last
- Contains
Most of these aggregates exist in LINQ. While a LINQ aggregate returns the value directly, say, as an int, The SyncLINQ equivalent returns an IBindable<int>. The IBindable<T> interface does two things:
- Has a
Valueproperty, which contains theint. - Can be modified when the source changes
- Implements
INotifyPropertyChanged, and raises aPropertyChangedevent if the value does change.
Using SyncLINQ aggregates works a little differently. Instead of using the resulting value directly as with LINQ, you use the IBindable<T> container as your DataContext, and the Value as the property to bind to, for example:
<TextBlock Text="{Binding Path=Value}" />
IBindable<decimal> salesTotal = _orders.Sum(o => o.Quantity * o.Price);
this.DataContext = salesTotal;
Again, this keeps the derived state synchronized with the real state.
Operators
Operators convert a single object into a derived object. Some examples of operators are:
- If
- Transform
These queries have no counterpart in LINQ, so I should further explain what they do:
The If operator takes a value and a lambda describing a condition, and returns an IBindable<bool> indicating the result. For example:
IBindable<bool> requiresApproval = salesTotal.If(s => s > 5000M);
Notice that salesTotal is the IBindable<decimal> container from the previous example, and the result is an IBindable<bool>. If the sales total changes because a new Order is added, the Sum operation notifies that the sales total has changed, and the If operation then re-evaluates and can raise PropertyChanged events to indicate the requiresApproval value has changed.
If the requiresApproval value were bound to a UI element’s background color somewhere with a converter, the UI element’s color could change from green to red as soon as a new order is placed - without any extra code to keep this derived state in sync with the real state.
The Transform operator takes a value and projects it into another value, similar to the Select iterator, but for a single object. Again, the result is an IBindable<T>, so if the source object changes, the value can be transformed again.
Dependencies
With any query, a number of triggers can cause the derived state to be re-evaluated. Most of the time, these triggers come from the source elements themselves via CollectionChanged or PropertyChanged events. However, the query may rely on objects which are outside of the source of the query.
The WithDependency extension, and its overloads, allow you to tell a SyncLINQ query to re-evaluate part or all of the derived state based on changes to an external object or objects. There are a number of overloads:
1: WithDependency(string) 2: WithDependency(TResult, string) 3: WithDependency(Func<TElement, TResult>, string propertyName) 4: WithDependency(DependencyObject, DependencyProperty) 5: WithDependency(TCollection) 6: WithDependency(Func<TElement, TCollection>) 7: WithDependency(TCollection, Func<TElement, bool>)
The first four overloads allow you to say “this query depends on this object’s ‘foo’ property”. If the object raises a PropertyChanged event, the query will re-evaluate that source object.
The second three relate to collections. If the collection raises a CollectionChanged event, the derived state can be re-evaluated in the most efficient way to keep it in sync with the real state.
Consider the following example:
contactList.ItemsSource = _contacts
.Where(c => c.Name.StartsWith(textBox1.Text)
.WithDependency(textBox1, TextBox.TextProperty);
This query filters contacts by the text entered in a TextBox. The WithDependency call says that should the TextBox’s Text property change, the query needs to be re-evaluated. This is all you would need to filter a list of items via a TextBox in SyncLINQ.
Going back to yesterday’s problem with the Underscheduled Orders filter and the things that could affect it, the entire solution can be expressed like this:
treeView.ItemsSource = _orders
.Where(o =>
o.ScheduleDays
.Sum(d => d.HoursScheduled)
.If(d.Value < o.RequestedDays)
.WithDependency(o, "RequestedDays")
)
.GroupBy(o =>
o.Customer.Name)
.WithDependency(o => o.Customer, "Name");
The schedule entries are aggregated, and the value is compared to the number of requested days. The entries could change (resized on the canvas), the requested days could change, and the customer name could change; but the TreeView will always be synchronized. The derived state is always synchronized with the real state.
Summary
SyncLINQ’s job is to enable derived state to be synchronized with real state, with a minimal amount of code. When I look at code I have written in the past and consider how much of it can be thrown out thanks to SyncLINQ’s bound nature and dependency system, my mind drools at the thought of replacing it.
In terms of right now, I have just implemented the first set of aggregate extensions, and I have a basic dependency system working. Over the coming week I’ll be working to implement an operator, and refactor much of the dependency system to support the overloads above.
All applications have to deal with derived state, even if the developers don’t realize it at first. I expect SyncLINQ can provide value out-of-the-box to any application.
Technorati tags: synclinq, binding, iterators, aggregates, operators
Filed under: Bindable LINQ

[…] Update: Read the three Categories of SyncLINQ Queries […]
Hi,
I see two major problems:
#1 performance; consider things like BeginUpdate/EndUpdate to avoid flickering
this approach does not compose!
#2 bijections; a LINQ query is usually a projection (sometimes with join), so it may not be updateable. To work around that, one has to explicit all transformations (in either direction) ending up with a network of possible states depending on each other. This may lead to infinite update storms.
One may control that by choosing State Machine Pattern.
As implementation of state transitions Your library would indeed simplify the life of the programmer, as it moves from explicit notation to declarative notation.
I’ll be interested in Your results. Keep on going.
- Martin
Paul,
Mabye you can create an overload of WithDependency that uses expression trees so we don’t have to use strings to refer to properties?
The signature would be: WithDependency(Expression>)
It would allow us to write things such as WithDependency(o => o.RequestedDays) and WithDependency(o => o.Customer.Name)
Just an idea. It may come with issues.
Fabrice
What I suggest above is similar to the technique used by LINQ to SQL’s DataShape.LoadWith()
Pingback from http://oakleafblog.blogspot.com/2007/11/link-and-entity-framework-posts-for.html
–rj
Hi Paul,
with SyncLINQ you’re creating a great library!
).
Although SynLINQ is still in Alpha stage, I’d like to start using SyncLINQ in my application (I’m an early adopter
And I’d like to start using IBindable in the following scenario:
from person in applicationState.User.PersonsWhoAdministerMe
select Process(person)
public class Person
{
IBindableCollection PersonsWhoAdministerMe
{
…
}
…
}
In this example:
* property ‘User’ would be a IBindable,
* PersonsWhoAdministerMe returns an IBindableCollection.
* Process(person) returns a user interface object that represents the person.
What I’d like to implement is the following scenario:
1. The value of property applicationState.User changes (to a different Person).
2. then User.PersonsWhoAdministerMe is reevaluated
3. then the “select Process(person)” statement (which returns a ISyncLinqCollection) fires a CollectionChanged event,
so that the user interface of the application is updated.
Well, the code above would not work, because property ‘User’ is an IBindable, which does not have a property called
‘PersonsWhoAdministerMe’.
Instead I would need:
from person in applicationState.User.Transform(person => person.PersonsWhoAdministerMe)
select Process(person)
However, in the SynLinq sources I’m missing the following classes, which you described above:
* the If and Transform operators.
* WithDependency extension method (I noticed that there is no WithDependency method in Extensions.cs and that everything in
ICanHaveDependencies.cs is commented out).
Have you implemented these methods already?
If so, could I get the source code (even if it is a work in progress and even if the interface might change significantly later)?
Also about WithDependency: have you considered ‘parsing’ the Lambda expressions (used in e.g. Where and Select) to extract dependencies
instead of using an explicit WithDependency method?
Thanks a lot,
Arjan Burggraaf
Hi Arjan,
WithDependency was still in the idea stage when I posted that (as well as the operators). I am actually implementing a simple form of WithDependency this week.
I’m glad to hear you would consider adopting it. Please contact me via the contact form on this site - I’d like to discuss this further with you.
Paul
Hi Paul,
it would be great to discuss this further with you. I’m working on some extra functionality too.
But where do I find the contact form?
Arjan
Hi, great code. I currently evaluating if I will use your librairy in my developpement projet. I look in your source code, but I can’t understand well all the process. Do you plan a well detail documentation explaining all part of your source lib ? I am afraid of using a open source code in a developpement project if i don’t undestand well the process in case of your are out of business for a while! Your lib are stable now ?
Another thing in your sample i see a menu that point to a sample called SyncLinq over Linq-to-SQL. I am very interested in this sample becaus this is what I will do. Do you have this sample ready somewhere?
Thanks a lot
[…] first wrote about my thinking behind dependencies here. I still plan to allow the addition of external dependencies (as that post suggested), but most can […]
[…] query operators in SyncLINQ are designed to allow exactly that. While LINQ takes operations on collections and turns it into a […]
[…] Iterators: […]