Tutorial
This tutorial will get you up and running with Rezoom.SQL. In just a few minutes, you'll be writing statically typed SQL in your program and running it on a SQLite database.
You'll need Visual Studio 2015 or 2017 -- the free Community Edition is fine.
Make sure you check the box for "F# language support" in the installer. If you've already installed VS2017, you can re-run the installer and modify your installation to include F# language support.
Creating a new F# project for the tutorial
In Visual Studio, click File -> New -> Project. On the left side, select Templates -> Other Languages -> Visual F#, and select Console Application. Make sure you target .NET framework 4.5 or newer.
Installing Rezoom.SQL from NuGet
In your new project, open up the package manager console window (View -> Other Windows -> Package Manager Console) and type:
Install-Package Rezoom.SQL.Provider
Install-Package Rezoom.SQL.Provider.SQLite
This will add Rezoom.SQL and its dependencies to your project, along with a few files to help you get started.
If you are using Paket instead of NuGet, see the important note for Paket users at the bottom of this page.
Setting up your database model
One of the files automatically added to your project is V1.model.sql
. Take a look inside and you'll see
some SQL code like this. Give it a quick read so you understand the example model.
Note: you might want to toggle off SQL -> Intellisense Enabled because it's designed for T-SQL syntax.
create table Users
( Id int primary key autoincrement
, Email string(254) unique
, Name string(64) null
);
create table Comments
( Id int primary key autoincrement
, AuthorId int references Users(Id)
, Comment string(512)
);
create index IX_Comments_AuthorId on Comments
(AuthorId);
Now open up Program.fs
, the main module of your program. The first thing you'll need to do is create the database
and run that migration script to create your tables. Fortunately, this doesn't take much code.
open Rezoom.SQL
open Rezoom.SQL.Migrations
type MyModel = SQLModel<"."> // find migrations in the project folder, "."
let migrate() =
// customize the default migration config so that it outputs a message after running a migration
let config =
{ MigrationConfig.Default with
LogMigrationRan = fun m -> printfn "Ran migration: %s" m.MigrationName
}
// run the migrations, creating the database if it doesn't exist
MyModel.Migrate(config)
[<EntryPoint>]
let main argv =
migrate()
// return 0 status code
0
Go ahead and run this program with ctrl+F5. You should see that it outputs that it ran the migration. If you run it again, it won't output anything, since the database already exists and there's no need to run migrations.
You can find the database it created, named rzsql.db
, in the bin/Debug
folder of your project.
If you'd like to take a look around it and see the tables, I recommend using
DB Browser for SQLite.
Adding some data
You add data to your tables using plain old SQL INSERT
statements. Try adding
this code to your program (if you're lazy, just delete your main
function and
paste this in its place):
open Rezoom.SQL.Synchronous // extension methods for running commands synchronously
// define a SQL command for inserting a user and getting their ID
type InsertUser = SQL<"""
insert into Users(Name, Email) values (@name, @email);
select last_insert_rowid() as id
""">
// define a SQL command for inserting a comment
type InsertComment = SQL<"insert into Comments(AuthorId, Comment) values (@authorId, @comment)">
let addExampleUser name email =
// open a context in which to run queries
use context = new ConnectionContext()
// insert a user and get their ID
let userId : int64 =
InsertUser.Command(email = email, name = Some name).ExecuteScalar(context)
// add a couple comments by the user
InsertComment.Command(authorId = int userId, comment = "Comment 1").Execute(context)
InsertComment.Command(authorId = int userId, comment = "Comment 2").Execute(context)
// edit your main function to call the new insert code
[<EntryPoint>]
let main argv =
migrate()
addExampleUser "Test Person" "[email protected]"
// return 0 status code
0
You may notice that if you run this program twice, you'll get an error the second time.
This is because of the unique constraint on User.Email
. If you change the email address
in the program, you can run it again. Try editing it to get an email address at runtime from Console.ReadLine()
.
Querying for data
So far you've already run a query once: the select last_insert_rowid() as id
following inserting a user.
But you can use the SQL<...>
provided type to run all sorts of SQL queries. Let's try getting the list of users.
Again, you can add this code onto your program by removing your main
function and putting this code in its place.
type ListUsers = SQL<"""
select * from Users
""">
let showUsers() =
use context = new ConnectionContext()
let users = ListUsers.Command().Execute(context)
printfn "There are %d users." users.Count
for user in users do
printfn "User ID %d's email is %s..." user.Id user.Email
match user.Name with
| None -> printfn " and they don't have a name."
| Some name -> printfn " and their name is %s." name
// edit your main function to call the new query code
[<EntryPoint>]
let main argv =
migrate()
showUsers()
// return 0 status code
0
Using more complex queries
You can use SQL joins, group by, etc. in your queries. The SQL syntax is closely based on that of SQLite. For more details, refer to the language section of this documentation.
For example, we can join the Users
table to the Comments
table to get a list of comments and data
about the users who wrote them. This also demonstrates using query parameters. Notice that Rezoom.SQL infers
that the parameter @name
is a string from the fact that the query concatenates it with other strings.
// find comments by users whose names contain the given substring
type ListCommentsByUser = SQL<"""
select u.Id, u.Email, c.Id as CommentId, c.Comment
from Users u
join Comments c on c.AuthorId = u.Id
where u.Name like '%' || @name || '%'
""">
let showComments name =
use context = new ConnectionContext()
let comments = ListCommentsByUser.Command(name).Execute(context)
printfn "There are %d comments by users matching the name `%s`." comments.Count name
for comment in comments do
printfn "User ID %d:" comment.Id
printfn " Email: %s" comment.Email
printfn " Comment ID: %d" comment.CommentId
printfn " Comment: %s" comment.Comment
// edit your main function to call the new query code
[<EntryPoint>]
let main argv =
migrate()
showComments "Test"
// return 0 status code
0
Note for Paket users
The package Rezoom.SQL.Provider.SQLite
includes sample configuration files to
help you get started. These include V1.model.sql and rzsql.json.
When you update using NuGet, if you changed these files, it'll ask you before overwriting them. You will probably want to answer "no" to this question.
If you are using Paket, watch out. It will not ask and will overwrite these files every time you update or restore packages. This is definitely not what you want! So if you are using Paket, either:
Just use Rezoom.SQL.Provider and create the configuration files yourself by referring to the configuration section of this manual. Once you've seen the examples it's not difficult.
Or use
nuget Rezoom.SQL.Provider.SQLite content: once
so the configuration files get installed the first time, but never overwritten. See Paket docs.