|
In a previous blog post, I briefly discussed some points about dynamic, updating LINQ views. In short, that blog post mentioned that LINQ is an incredibly powerful tool, but there is room for improvement. Basically LINQ allows you to query relational databases through tools like LINQ to SQL or LINQ to Entities and you can query any in-memory data source that implements IEnumerable<T>. This is extremely powerful and can dramatically increase the productivity of many programmers. The problem is that the result sets returned by LINQ are stale and disconnected. LINQ to SQL and other supersets of LINQ get around this limitation by giving you methods that allow you to push changes back to the original source. This is a perfect model for database programming because that's pretty much what 99.9% of all DB activity boils down to : query for data, suggest changes to the database, commit changes.
The problem is that there are a lot of applications that don't fit that model. There are applications where you want to start a query against a source, but then you want your result set to be automatically updated when changes occur in the source that affect the query result set. This is especially true if you are binding your result sets to a GUI where you want the GUI to automatically change when the source from which your query was generated is modified.
This is where Continuous LINQ comes in. Based on the work done in Streaming LINQ (SLINQ), CLINQ takes a slightly different approach. The main purpose of CLINQ is to allow for GUI-bound dynamic data viewing. This means that instead of inventing my own notification classes, everything I do comes from INotifyPropertyChanged and INotifyCollectionChanged (which I get by deriving my custom class, ContinuousCollection, from ObservableCollection). Where SLINQ uses a polling timer to inspect for changes, Continuous LINQ pushes changes directly from the source all the way up through the filters, sorts, and groups up to the GUI without any polling timers.
All of LINQ is made possible through the use of language extensions. When you do a standard LINQ query that does a filter and a sort, you're actually calling the .Where() language extension method to IEnumerable<T>. The output of that is then sent to the .OrderBy() language extension method to IEnumerable<T>.
This is the basic plumbing that makes CLINQ possible. By extending ContinuousCollection (Essentially an ObservableCollection of items that must implement INotifyPropertyChanged), I can write my own Where(), OrderBy(), [optionally ThenBy()], and GroupBy() methods.
Take a look at the following diagram for an illustration of how CLINQ works:

As each extension method is called, a different adapter is attached. These adapters listen for changes and then pass them further up the chain without requiring any foreknowledge of what might be on the other end of the chain. This is what allows you to write filter-only or sort-only or filter-group-and-sort type queries without having to use special one-off cases in the code.
Let's take a look at a sample query:
var query = from cust in Customers
where cust.CustomerKey >= 12
group cust by cust.Age into customerGroups
orderby customerGroups.Key descending
select customerGroups;
What happens here is the .Where() method is called. A FilteringViewAdapter is attached to the source collection (Customers) and to the output list (a new instance of ContinuousCollection). The list is initially filtered and then continuously filtered thereafter. This means that when an item is added to the Customers list, the special adapter will hear about it and evaluate the new item against the predicate as follows:
if (_predicate(newItem)) output.Add(newItem);
(Yes, lamba functions kick ass...). Additionally, if any property of any element within the source collection changes, that element is re-evaluated for membership in the output collection. For example, if you're querying for all students with a passing grade, and some background thread changes Dave's grade from an F to an A, then your GUI will be immediately aware of the fact that Dave is now in the output collection.
Next, the output of the .Where() language extension (the new instance of ContinuousCollection) is then sent to the next extension method evaluated by the LINQ parser in C#. In the case of our sample query, that's the GroupBy() method. This method returns an IGrouping<>, which basically is a collection of IEnumerables, where each IEnumerable also has a property called Key. In my case, the output of the extension returns a GroupedContinuousCollection, which just inherits from ContinuousCollection and adds the Key property. Here, an adapter is attached to the input (which is the output of the filter clause) that continuously monitors changes to see if items need to change group memberships. For example, if a background thread changes Dave's grade from an F to an A, and you're grouping by student.Grade, then Dave's group membership should be changed. If you're using CLINQ, then your GUI will be automatically updated showing the new group membership (currently I have some thread synch bugs related to grouping, but those will be hammered out shortly).
Finally, the OrderBy() extension method is called. This attaches a SortingViewAdapter which is responsible not only for the initial sort, but for moving items within a list to make sure that when existing items are modified they are placed in the appropriate sort position and when new items are added, they are added at the correct spot relative to the sort order.
Personally, I think that the in-memory models of larger apps that are taking advantage of Model-View-Controller and Model-View-Presenter are going to all be built on technology like LINQ in the future. I think a huge amount of those applications will be able to take advantage of stuff like CLINQ in ways that people might not have expected. There are a lot of potential places where defining queries at design time that act as dynamic, run-time filters/sorters/groupers can dramatically increase developer productivity and potentially even app reliability and scalability.
As soon as I've figured out a way to distribute this thing, and I've ironed out the grouping kinks, I'll let some more of the source code out.
Distributing it....now that's the kicker.