The Task Parallel Library Series - Task Parallelism

So far, we've looked the the motivations behind concurrency and Data Parallelism as a technique to introduce concurrency into your code. We've also looked at some simple examples of Parallel.For() and Parallel.ForEach(). In this post we're going to explore the other main way that concurrency can be harnessed, Task Parallelism.

Data parallelism was all about executing the same block of code on many different data items. Task parallelism is about concurrently executing different blocks of code, perhaps operating on the same data, perhaps on different. What's important is that the tasks are, as far as possible, independent. If there are dependencies between them things will rapidly get more complex, and all those issues we discussed in data parallelism such as corruption and deadlocks will raise their heads.

Let's look at an example. Firstly, in the familiar sequential world:


public void OnlineStore()
{
	TakeOrder();
	SendOrderReceivedNotification();
	PrepareInvoice();
	SendInvoice();
	PickGoodsFromShelf();
	UpdateStockControl();
	PackGoods();
	ShipGoods();
	SendDispatchedNotification();
}

In here, we have a whole bunch of different methods (tasks, code blocks, call them what you want). Some are dependent on others, some aren't. Let's see how it could be parallelised (pseudocode only):


public void Amazon()
{
	TakeOrder();
	
	in parallel do
		=> SendOrderReceivedNotification();
		=> DealWithInvoicing();
		=> DealWithTheGoods();
}

public void DealWithInvoicing()
{
	PrepareInvoice();
	SendInvoice();
}

public void DealWithTheGoods()
{
	PickGoodsFromShelf();

	in parallel do
		=> UpdateStockControl();
		=> SendGoods();
}

public void SendGoods()
{ 
	PackGoods();
	ShipGoods();
	SendDispatchedNotification();
}

A bit more code, but still readable (I'd actually argue a bit more readable since the first method was, IMO, doing way too much stuff). Basically all we've done is divide the tasks into those that are dependent and those that aren't. For the independent tasks, we're using a new "in parallel do" construct to indicate to the language / runtime that these can be run on different threads. In the new code, the longest chain from start to completion is 5 steps compared to the 9 steps in the sequential code, so we should expect some speedup when run on a multi-core system.

How much speedup? Since we've dropped from 9 steps to 5, does that indicate that we should now be almost twice as fast? Clearly not, since each step is unlikely to have the same sequential execution time. Let me introduce Gene Amdahl who formulated the fundamental limits to what parallelisation could achieve. In essence, he stated that the maximum speedup that can be achieved through concurrency is limited by the sequential part of the program. That is, if you have a program that takes 10 hours to run and within that there is a task of 2 hours that cannot be parallelised, then the fastest that program can run, regardless of the number of processors that can be thrown at it, is 2 hours. Not exactly rocket science, but it earned the title of Amdahl's Law; go take a look if you want to see the maths (it's not hard). Data Parallelism enjoys the fact that the sequential parts can be very small, and so the possible speedups can be truly enormous. Task parallelism is more restricted, in that it's limited to how fast the longest (in time, not code) dependent chain of tasks can run.

If anyone's reading all this, are you spotting a theme yet? Concurrency is important (and will get more so), but dependencies are the problem. Dependencies introduce the bottleneck beyond which you're relying on the hardware to improve it's sequential execution speed to see improvements. And we've already seen that that's unlikely. Dependencies are also the thing that cause you to start dropping locks and other synchronisation mechanisms in your code, obscuring your "business rules" and introducing the risk of hard-to-test, hard-to-diagnose bugs. Loosely coupled, independent code is something that you should strive for, and immutable data structures assist greatly in this. I'm thinking that perhaps the "I" in SOLID could be re-purposed...

That should give an idea as to what Task Parallelism is all about. In the next post, we'll look at how to do this in .Net 4.0.

The Task Parallel Library Series - Parallel.For & Parallel.ForEach

In the previous post we discussed the principles behind data parallelism, in this one we'll show how to implement it in .Net 4.0. This will probably be a short post, since it's really simple. Here's a regular sequential loop:


var data = GetTenThousandElementArray();for (var i = 0; i < 10000; i++)
{
 DoSomeProcessing(data[i]);
}

And here's the parallelised version:


var data = GetTenThousandElementArray();Parallel.For(0, 10000, i =>
{
 DoSomeProcessing(data[i]);
});

Pretty simple stuff, and highly readable. Just switch from the C# for keyword to a call to the static For() method on the System.Threading.Tasks.Parallel type. In it's basic form, this method takes three parameters - the "from" value (inclusive), the "to" value (exclusive) and the loop body itself (shown as a lambda here, but an anonymous delegate or full-blown named delegate will also work just fine)1.

What about data that doesn't have a known range, such as an IEnumerable<T>, where you don't know how many values may be retrieved? In sequential code, you'd handle that with:


var data = SomeSourceOfIEnumerable();   foreach (var val in data)
{
 DoSomeProcessing(val);
}

To switch this to parallel execution, you can probably guess what you need to do:


var data = SomeSourceOfIEnumerable();   Parallel.ForEach(data, (val) =>
{
 DoSomeProcessing(val);
});

Again, the parallel version is pretty much identical to the sequential but the intent is quite clear. What you should notice from both examples is that we aren't dealing with threads. We aren't having to look at how many cores we have available, or coding up some algorithm for partitioning the data. We simply state the intent, and let the runtime take it's best shot at how to execute the code. And it's the runtime that has all the knowledge as to what state the environment is in right now, so arguably it's in the best position for making those decisions.

In the rare scenarios where it doesn't do it "right", or where you know something about your data source that could lead to a more optimal way of processing, there are various hooks available so that you can take on more responsibility; depending on how long this series gets and on whether anyone asks, we may look at some of them in later posts.

You may also be wondering how to handle exceptions, how to break out of the loop and how to cancel the loop. Those are all important things that are frequently required, and they also tend to mess the code up if you're doing it by hand. In future posts we'll examine each of these and show how it is achieved through the TPL.

As we discussed in the previous post, the examples above assume that the DoSomeProcessing method is itself thread-safe, which is ideally achieved by not accessing any shared state. That doesn't mean that you can't share stuff, just that each loop iteration needs to be careful not to tread on anyone else's toes. For example, this is fine:


var data = GetTenThousandElementArray();Parallel.For(0, 10000, index =>
{
 data[index] = data[index]*data[index];
});

Although each loop iteration is accessing the data array in parallel, each access is only dealing with a single distinct element. Since there is no concurrent access to a single memory location, this code is completely safe. Compare that with:


var sum = 0;
Parallel.For(0, 10000, index =>
{
 sum += data[index];
});

In this code, each iteration is accessing the single sum field and invalid results are likely2. Note that aggregate operations like this are a pretty common requirement, and there is a way to achieve this within the TPL which we'll look at soon. For now, I think that's enough. Next post will be on Task Parallelism, see you there.


1The observant amongst you (and ReSharper) will say that you can replace that lambda as follows:


Parallel.For(0, 10000, DoSomeProcessing);

Feel free to do that in your code, but for this series I'll stick with the longer version just to highlight the similarities with the sequential code.

2Only likely? Well, yes. Here is an example of the fun you can have testing concurrent code - if you write this code and run it, chances are it will work just fine. If you've got a single core machine (or a single core assigned to your VM), then the odds are in fact pretty good that it will run correctly. Why? Well, for it to produce invalid results, a thread context switch has to happen at just the right place. And you've got no control over that, it's all down to the Windows scheduler. What you can be sure of is that it will go wrong shortly after shipping; there's a guy called Murphy who can explain that to you! Later on in this series, I'll talk some more about testing and about the CHESS tool from Microsoft Research that can help out. One step at a time though.

The Task Parallel Library Series - Data Parallelism

One of the approaches that can be taken to introduce concurrency into your applications is through data parallelism. This approach distributes data across multiple cores, where each core executes the same instructions on different items from the data set.

As an example, consider some simple algorithm that processes the pixels of an image. You may have code something like this:


foreach (Pixel p in image)
{
 Process(p);
}

This is a regular sequential for loop which iterates over the image. What's interesting is that each iteration is independent of all the others (assuming that the implementation of Process does nothing odd). Given that, it seems reasonable to think that we could change the code in the following way (pseudocode only here):


foreach (Pixel p in image) in parallel
{
 Process(p);
}

Here, the "in parallel" directive instructs the language / runtime to run the loop over many processors, each executing the loop body with a different item from the input data set.

Code like this is known as "embarrassingly parallel" - due to the lack of interdependencies between each iteration of the loop, distributing this across multiple processors is pretty straightforward. There are some complexities to consider, such as exception handling and cancellation, but nothing too gnarly.

In terms of scalability, it doesn't get much better. Other than a little bit of overhead in the management of the parallel operations, it's easy to see that on a system with N cores, this approach can give an performance increase of N. Of course, things aren't quite as good as that in the real world and true linear performance gains are pretty unusual, but you will be getting a good return.

However, things can get considerably more complex if the iterations have some interdependencies. As a worse case, you could have a scenario where each iteration requires the results of the previous iterations - in this scenario, unless you can make some change to your algorithm, it would not be possible to parallelise the task at all. Where interdependencies do exist, make sure that you understand the implications of what you are doing. For example:


foreach (Pixel p in image) in parallel
{
 if (SomeOtherDependency.NotProcessed)
 {
  wait;
 }
 Process(p);
}

In this code, this loop body is blocking whilst some other dependency is completed. However, you have no knowledge of how the system is scheduling the work - although the number of cores is going to increase rapidly, it is likely for some time that the size of a typical data set will be larger (consider image processing - it's going to be a while until we have multi-million core machines such that each core could process a single pixel). Given that, the runtime will be partitioning the data set and providing each core with a subset of the data to work upon. What if, in the example above, the SomeOtherDependency had been scheduled to be processed on the same core core as the iteration that blocks. Deadlock, pure and simple. Depending on the runtime, you may be able to supply your own partitioning function to ensure that the necessary ordering is preserved, but as you go further down the complexity path, be sure to measure that what you're doing is actually providing a performance improvement over the straightforward sequential code.

For me, that's one of the golden rules. If you don't measure, you don't know. If your code starts to deviate away from the embarrassingly parallel approach, then you need to be sure that the synchronisation / locking logic that you are putting in place isn't actually destroying all the perceived performance benefits.

You will also quickly start to see the value of moving away from the mutable state model that we are all used to. If your state is immutable1, then removing any dependencies between loop iterations becomes much, much easier. You can't always be immutable (at least, not easily) but where you can, do so. I think you'll also find that regular sequential code becomes easier to understand and maintain once state stops mutating.

When looking to make parts of your application concurrent, data parallelism is almost always the lowest hanging fruit. It tends to be fairly easy to spot loops like this, and changing a loop to run in parallel has pretty much zero impact on the rest of your code base. In the next post, we'll look at the support in .Net 4.0 for doing precisely this.


1 for those not sure, an immutable type is one that can't be modified once created. The canonical example in .Net is System.String.
The Task Parallel Library Series - Introduction

One of the key parts of .Net 4.0 is the Task Parallel Library, a new set of types that provide a higher level of abstraction for concurrent programming than the previous System.Threading namespace.

This is the first of a small series of posts giving an overview of the library and how to use it; hopefully it will be of use to someone.

As a starter, we'll have a quick look at why this concurrency thing seems to be the fashion at the moment and what the problems are with the current (i.e. pre-4.0) tools. If you know this stuff, then just move along, I won't be offended.

Right, so why the interest in concurrency? Up until a few years back we lived in a world where Moore's Law was doubling the number of transistors on a die every couple of years. As a side-effect of this, the clock speeds were also doubling at about the same rate. For us sequential programmers this was pretty much a free ride - every 2 years, our systems either got faster or could do twice as much work in the same time without us lifting a finger1.

However, things changed. The ever-increasing clock speed was starting to cause issues, the biggest of them being heat generation. Without sophisticated cooling systems, there was no way the speeds could be raised any further without just frying the chip; laptops were starting to get hot enough to cause minor burns, and keeping a large datacenter cool was becoming a serious issue.

As a result, the unit of measure of performance started moving away from GHz (since that was a dead end) to instructions per watt. And it turns out the reducing the clockspeed by 50% gave around an 80% reduction in power usage - so 2.5 times as efficient, which is generally thought to be a good thing. Moore's Law also hasn't stopped - the number of transistors on a die continues to increase. So how are manufacturers using them? By making the processing units much simpler and running at lower speeds, but having many of them. Improved efficiency, but still increasing the number of operations that can be performed each second.

This was first seen in the consumer space with chips such as the Intel Core Duo and AMDs Athlon, each with two cores2. The latest to the party is the Intel Core i7, with up to 4 cores (plus 4 hyperthreading units). In the wings is hardware such as the Intel Larrabee card - initially targeted at graphics but now aiming more at HPC, this x86-based unit is likely to have around 50 cores on its first release. Although Larrabee is not currently focusing on consumer hardware, it is inevitable that this approach of many simple cores is the direction the hardware manufacturers are heading in.

What does this mean to us sequential software developers? Well, the news is not good. Instead of a doubling of performance every two years, it is likely that our software will actually slow down. That's right, it's going to get worse, not better. How do you think your customers will feel about that? Particularly when other software from your competitors is showing improved performance? I'm guessing that they won't like it too much.

So what's the solution? Concurrency. You have to make your software able to utilise these additional cores, and to scale with them as the number increases. Failing to do so will simply mean that you get outclassed by the competition. Concurrency is your silver bullet.

Well, that doesn't sound so bad. After all, there's System.Threading.Thread and System.Threading.ThreadPool. What more do I need? The problem with concurrent programming, and it's a big one, is that the bulk of us have lived the last 10 to 15 years (or more) in a single-threaded, object oriented world, where mutable state is the norm. This world does not translate well to a concurrent approach - once multiple threads start accessing this mutable state, bad things are going to happen. Bad things that, unfortunately, are often missed during testing. Data corruption, random crashes and random hangs are not the sorts of things that inspire confidence in your software.

The approach normally taken is to scatter "lock" statements around, and to use objects such as Semaphores and Monitors to protect and synchronise the various threads. The problem is that these are both error prone (nothing will tell you that you've missed out a lock), and they seriously degrade the ability to read the code, muddying the interesting "business logic" with a potentially large amount of gnarly threading code. If you make your locks too fine-grained you risk missing them in places and possibly increase the risk of deadlock. Make them too coarse-grained and you end up not scaling so well, with your application suffering significant contention over your crude locking strategy. Concurrency might be the silver bullet that allows you to scale on the upcoming hardware but it's a bullet that's really hard to fire with any accuracy. And no one wants bullets flying in random directions. (Thinking I've pushed this analogy about as far as it will go!)

So what of the Task Parallel Library? Well, it doesn't magically make all the pain go away, but it does offer some new abstractions that can help alleviate some of it and leave the code somewhat more readable than before. It also starts guiding you down a path where shared, mutable state is not the norm, the only approach that I think has got the potential to work in the long term. The next few posts will hopefully shed some light on how to use it and what it can and can't do for you.


1To be clear, twice the amount of work, which is different from handling twice the amount of input data. How much additional data could be processed when performance is doubled depends entirely on the complexity of the algorithms being used.

2There were earlier dual-core processors such as the IBM POWER4 back in 2001, and Intel had offerings such as the Pentium D. But is was really (IMO) the Core Duo and Athlon that really pushed it form a marketing perspective.

NDC 2010

Last week I was lucky enough to attend this year's Norwegian Developer Conference, NDC2010, in the great city of Oslo.

In contrast to last year's conference where I was an attendee, this year I was lucky enough to be one of the speakers. I had 3 sessions on the first day, all on the topic of concurrency, these being:

  • Overview of Concurrent Programming Techniques - an overview of the various features available in .Net 4.0
  • CHESS - Finding and Reproducing Heisenbugs in Concurrent Programs - a fairly in-depth look at the CHESS tool from Microsoft Research and available through DevLabs
  • The Parallel Task Library in .Net 4.0 - following on from the overview, this talk goes into more depth on the new APIs available

Although having three talks on the first day was pretty tiring, it was great to be able to spend Thursday and Friday chatting with attendees and other speakers and watching some of the other sessions. It was also good to have a spread of technologies being represented and not just a slew of Microsoft products being discussed - not that there's anything wrong with that, just that it was nice to be able to compare & contrast which is hard at other events such as TechEd and PDC.

For those that weren't able to make the conference, all the sessions were recorded and can be found here. In addition, for those that want to download there's also a torrent of the first 33 talks available here. I'm sure that there will be additional torrents made available as the remaining talks are put online; I'd keep an eye on the #ndc2010 hashtag on Twitter where I've no doubt it will show up :)

It's certainly worth finding some time to watch a few of these - those that I saw were of a high quality, and there are so many diverse topics that there's bound to be something of interest to pretty much anyone.

The organisation of the conference was excellent, and it was great to see the exhibitors dishing out hotdogs, ice-creams & smoothies instead of the usual useless tat that is normally given away.

Looking forward to #ndc2011 already!

Going Home!

For the last 6 days I've joined an ever growing group of people that have been stuck away from home as a result of the Eyjafjallajokull volcano and the subsequent shutdown of UK airspace.

As a few of you probably know, I'm based in Spain and head over to the UK office on a monthly basis to do the invaluable face-to-face meetings and chats over a coffee that are kind of hard to engineer via Skype. This trip has been somewhat extended, much to the dismay (or delight, I'm not sure which!) of my wife & daughters.

Today the trek home begins - I finally gave up relying on the airlines on Sunday and booked myself a train ticked, due to a good chance that the ash cloud would persist for some time (with the subsequent impact on other forms of transport), and due to a concern that flights would be reopened due to political / economic pressures rather than good scientific evidence that it was indeed safe to fly. I'm not a nervous traveller by any means, but I've not desire to become a guinea pig in some executive's experiment anytime soon. Not when it may involve "tubes of burning death" as one of my colleagues so nicely put it.

So for the next couple of days I'm going to be fighting with trains & automobiles, and generally avoiding the planes. Internet access is likely to be sporadic, but when I'm online I'll be tweeting where I am. I you're not interested, I suggest you unfollow me for a couple of days :)

The current plan is London -> Paris -> Avignon -> Barcelona, and from there either a flight to Málaga or the Spanish high speed rail to Madrid and the Málaga. Should all be good fun. Especially since the French appear to have chosen now as a good time for a rail strike. Oh well, who wants easy anyway?

Open Volcano 10

Today was spent at Open Volcano 10. For those that didn't hear of it, this was an Open Spaces conference conceived by Roy Osherove, Uncle Bob and others and organised and run superbly by Rachel Davies and Paul Dolman-Darrall (huge apologies to the many others that I've missed out!).

What was so special about it? Well, a couple of things. Firstly, it was enabled by the unforeseen eruption of the Eyjafjallajokull volcano in Iceland and the unprecedented impact that has had on European air travel. This resulted in a number of the "big names" in the software industry being stuck here in ole' Blighty with not much to occupy their minds.

Secondly, the speed that a small bunch of unashamed geeks managed to turn an idea into reality was truly awesome. The "concept to cash" process took no more than a few days, and resulted in around 100 attendees all meeting at the same venue at the same time, with food and drinks provided. A feat that many a government organisation would be envious of!

Thirdly, the enthusiasm within the individuals to create a day that would provide value and (equally importantly, in my book) fun. From no plans whatsoever, and with only the lightest of touches from Rachel, around 20 sessions were organised and executed. From the 5 that I attended, I can say that they all provided great food for thought in their own way; indeed, I'd go as far to say as it being one of the best 1 day conferences that I've ever attended, which is a great testament to the organisers and fellow attendees.

For those that haven't attended an Open Spaces session before, I'd recommend giving it a go. It's a fresh alternative to the traditional more structured conference, and follows the following 4 principles:

  • Whoever comes is the right people
  • Whenever it starts is the right time
  • Whatever happens is the only thing that could have
  • When it's over, it's over

The only rule is the Rule of Two Feet - if the session you're in turns out to not be the right session for you, then leave. Nice and simple. No one will be offended - anyone that thinks that their content will appeal to *everyone* is clearly misguided! By moving on, you leave more space for those that were getting value, and you can find value yourself in another talk. If there's no value anywhere, then it's time to pickup the baton and start something.

For those that missed it, a number the sessions were recorded and I believe they will be up on the Skills Matter website in due course. For those that did attend, it was great meeting you all and I look forward to a future event, although ideally one in which there were more volunteers rather than refugees!

Final thanks go to the sponsors that made it possible - as a complete and unashamed plug, I delighted to say that iMeta were the first to step up! Following closely behind were Emergent Behvaiour, JetBrains, Riverglide, Skills Matter and XTC, and I think it's great the our community could pull something like this together so quickly.

TekPub's Mastering LINQ Challenge

Just saw a good little LINQ quiz on Justin Etheredge's blog - I saw it way too late to get the TekPub subscription, but thought it looked fun anyhow. Here's my solution:

   static void Main(string[] args)
   {
     int max = 100;

     var primes = Enumerable.Range(1, max)
      .Where(i => i > 1)
      .Aggregate(Enumerable.Range(2, max - 1).ToArray(), (sieve, i) =>
      {
        if ((i > Math.Sqrt(max)) || (sieve[i - 2] == 0)) return sieve;
        for (int m = 2; m <= max / i; m++)
          sieve[i * m - 2] = 0;
        return sieve;
      })
      .Where(n => n != 0)
      ;

     foreach (var p in primes)
     {
       Console.WriteLine(p);
     }

   }

Arguably it's not pure LINQ; there's more going on inside the aggregate lambda than I'd like, but it does the job and for those into this sort of thing you should recognise it as an implementation of the Sieve of Eratosthenes.

Using the new Linq to NH Provider and migrating from the old one

Using the new Linq provider is pretty simple. It all hangs of a Query() extension method on ISession, so you can do things like the following:

  from c in session.Query<Customer>() select c

In my tests, I've tended to wrap the session.Query() call behind a simple facade, along the lines of:

  public class Northwind
  {
   private readonly ISession _session;

   public Northwind(ISession session)
   {
   _session = session;
   }

   public IQueryable<Customer> Customers   { get { return _session.Query<Customer>(); }
  }

Of course, that's entirely optional, but I find the resulting code easier to read:

  from c in db.Customers select c

Once you know how to hook into the session (which as you can see is pretty simple), the rest is just straightforward Linq code, and entirely up to you! Right now I'm not exposing any extension points, but they'll be coming soon (plus another post to describe how to use them).

The version 1 provider used an ISession extension method call Linq() to provide its hook. I purposefully used a different name, since there's no reason at all why you can't use both providers within the same project or, indeed, within the same session. So that gives a couple of migration options for folk that want to move to the new provider:

  • Leave all your current queries using .Linq(), and start using .Query() for new ones.

  • Start changing existing queries from .Linq() to .Query(), just by changing them or by a simple search & replace. The rest of the expression will (hopefully!) just work.

  • Drop your reference to the original Linq provider assembly, and create your own extension method:

      public static IQueryable<T> Linq<T>(this ISession session)
      {
        return session.Query<T>();
      }


    This lets you switch to the new provider without changing a line of your code - and if you find it all goes to hell, you just re-add the V1 reference and comment out your extension method. Should work like a treat.

Other than that, I don't think there's much to tell - usage really should be pretty simple. Oh, one thing that springs to mind - although you can use the V1 provider and the new provider within the same project (or session), don't try to compose queries from them together; that's really going to do weird stuff!

Linq to NHibernate Progress Report - A Christmas Gift?

Time for another progress report, and this one's a biggie :)

Barring a couple of pretty minor things that won't take much fixing, all the original Linq tests have now been ported over to the new provider and are all passing. That means the new provider is now (from the perspective of the tests, at least) in better shape that the version 1 provider. It can do everything the original provider could, plus a whole bunch more. A couple of example queries that now work just fine are:

  from e in db.Employees
  from et in e.Territories
  where e.Address.City == "Seattle"
  select new {e.FirstName, e.LastName, et.Region.Description};

  from c in db.Customers
  join o in db.Orders on c.CustomerId equals o.Customer.CustomerId into orders
  select new {c.ContactName, OrderCount = orders.Average(x => x.Freight)};

  from o in db.Orders
  from p in db.Products
  join d in db.OrderLines on new {o.OrderId, p.ProductId} equals new {d.Order.OrderId, d.Product.ProductId}
   into details
  from d in details
  select new {o.OrderId, p.ProductId, d.UnitPrice}

  from c in db.Customers
  join o in db.Orders on c.CustomerId equals o.Customer.CustomerId
  group o by c into x
  select new { CustomerName = x.Key.ContactName, Order = x }

(ignore whether those queries make any real sense, it's just the form of them that matters)

So, more importantly, what doesn't work? Well, out of the tests that we've currently got, not a huge amount. Some important areas that are missing are:

  • Nested selects to produce hierarchical output. I've got some prototype code for doing this, so it will be supported at some point but isn't there right now. Of course, since NH already understands relationships, nested selects are far less important than they are for something like Linq to SQL.

  • Group joins that produce hierarchical output - these essentially boil down to the same code as the nested select case, so support for these will probably come at around the same time. Group joins that don't introduce a hierarchy should work just fine.

  • Set operations, such as Union and Intersect. Union you can obviously do yourself in client code with no particular overhead. Intersect would really be better in the provider :)

  • Left outer join style queries, such as:

    from e in db.Employees
    join o in db.Orders on e equals o.Employee into ords
    from o in ords.DefaultIfEmpty()
    select new {e.FirstName, e.LastName, Order = o};

    This is a fairly widely used construct, so is close to top of the list for future support

  • Let expressions, such as:

    from c in db.Customers
    join o in db.Orders on c.CustomerId equals o.Customer.CustomerId into ords
    let z = c.Address.City + c.Address.Country
    from o in ords
    select new {c.ContactName, o.OrderId, z};

    Again, this is quite high on the TODO list.

  • Support for custom functions. This is actually fully implemented internally, but I just want to review the API usage before I tell you all how to use it - it probably needs a little cleanup from it's current state, but I don't anticipate any major changes.

I've also got a number of TODOs, but mainly cleanup rather than functional, plus I need to work through the error handling to ensure that any queries that are passed in that the provider can't handle are rejected gracefully rather than just barfing (which is the likely case right now).

The only gotcha that I know of is a query that runs just fine but doesn't necessarily return the correct number of results. Specifically, queries like this:

  from user in db.Users
  select new
  {
    user.Name,
    RoleName = user.Role.Name
  };

Right now, this generates a join to the Role table, so any users that don't have a role are not returned. This differs from Linq to SQL where a left join is generated, giving a null RoleName for any users without a role. I believe the Linq to SQL implementation to be correct - the above query doesn't explicitly have any form of filtering (where clause, join clause etc), so you should get back all the users in the database. This one is top of the list, and hopefully will be fixed soon (it's not hard, I've just run out of time!).

In summary, I'm getting pretty happy with the state of the provider and think that it's now ready for general usage - although there are still some important query forms to support, I think there's sufficient there now to do useful work. If you've got good test coverage, then I'd even be happy for it to go live. If you don't have good coverage, then don't come crying to me :)

Of course, this is all in the trunk, so anyone wanting to play either needs to get the trunk source and build it, or take the much easier option of having Horn do the work. Horn builds the trunk on a daily basis, so look for a package built after around 2300GMT on the 16/12/2009 (the package URL on Horn has the datetime stamp in it, so it's pretty easy to spot).

I'm on holiday for the next couple of weeks, and will only have intermittent internet access. However, ping me either by email or twitter with comments / bugs / suggestions, and I'll do my best to reply. Normal service (whatever that is) will return around Jan 4th.