Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Continuous LINQ


Kevin is a Research Developer at Liquidnet, an equities trading system, where he has been researching Web 2.0, social software, and .NET 3.0. He can be contacted at [email protected].


If you haven't been paying a lot of attention to the barrage of new Microsoft technology, LINQ is one of the key new features of the .NET Framework 3.5. LINQ, short for "Language Integrated Query," lets you express filtering, sorting, grouping, and aggregation tasks using your language of choice with a syntax that looks pretty familiar to anyone with experience writing SQL queries for relational databases.

No matter what kind of application you're writing or what industry the application is for, there are some basic tasks that almost every programmer needs to do. At some point, you will likely have a list of data you need sorted, filtered, grouped, or aggregated in some fashion. Before LINQ, this involved the tedium of creating a loop to iterate through all of the list items, then adding the items that match a filter to the output collection, making sure to preserve the proper sort order. Using LINQ, you can now write statements that look like this, dramatically simplifying the code:


var results = 
  from customer in CustomerList
           where customer.Age > 21
           orderby customer.LastName
              select customer;

Through the magic of the language extensions feature in .NET 3.5, this query will essentially turn into something that looks like this:


IEnumerable<Customer> results =
  CustomerList.Where( f => f.Age > 
    21).OrderBy( o=> 
      o.LastName ).Select( s => s);


Don't worry if this syntax looks ugly to you—that's the entire purpose behind embedding language shortcuts for LINQ queries directly in C# and VB.NET. What you see in the preceding code sample are language extensions and lambda functions. The Where language extension to IEnumerable<T> lets you filter pretty much any list, so long as the items in the list implement IEquatable<T>.

On its own, LINQ is a fantastic addition to the arsenal of any developer writing virtually any kind of application. The true power of the .NET Framework 3.5 comes with your ability to enhance the functionality of LINQ using language extensions. Language extensions let you create your own overridden implementations for Where, OrderBy, GroupBy, and the like. Continuous LINQ (CLINQ) is just one sample of such an extension to LINQ that was created using this facility.

When building database applications, the general model is you write a query to obtain some data. Data can then be modified and then sent back to the database. In general, however, you do not expect to get continuous updates from the database as data in the underlying tables change. With most relational databases, the performance hit from doing such a thing would bring the database to a halt and probably impact the application as well.

Other applications need to be quickly updated in response to continuously changing data. An example from the financial industry would be an application that listens to market data ticks. Using the current implementation of LINQ, you could write a query like this:


var tickData =    from tick in MarketData.Ticks
         where tick.Symbol == "AAPL"
         orderby tick.Ask descending
               select tick;

The problem with this implementation is that tickData is completely stale. Immediately after you run the query, tickData will never grow or shrink dynamically on its own. It would be nice if the contents of tickData could continuously update every time new market data is added to the original source of the query, MarketData.Ticks. This is what the Continuous LINQ extension does.

Continuous LINQ works by attaching listeners, or adapters, to the source collection when the Where, OrderBy, and GroupBy language extensions are invoked. At some point in the future, it might support other extensions as well, but these were the most important. The adapter's job is to listen to changes to the source collection, including all items in the source collection, and decide if that change needs to be propagated to the destination collection. For example, using the preceding market-data query, if MarketData.Ticks was derived from ContinuousCollection<T>, then adapters would be created that listen to the source data. If a new item is added to the source collection that has a symbol name of "AAPL," then the item is automatically added to the results. These adapters also maintain sort order and can be nested and chained at will. Figure 1 illustrates the flow of data through adapters from a typical CLINQ query.

[Click image to view at full size]

Figure 1: Data flow through CLINQ adapters.

You can extend any IEnumerable<T> derivative class and provide your own domain-specific implementations for the where, orderby, and groupby LINQ query operators. Listing One illustrates the where and orderby extensions to a class called ContinuousCollection<T>. This class is really nothing more than a thread-safe derivative of ObservableCollection<T> that ensures that all change publications take place on the main GUI dispatcher thread to let all WPF GUIs and CLINQ adapters see the changes.

public static class ContinuousQueryExtension
{
  #region Where
  public static ContinuousCollection<T> Where<T>(
      this ContinuousCollection<T> source, Func<T, bool> filterFunc) where T: IEquatable<T>
  {
      Trace.WriteLine("Filtering Observable Collection.");
      ContinuousCollection<T> output = new ContinuousCollection<T>();
      FilteringViewAdapter<T> fva = 
        new FilteringViewAdapter<T>(source, output, filterFunc);

      return output;            
  }
  #endregion
  #region OrderBy
  public static ContinuousCollection<TSource> OrderBy<TSource, TKey>(
      this ContinuousCollection<TSource> source, Func<TSource, TKey> keySelector)
      where TSource : IEquatable<TSource>
      where TKey : IComparable
  {
      Trace.WriteLine("Ordering Observable Collection (Ascending).");
      ContinuousCollection<TSource> output = new ContinuousCollection<TSource>();
      SortingViewAdapter<TSource, TKey> sva = new SortingViewAdapter<TSource, TKey>(
          source, output,
          new FuncComparer<TSource, TKey>(keySelector, false));
      return output;                      
  }
  #endregion
  #region OrderByDescending
  public static ContinuousCollection<TSource> OrderByDescending<TSource, TKey>(
      this ContinuousCollection<TSource> source, Func<TSource, TKey> keySelector)
      where TSource : IEquatable<TSource>
      where TKey : IComparable
  {
      Trace.WriteLine("Ordering Observable Collection (Descending).");
      ContinuousCollection<TSource> output = new ContinuousCollection<TSource>();
      SortingViewAdapter<TSource, TKey> sva = new SortingViewAdapter<TSource, TKey>(
          source, output,
          new FuncComparer<TSource, TKey>(keySelector, true));
      return output;             
  }
  #endregion
}
Listing One

In the constructor of the adapters, the adapter attaches event handlers for the collection-changed events and the property-changed events for each item in the collection. Then, in response to these changes, the adapter determines if the changed item needs to be propagated to the destination collection or if it needs to be removed from the destination collection (for example, a modified item no longer satisfies the query predicate and needs to be removed). The removal will cascade down the chain so it makes no difference how many where, groupby, or orderby adapters the change needs to pass through.

The great thing about CLINQ and other language extensions enabled through LINQ is that all of the complexity is hidden from developers. You don't need to look at Listing One to use the advanced functionality—simply write a LINQ query against a ContinuousCollection instead of a standard list and it "just works."

There are lots of different samples that can be written to illustrate CLINQ in action, but one of the more applicable samples I've found is an Order Book. An order book application monitors the market data for a select subset of symbols. Without CLINQ, I would have to set up manual monitoring threads that listen for changes to the underlying market data and then manually propagate those changes to the model, which is then bound to the GUI. This creates a lot of moving parts, makes debugging difficult, and can be very error-prone. With CLINQ, I can simply set up some queries in the controller code and everything is dynamically updated as needed. For example, in the demo order book application, to open a new book and create a variable that contains just the subset of market data belonging to a particular symbol, you can use a simple CLINQ query (a LINQ query against a ContinuousCollection). Listing Two shows the controller method SubscribeToSymbol, which creates a new window and sets the AskTicks and BidTicks properties of that window to CLINQ queries.

public void SubscribeToSymbol(MarketSymbol symbol)
{
    if (!ModelRoot.Current.SubscribedSymbols.Contains(symbol))
    {
        ModelRoot.Current.SubscribedSymbols.Add(symbol);
        MarketDataBook newBook = new MarketDataBook(); // WPF Window
        newBook.Symbol = symbol;
        newBook.AskTicks =
            from tick in ModelRoot.Current.MarketData
            where tick.Side == TickSide.Ask &&
                  tick.Symbol == symbol
            orderby tick.Price descending
            select tick; // SELL
        newBook.BidTicks =
            from tick in ModelRoot.Current.MarketData
            where tick.Side == TickSide.Bid &&
                  tick.Symbol == symbol
            orderby tick.Price
            select tick; // BUY
        _monitorBooks.Add(symbol, newBook);
    }
}
Listing Two

Once the AskTicks and BidTicks properties have been set, those collections will be automatically updated as more market data streams in, without any further effort on the part of the programmer. This is a powerful way of dealing with message processing and streaming data.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.