Enabling a Data Source for LINQ Querying
There are various ways to extend LINQ to enable any data source to be queried in the LINQ pattern. The data source might be a data structure, a Web service, a file system, or a database, to name some. The LINQ pattern makes it easy for clients to query a data source for which LINQ querying is enabled, because the syntax and pattern of the query does not change. The ways in which LINQ can be extended to these data sources include the following:
Implementing the IEnumerable<T> interface in a type to enable LINQ to Objects querying of that type.
Creating a provider for your data source that implements the IQueryable<T> interface. A provider that implements this interface receives LINQ queries in the form of expression trees, which it can execute in a custom way, for example remotely.
Creating a provider for your data source that takes advantage of an existing LINQ technology. Such a provider would enable not only querying, but also insert, update, and delete operations and mapping for user-defined types.
This topic discusses these options.
There are two ways you can enable LINQ querying of in-memory data. If the data is of a type that implements IEnumerable<T>, you can query the data by using LINQ to Objects. If it does not make sense to enable enumeration of your type by implementing the IEnumerable<T> interface, you can define LINQ standard query operator methods in that type or create LINQ standard query operator methods that extend the type. Custom implementations of the standard query operators should use deferred execution to return the results.
The best option for enabling LINQ querying of a remote data source is to implement the IQueryable<T> interface. However, this differs from extending a provider such as LINQ to SQL for a data source. No provider models for extending existing LINQ technologies, such as LINQ to SQL, to other types of data source are available in Visual Studio 2008.
LINQ providers that implement IQueryable<T> can vary widely in their complexity. This section discusses the different levels of complexity.
A less complex IQueryable provider might interface with a single method of a Web service. This type of provider is very specific because it expects specific information in the queries that it handles. It has a closed type system, perhaps exposing a single result type. Most of the execution of the query occurs locally, for example by using the Enumerable implementations of the standard query operators. A less complex provider might examine only one method call expression in the expression tree that represents the query, and let the remaining logic of the query be handled elsewhere.
An IQueryable provider of medium complexity might target a data source that has a partially expressive query language. If it targets a Web service, it might interface with more than one method of the Web service and select the method to call based on the question that the query poses. A provider of medium complexity would have a richer type system than a simple provider, but it would still be a fixed type system. For example, the provider might expose types that have one-to-many relationships that can be traversed, but it would not provide mapping technology for user-defined types.
A complex IQueryable provider, such as the LINQ to SQL provider, might translate complete LINQ queries to an expressive query language, such as SQL. A complex provider is more general than a less complex provider, because it can handle a wider variety of questions in the query. It also has an open type system and therefore must contain extensive infrastructure to map user-defined types. Developing a complex provider requires a significant amount of effort.