The discussion thus far establishes a baseline of what developers are used to having when they work against data sources. What happens when the schema changes? You can easily recognize the changes and you can write your queries in accordance with the newly added column names as soon as the IntelliSense engine refreshes the data. It's easy and transparent.
Statically typed languages have an impedance mismatch problem and they don't provide an elegant way of handling schema changes. F# is a statically typed programming language that includes type abbreviations and units of measure that make it easy to specify primitive data types. However, there is an outside world with typed data that is dissociated from your F# code. Different kinds of tools generate wrappers in order to allow you to write code with types that are friendly for your programming language. In some cases, you end up including separate assemblies compiled from C# code or other .NET languages in order to consume data sources in F#.
Consider my simple database. No matter the Object-Relational Mapping (ORM) you might use in your application, you need to run some kind of automatic or manual process to update the mappings. However, that isn't the only problem; you also need to consider that task's impact on both the build process and source control. In some cases, you don't have to leave the IDE to perform those tasks, but in other cases, you have to use external tools, such as command line utilities.
The problem is worse when you have to consume big data in the cloud: Different Web services and APIs that have extremely complex schemas that are continuously changing. In those cases, it is necessary to use different kinds of code generators to acquire the necessary type translations for the programming languages. In addition, there is often a loss of type information. Think about your usual procedure to consume a Web service in .NET languages. Whenever there is a schema change, you need to update the service references. In some cases, because schemas are changing all the time, it is a big problem to continue using that mechanism. In other cases, it becomes impossible because the code generation tools take too much time and too many resources to build entire schemas, and there are schemas that are truly huge on the Web.
The problem fundamentally is: Why spend time analyzing and solving the impedance mismatch problem of statically typed languages and their way of handling schema changes? F# 3.0 type providers come to the rescue and enable you to focus on your code without having to worry about schema changes and updates.
F# 3.0 type providers generate the necessary wrappers at design time while you're writing code in either the IDE or the FSI (short for F# Interactive) window. As I'll show, everything magical happens under the hood and you don't have to maintain extra source code.
Accessing a SQL Server Database with a LINQ to SQL Type Provider
F# 3.0 provides the four different type providers to connect to SQL databases:
DbmlFile(LINQ to SQL)
SqlDataConnection(LINQ to SQL)
In this first example of type providers, I'll use
SqlDataConnection (LINQ to SQL) to consume data from the sample database. After creating a new F# project, it is necessary to follow the next steps before using the
SqlDataConnection type provider:
- Right click on References in Solution Explorer, and select Add Reference… The Reference Manager dialog box will appear. Click on Assemblies | Framework in the left pane, check both
System.Data.Linq(see Figure 4).
Figure 4: Adding the necessary Framework references.
- Click on Assemblies | Extensions in the left pane. Then, check
FSharp.Data.TypeProviders(see Figure 5).
Figure 5: Adding the necessary Extensions reference.
The Type Provider Security dialog box will appear indicating that you've opened a source code file in a project that references a type provider (see Figure 6).
Figure 6: Warning dialog box.
- If you trust the type provider assembly, click Enable (in this case, you trust it, but you must be careful with providers from untrusted third parties). Your project references will include the
FSharp.Data.TypeProviders(see Figure 7).
Figure 7: The project references after you finished added the necessary assemblies to use the Microsoft SQL Server type provider.
If you prefer the F# script and you want to work in the FSI window, the following lines will do the same thing. Note that you will have to execute them if you want to test code in the FSI window related to the SqlDataConnection type provider.
#r "System.Data" #r "System.Data.Linq" #r "FSharp.Data.TypeProviders"
As soon as you specify a value for
ConnectionString at design time, the type provider will try to establish a connection to the specified database server. The type provider must retrieve data for the Intellisense engine. This way, the type provider will display an error when you enter an invalid connection string. For example, the following lines won't establish any connection:
open Microsoft.FSharp.Data.TypeProviders type SqlServerConnection = SqlDataConnection<ConnectionString = @"WrongConnectionString">
"WrongConnectionString" is an invalid connection string; therefore, the IDE will display the following error at design-time:
'Microsoft.FSharp.Data.TypeProviders.DesignTime.DataProviders' reported an error: Format of the initialization string does not conform to specification starting at index 0 (see Figure 8).
Figure 8: The type provider reporting an error with an invalid connection string.
If the type provider isn't able to retrieve the schema, it provides detailed information at design time that allows you to easily troubleshoot the problem. For example, I'm using a SQL Server instance running on system WIN8X64, and the initial catalog should be Retrogames. The following lines specify an incorrect
Initial Catalog: RetrogamesWrong:
open Microsoft.FSharp.Data.TypeProviders type SqlServerConnection = SqlDataConnection<ConnectionString = @"Data Source=WIN8X64;Initial Catalog=RetrogamesWrong;Integrated Security=True">
The IDE displays the following error at design time:
The type provider 'Microsoft.FSharp.Data.TypeProviders.DesignTime.DataProviders' reported an error: Cannot open database “RetrogamesWrong” requested by the login. The login failed for user ‘WIN8X64\gaston' (see Figure 9).
Figure 9: The type provider reporting an error with a wrong initial catalog specified in the connection string.