Whenever I come across an unexpected technical issue, I try to balance the desire to post my findings to my blog quickly: so that someone else doesn't have to fight the same battle I did – or, wait a little bit: so that I can dig into the issue in more detail and provide a complete explanation of what's behind the issue. Well, my post from yesterday evening erred just a little on the side of posting too quickly. J
You'll recall that the issue was that the following LINQ query and data binding work in a C# device project
var selectedRows = from order in _northwindDataSet.Orders
where order.Ship_Country == filterDialog.SelectedCountry
select order;
dataGrid1.DataSource = selectedRows.CopyToDataTable();
But the same LINQ query and data binding don't work in a VB device project (although they do work in a VB desktop project).
Dim selectedRows = From order In NorthwindDataSet.Orders _
Where order.Ship_Country = "UK" _
Select order
OrdersBindingSource.DataSource = selectedRows.AsDataView()
Get this … … both the C# and VB device projects are behaving correctly?!?
Here's why – The general rule in .NET CF development is that device projects (whether C# or VB) use the same compiler as desktop projects but have reduced library and runtime support. This means that you can use all language features in .NET CF apps, but must limit the classes and methods you use to those in the supported subset.
But then came LINQ…
LINQ (Language Integrated Query) – As its name implies, LINQ allows you to write queries consistent with your programming language. Unlike most language features that compile directly into IL, LINQ query statements are first transformed into the corresponding method calls; the method calls are then compiled into IL. This means that in the case of LINQ queries, the query language is directly dependent on the supported library subset – this dependence on the library subset is what leads to what appears to be a problem with LINQ in VB Smart Device projects.
If you've worked with .NET CF for very long, you're probably aware that the .NET CF team works very hard to keep the deployed .NET CF footprint as small as reasonably possible and therefore excludes features that are not used frequently or that one can easily find an alternative to. Consistent with this effort, the .NET CF team did not include the Select extension method for EnumerableRowCollection<T> because when performing a query against a DataRow-derived value where the selector returns that same value ( from order .... select order ), the Select method doesn't provide any value. In this situation, the Select method would end up iterating through each member of the EnumerableRowCollection<T> to ultimately produce another EnumerableRowCollection<T> with the same members.
So how does the absence of this method affect the difference in query syntax support between C# and VB device projects?
Well the C# syntax for LINQ queries requires that you always include the Select keyword to indicate the value to return. Because C# LINQ query syntax always requires the Select keyword, the C# compiler evaluates whether the method syntax for the query requires a call to the actual Select method and only includes the call to the Select method if the method provides value. As a result, the C# LINQ query at the beginning of this post transforms into the following method call.
_northwindDataSet.Orders.Where<NorthwindDataSet.OrdersRow>(order => order.Ship_Country == "UK");
The VB syntax for LINQ queries does not require the Select keyword when the selector returns the same value that is queried (from order ... select order). Because the Select keyword is not required, VB assumes that if you include the Select keyword in the query syntax then you must want to call the Select method. As a result the VB query turns into the following method call (expressed in C# for consistency).
_northwindDataSet.Orders.Where<NorthwindDataSet.OrdersRow>(order => order.Ship_Country == "UK").Select<NorthwindDataSet.OrdersRow, NorthwindDataSet.OrdersRow>(order => order);
It's the inclusion of the Select method in the transformed VB code that creates the problem when the code attempts to apply the AsDataView extension method to the query result; the AsDataView extension method requires an EnumerableRowCollection<T> value. Since .NET CF doesn't include the Select extension method for EnumerableRowCollection<T>, the compiler must choose one of the other Select extensions methods, both of which return IEnumerable<T>. As a result, the query return type is IEnumarable<T> rather than the expected EnumerableRowCollection<T> and just as the compiler reports, there is no supported AsDataView extension method for the returned IEnumerable<T> query result.
So how do we fix the problem? … When writing your VB Smart Device project LINQ queries, only include the Select keyword when selecting a value different that the value being queried. With this in mind, the proper way to write the query and data binding code is the following.
Dim selectedRows = From order In NorthwindDataSet.Orders _
Where order.Ship_Country = "UK"
OrdersBindingSource.DataSource = selectedRows.AsDataView()
What seemed so complicated ends up being so simple. J
Posted
Feb 05 2008, 10:49 AM
by
jim-wilson