How CLINQ Works

Before discussing how Continuous LINQ is possible, we should take a quick look at how LINQ is possible. LINQ queries are made possible through some syntactic magic that was put into the .NET Framework v3.5.
Keywords such as where, select, from, orderby, and groupby are converted from "natural" query syntax, which is the SQL-like syntax that most of us are familiar with,
into function calls that make extensive use of lambdas and language extensions to add these methods to objects of type IEnumerable<T>, etc.

For example, the following natural syntax LINQ query:
    var q = from cust in rawData.Customers
                where cust.Age > 21
                orderby cust.Name
                select cust;


will be re-written in language extension syntax by the compiler to look more like this:
    var q = rawData.Customers.Where( cust => cust.Age > 21).OrderBy( cust => cust.Name ).Select ( row => row );

Extending LINQ with Language Extensions

If you want to inject your own code into the query pipeline that operates on objects of a particular type, then all you really need to do is implement your own language extensions on that type, provided those extensions match the signatures of the default LINQ extensions.

The goal of Continuous LINQ is to ride on top of the normal LINQ queries. Whenever you issue a LINQ query against a special type (e.g. a ContinuousCollection),
CLINQ will inject listeners into the source that will propagate applicable changes to the output result set.
This is what allows you to write a query against a list of customers that only contains customers over age 21 that continuously updates itself when customers are added to and removed from the original source query, as well as when the age of customers is modified.

Here is an example of one of the many language extensions that drives CLINQ.
This is an extension that adds a Where method on an input collection wrapper used for CLINQ housekeeping (it wraps ObservableCollection<T> instances).
Note how a new CLINQ collection is created for the output and a FilteringViewAdapter<T> is attached to the input.
This behavior that we call adapter chaining is key to allowing CLINQ to function properly.

 private static ContinuousCollection<T> Where<T>(
            InputCollectionWrapper<T> source, Func<T, bool> filterFunc) where T : INotifyPropertyChanged
        {
            Trace.WriteLine("Filtering Observable Collection.");
            ContinuousCollection<T> output = new ContinuousCollection<T>();
            new FilteringViewAdapter<T>(source, output, filterFunc);

            return output;            
        }


When more than one LINQ query segment exists, the output from one method call is passed as the input to the next language extension in line.
For example, in the following query:

    var q = from order in rawData.Orders
          where order.Price > 500.00
          orderby order.VendorID desc


Two adapters will be created. The Where method will be invoked, which will attach a FilterViewAdapter<T> to the source collection.
The output of that (which is dynamically updated by the FilterViewAdapter<T> in response to source events) will be passed as the input to the OrderBy method.
The OrderBy method will attach a SortingViewAdapter<T> to the input (which is the output of the filter, remember), and produce another CLINQ collection
as output.
This adapter chaining gives us nearly infinite flexibility and allows virtually any standard LINQ query to become continuous just by virtue of having the CLINQ namespace in scope or by querying directly against a ContinuousCollection<T>.

Last edited Feb 13, 2008 at 2:02 PM by DotNetAddict, version 8

Comments

DotNetAddict Mar 16, 2009 at 3:35 PM 
This is actually some old code.... but what used to happen when creating a new instance of the FVA was that the constructor for that class would create event listeners on the source... which meant that the FVA wouldn't be GC'd until the source was GC'd (another source of problems, actually).. but yes, we intended to not use the return value from the FVA creation.

Have a look at some of the 2.0 code for how we're doing things now.

IDisposable Oct 10, 2008 at 4:19 AM 
I don't get how that bare new FilteringViewAdapter<T>(source, output, filterFunc) (which isn't assigned to anything) is supposed to work on the second-to-last code example... are you missing some code?