Using LINQ to Access SQL Server Compact Directly – A follow up

You Can Take it With You

Syndication

News

  • Don't miss the next Windows Mobile Webcast... Unit Testing for Mobile Devices: http://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?EventID=1032382824&EventCategory=4&culture=en-US&CountryCode=US.

You may recall my webcast from about 6/7 weeks ago where I talked about how to use custom extension methods to allow your mobile device applications to efficiently query a SQL Server Compact (SSC) databases directly.

I received a question today regarding the webcast. The question relates to the verbosity of returning anonymous types when using the technique discussed in the webcast. For example…

var records = from order in resultSet
where (string)order["Ship Country"] == "UK"
select new
{
ShipName = (string)order["Ship Name"],
ShipAddress = (string)order["Ship Address"],
ShipCity = (string)order["Ship City"],
ShipPostalCode = (string)order["Ship Postal Code"],
ShipCountry = (string)order["Ship Country"],
ShipVia = (int)order["Ship Via"]
};

The verbosity is necessary because "order" in the above LINQ statement is of type SqlCeUpdatableRecord. Although Visual Studio supports generating typed-wrappers for SqlCeResultSet, it doesn't generate wrappers for the SqlCeUpdatableRecord corresponding to the SqlCeResultSet. With that being the case, specifying the individual column values within the anonymous type declaration requires you to use either the indexer (or Getxxx functions).

Ultimately there's nothing we can do about the way the column values are accessed (unless you write your own typed-wrapper generator for SqlCeUpdatableRecord). What we can do is move the code that creates the anonymous type into a separate function.

private object ShippingColumns(SqlCeUpdatableRecord order)
{
return new
{
ShipName = (string)order["Ship Name"],
ShipAddress = (string)order["Ship Address"],
ShipCity = (string)order["Ship City"],
ShipPostalCode = (string)order["Ship Postal Code"],
ShipCountry = (string)order["Ship Country"],
ShipVia = (int)order["Ship Via"]
};
}

With that, the LINQ statement becomes very simple.

var records = from order in resultSet
where (string)order["Ship Country"] == "UK"
select ShippingColumns(order);

Once we move the anonymous type declaration to a separate function we can take things one step further and optimize the column access by caching the column ordinals and then retrieving the column values using the Getxxx functions.

private object ShippingColumns(SqlCeUpdatableRecord order)
{
if (_nameIndex == -1)
InitializeIndexes(order);

return new
{
ShipName = order.GetString(_nameIndex),
ShipAddress = order.GetString(_addressIndex),
ShipCity = order.GetString(_cityIndex),
ShipPostalCode = order.GetString(_postalCodeIndex),
ShipCountry = order.GetString(_countryIndex),
ShipVia = order.GetInt32(_viaIndex)
};
}

private
void InitializeIndexes(SqlCeUpdatableRecord record)
{
_nameIndex = record.GetOrdinal("Ship Name");
_addressIndex = record.GetOrdinal("Ship Address");
_cityIndex = record.GetOrdinal("Ship City");
_postalCodeIndex = record.GetOrdinal("Ship Postal Code");
_countryIndex = record.GetOrdinal("Ship Country");
_viaIndex = record.GetOrdinal("Ship Via");
}

private
int _nameIndex = -1;
private int _addressIndex = -1;
private int _cityIndex = -1;
private int _postalCodeIndex = -1;
private int _countryIndex = -1;
private int _viaIndex = -1;

Although not a huge performance increase, storing the column indices does eliminate the overhead of the indexer looking up the column name each time. More importantly, using the strongly-typed Getxxx functions eliminates the overhead of boxing any column values that are value-types. As you know, excessive boxing creates a lot of scrap objects which leads to increased memory and garbage collection overhead.

Using regular class methods like those above work just fine; however, if you find that you use a common anonymous type throughout different parts of your application, you may want to use an extension method – extension methods are also nice just because of their class-member-like syntax.

public static class ResultSetExtension
{
public static IEnumerable<SqlCeUpdatableRecord> Where(
this SqlCeResultSet resultSet, Func<SqlCeUpdatableRecord, bool> theFunc)
{
return new PrepAwareEnumerableWrapper(resultSet, theFunc);
}

public static object ShippingColumns(this SqlCeUpdatableRecord order)
{
if (_nameIndex == -1)
InitializeIndexes(order);

return new
{
ShipName = order.GetString(_nameIndex),
ShipAddress = order.GetString(_addressIndex),
ShipCity = order.GetString(_cityIndex),
ShipPostalCode = order.GetString(_postalCodeIndex),
ShipCountry = order.GetString(_countryIndex),
ShipVia = order.GetInt32(_viaIndex)
};
}

private void InitializeIndexes(SqlCeUpdatableRecord record) { ... }

With the extension method, the LINQ statement becomes…

var records = from order in resultSet
where (string)order["Ship Country"] == "UK"
select order.ShippingColumns();

A couple of notes before I finish up…

One thing to keep in mind is that all of these functions that create the anonymous type have a return type of object. This means that the IEnumerable<T> collection that is created by the LINQ statement will be of type IEnumerable<object> rather than IEnumerable<compiler_generated_type>. In most cases this difference does not matter but it is something to be aware of.

And for a final note, if you do find yourself returning the same anonymous type construct from a number of LINQ statements, you might want to consider defining an explicit type and then constructing instances of that type within the LINQ statement. In my experience, anytime I find myself using an anonymous type/function/etc. more than once or twice that I ultimately end up needing access to it in my code in a non-anonymous fashion.

To return a specific type from a LINQ statement, simply define a type that has a constructor that accepts a SqlCeUpdatableRecord and assigns the desired columns to the corresponding type members – basically the constructor will look like the ShippingColumns methods shown above. Assuming that you've defined a class named ShipInfo with the appropriate constructor, your LINQ statement would look like the following…

var records = from order in resultSet
where (string)order["Ship Country"] == "UK"
select new ShipInfo(order);

I've updated one of the samples from the original webcast to include examples of what we've talked about in this post. If you'd like the updated sample, you can download it from here. The methods you'll want to look at in the download are menuRedefineType_Click, menuDynamicType_Click, menuDynamicTypeExtMethod_Click all of which are in the Form1.cs source file.


Posted Apr 18 2008, 02:37 PM by jim-wilson

Comments

Rich M wrote re: Using LINQ to Access SQL Server Compact Directly – A follow up
on 04-30-2008 9:52 AM
Jim, Great article about linq. I'm reading more and more and will try to incorporate linq into my day to day stuff.
Regarding my mobile activity, I'm at a loss to find *the* way to connect to a network based sql server.
We are a manufacturer looking to use RF devices running mobile 5.0, scanning barcodes, getting results, etc. I do not want and basically cannot use SQLcompact as the db is too big to update to the units.
Can you point me in a direction that might help me on this as everything appears to be oriented to the onboard sql files not the network sql server.
Any help will help.
Jim Wilson wrote re: Using LINQ to Access SQL Server Compact Directly – A follow up
on 05-02-2008 4:42 PM
Rich;

In terms of accessing a server-based SQL Server instance from a mobile device, you have basically 2 choices.

1. You can use the System.Data.SqlClient classes. .NET CF provides the same family of classes in this namespace as the desktop
2. Use Windows Communications Foundation (or other web service-like mechanism)

Option 1 allows you to access SQL Server from a .NET CF app just a desktop app does.
- Pros:
This will allow you to directly issue SQL statements from inside of your .NET CF application to update/delete/insert/select against the server db
- Cons:
The SQL Server network protocol TDS isn't designed to handle intermittent lose of connectivity. If the connection blinks out periodically, there's a good chance things may hang. Also, I don't expect Microsoft to offer much future investment in this solution for mobile devices; I don't expect it to go away, but I don't think we'll see any enhancements either.

Option 2 is the standard HTTP-based web service solution. I personally would go with WCF. You could create the service using the old ASP.NET Web Service project type and in the client app could use the Add Web Reference option of Visual Studio to create a proxy that allows you to call the service. Honestly though, the ASP.NET-style of web services is pretty much out of fashion at this point so I'd stay away from it.
- Pros:
This solution is designed for wireless networks and works well in these environments
- Cons:
You'll need to create server-side methods to represent the database operations you need to perform. Using WCF you can host the server-side service in pretty much any process type you like. If you went the traditional ASP.NET web service path you'd need to have IIS on the server

If it was my project I'd use WCF. It's easy to get started with yet very powerful and flexible if you do need add some kind of special handling later. Another important point is that http-based WCF services work very well in the kind of environment you've described - they're very tolerant of the network blinking out occasionally.

The fact that using WCF (or any type of web service) requires you to create server-side methods to wrap the SQL operations isn't really that big of a deal. You'll be able to create them pretty easily. It's really just a matter of writing the SQL statements on the server that you would've written in the device-side app anyway. You then just write simple method wrappers on the server and then call those methods from the device through a proxy – The Power Toys for .NET CF 3.5 includes a utility called netcfsvcutil that creates the proxies – it’s just a .NET CF compatible version of the desktop’s svcutil utility

I realize that I've given you a lot to look at and only high-level info but I think it's enough to help you get started. Once you take a look at things, please feel free to contact me with additional questions.

- Jim
Jim Wilson wrote re: Using LINQ to Access SQL Server Compact Directly – A follow up
on 05-02-2008 4:46 PM
Rich;

I had tried to include some URLs in my reply but my blogging service kept rejecting my post as spam.

So I've had to break the URLs out into a couple of seperate posts...

Here's the System.Data.SqlClient
URLS:
http://msdn.microsoft.com/en-us/library/ms173281.aspx
http://msdn.microsoft.com/en-us/library/aa275613(SQL.80).aspx
http://msdn.microsoft.com/en-us/library/system.data.sqlclient(VS.80).aspx

-Jim
Jim Wilson wrote re: Using LINQ to Access SQL Server Compact Directly – A follow up
on 05-02-2008 4:47 PM
Rich;

And finally, here's the WCF
URLs:
http://msdn.microsoft.com/en-us/library/bb397803.aspx
http://blogs.msdn.com/andrewarnottms/archive/2007/08/21/the-wcf-subset-supported-by-netcf.aspx
http://www.microsoft.com/downloads/details.aspx?familyid=C8174C14-A27D-4148-BF01-86C2E0953EAB&displaylang=en

Sorry I had to spread these across 3 replies, unfortunately it was the only way I could include all of the URLs.

-Jim
Durant Rodrigue wrote re: Using LINQ to Access SQL Server Compact Directly – A follow up
on 05-17-2008 6:32 AM
Jim,

I really enjoyed watching the webcast. I thought some of the ideas you had regarding how to use extensions and your approach to presenting this example was very good.

I downloaded the example code you supplied and in order to understand it I began digging through the code to better understand what you were doing and the test methodology.

When I begain looking at the IterateOverData methods in the Form1.cs file, I began to find some issues with your test methodology which is scewing the performance numbers. In the first iterator (used for raw, index,custom ext and prepared) the code is looping through the records and creates an int and string for every record. In the second iterator (used for the redefine type) the code is looping through creating two strings for every record. The last iterator (used by the dynamic type and dynamic type with extension) the code only loops through the data without creating variables.

In your defense, I recognize you are setting the data source on a grid for the last two and I am suspect your thinking is that this provides the same type of processing which would validate the test.

When I removed the variable creation and setting of the datasource on the grid, I found that the numbers do not match what you were finding. In reality, what I found was the after the code had been primed, the raw and index out performed the other items which is what I expected.

I wanted to make you aware of this since I know how easy it is to make a mistake on testing perfromance items. I have made my share of mistakes regarding that and it is good to know when they are made. I hope this does not offend you and I look forward to hearing back from you if you have arguments contrary to what I have indicated here.

durantng1@traxwareinc.com

Hopefully, I will not get flamed too badly by putting my email out there.

Durant
Jim Wilson wrote re: Using LINQ to Access SQL Server Compact Directly – A follow up
on 05-19-2008 9:32 AM
Durant;

Thank you for taking the time to investigate the code and for letting me know that I had inadvertently skewed the test results.

I don't recall the circumstances that I was considering when putting the different test scenarios together. In any case, I want to be sure to test the results on a level playing field and certainly don't wish to give the impression that something I've put together performs better than it actually does.

As time permits, I'll re-do the tests using a more consistent methodology and post the resulting numbers.

Thanks again for the information and for taking the time to let me know about your observations.

Thanks,
Jim

Add a Comment

(required)  
(optional)
(required)  
Remember Me?