Post

SQLite and Dapper in C#

In this article I am going to cover the basics of running SQLite and Dapper in a C# console application. There is a lot of code below which may be highly opinionated (so please bear that in mind while reading it) with links to an example application on Github below should you wish to see it all in action.

Tools and Resources

While creating this article I made use of the following online resources and tools, and feel that the original authors should get the credit for the bits and pieces I used.

First Steps

First thing we need to do is create a new console application in Visual Studio and install SQLite and Dapper via the package manager console.

1
2
Install-Package System.Data.SQLite.Core
Install-Package Dapper

This will install the required files for SQLite and Dapper into your solution along with any dependencies that they might have.

Connecting to the Database

The connection strings used in SQLite are pretty straight forward seeing that it is a server-less database engine and for this project is as simple as Data Source=FilePath.sqlite;Version=3;. The connection does support additional parameters like credentials, but that is beyond the scope of this tutorial.

To connect to our database (and create it, if missing) add a CreateAndOpenDb() method with the following body:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static SQLiteConnection _dbConnection;
// ...
static void CreateAndOpenDb()
{
    var dbFilePath = "./TestDb.sqlite";
    if (!File.Exists(dbFilePath))
    {
        SQLiteConnection.CreateFile(dbFilePath);
    }
    _dbConnection = new SQLiteConnection(string.Format(
        "Data Source={0};Version=3;", dbFilePath));
    _dbConnection.Open();
}

Quickly running the code confirms that I am able to create and connect to my new database.

Extension methods

I would suggest creating some extension/helper methods for common SQLite tasks to help speed up development, and will start off by creating a new SQLiteExtensions class as listed below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void ExecuteNonQuery(
    this SQLiteConnection connection,
    string commandText)
{
    // Ensure we have a connection
    if (connection == null)
    {
        throw new NullReferenceException("Please provide a connection");
    }

    // Ensure that the connection state is Open
    if (connection.State != ConnectionState.Open)
    {
        connection.Open();
    }

    // Use Dapper to execute the given query
    connection.Execute(commandText);
}

Initial Database Seeding

After creating the DB you will need to create all the tables and objects that your application needs in order to run, I would suggest creating a seed method that gets called when the database is first created. Below is the contents of my SeedDatabase() method.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static void SeedDatabase()
{
    // Create a Users table
    _dbConnection.ExecuteNonQuery(@"
        CREATE TABLE IF NOT EXISTS [Users] (
            [Id] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
            [Username] NVARCHAR(64) NOT NULL,
            [Email] NVARCHAR(128) NOT NULL,
            [Password] NVARCHAR(128) NOT NULL,
            [DateCreated] TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )");

    // Insert an ADMIN user
    _dbConnection.ExecuteNonQuery(@"
        INSERT INTO Users
            (Username, Email, Password)
        VALUES
            ('admin', 'niemand.richard@gmail.com', 'test')");
}

This will create and seed a Users table with an admin user.

Simple User model

Next, we will create a User class to represent an entry in the Users table. This class should have a property per column you wish to interact with from the target database table.

1
2
3
4
5
6
7
8
public class User
{
    public int Id { get; set; }
    public string Username { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }
    public DateTime DateCreated { get; set; }
}

We will now make use of Dappers Query<T> method to map our admin user to the newly created User class.

1
2
var adminUser = _dbConnection.Query<User>(
    "SELECT * FROM Users WHERE Username = 'admin'")

Which results in the user being mapped like so:

Dapper is amazing!

Creating a new user

Now it’s time to add a new user to the database, but only if the user doesn’t already exist. In order to do this, I will need to perform the following actions:

  • Create a new User object to save to the database
  • Check to see that the user doesn’t already exist
  • Insert the user into the database

I will need to modify my ExecuteNonQuery() method a bit to make things easier.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void ExecuteNonQuery(
    this SQLiteConnection connection,
    string commandText,
    object param = null)
{
    // Ensure we have a connection
    if (connection == null)
    {
        throw new NullReferenceException(
            "Please provide a connection");
    }

    // Ensure that the connection state is Open
    if (connection.State != ConnectionState.Open)
    {
        connection.Open();
    }

    // Use Dapper to execute the given query
    connection.Execute(commandText, param);
}

This now allows for an optional object to be passed into the method, which is used by Dapper to replace any @parameter placeholders in the given command text.

To help make working with Users easier I added a new UserExtensions class to the project to house all database/user related code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static class UserExtensions
{
    public static void SaveAsNewUser(
        this User user,
        SQLiteConnection connection)
    {
        connection.ExecuteNonQuery(@"
            INSERT INTO Users (Username, Email, Password)
            VALUES (@Username, @Email, @Password)",
            user);
    }

    public static bool ExistsInDb(
        this User user,
        SQLiteConnection connection)
    {
        var rows = connection.Query(string.Format(
            "SELECT COUNT(1) as 'Count' FROM Users WHERE Username = '{0}'",
            user.Username));

        return (int) rows.First().Count > 0;
    }
}

This now allows me to add a CreateSecondUser() method to my Program class with the following body:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static void CreateSecondUser()
{
    var secondUser = new User
    {
        Username = "rachel",
        Email = "1@2.com",
        Password = "password"
    };

    if (!secondUser.ExistsInDb(_dbConnection))
    {
        secondUser.SaveAsNewUser(_dbConnection);
    }
}

This approach allows for more readable code, and helps a lot with code refactoring, as all of your management code is in one single place. After compiling and running the code I now have my second Rachel user.

Updating a user

I now add a GetUserByName() and SaveChanges() method to UserExtensions, allowing me to easily save any changes I make to a user back to the database.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static User GetUserByName(
    this SQLiteConnection connection,
    string username)
{
    var userCollection = connection.Query<User>(
        "SELECT * FROM Users WHERE Username = @UserName",
        new {Username = username});

    return userCollection.FirstOrDefault();
}

public static void SaveChanges(
    this User user,
    SQLiteConnection connection)
{
    connection.ExecuteNonQuery(@"
        UPDATE Users
        SET
        Email = @Email, Password = @Password
        WHERE Id = @Id", user);
}

Next I add a ModifyAdminUser() method to Program with the following body.

1
2
3
4
5
6
7
static void ModifyAdminUser()
{
    var adminUser = _dbConnection.GetUserByName("admin");
    adminUser.Password = string.Format(
        "pass_{0}", DateTime.Now.Millisecond);
    adminUser.SaveChanges(_dbConnection);
}

Compiling and running the project results in my admin users password being updated.

More, more, more users

I added a AddMoreUsers() method to Program to allow me to add user accounts easily. This method takes in a number of users to add and makes use of a RandomString() method I found online to help generate some unique usernames.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
static void AddMoreUsers(int amount)
{
    var baseUsername = string.Format(
        "{0}{1}{2}{3}{4}{5}",
        DateTime.Now.Year,
        DateTime.Now.Month.ToString().PadLeft(2, '0'),
        DateTime.Now.Day.ToString().PadLeft(2, '0'),
        DateTime.Now.Hour.ToString().PadLeft(2, '0'),
        DateTime.Now.Minute.ToString().PadLeft(2, '0'),
        DateTime.Now.Second.ToString().PadLeft(2, '0'));

    for (var i = 0; i < amount; i++)
    {
        var tempUser = new User
        {
            Username = string.Format("{0}{1}",
                baseUsername, RandomString(4)),
            Password = RandomString(10),
            Email = string.Format("{0}@{1}.com",
                RandomString(12), RandomString(5))
        };

        tempUser.SaveAsNewUser(_dbConnection);
    }
}

Now I have loads of user accounts to mess around with.

Removing a User

The last thing I would like to cover is deleting users from the DB. To do this I added the GetLastUser() and RemoveFromDb() methods to the UserExtensions class with the following body.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static User GetLastUser(
    this SQLiteConnection connection)
{
    var userCollection = connection.Query<User>(
        "SELECT * FROM Users ORDER BY Id DESC LIMIT 1");
    return userCollection.FirstOrDefault();
}

public static void RemoveFromDb(
    this User user,
    SQLiteConnection connection)
{
    connection.ExecuteNonQuery(
        "DELETE FROM Users WHERE Id = @Id", user);
}

These methods allow me to add a new RemoveLastNonAdminUser() method to the Program class with the following body.

1
2
3
4
5
6
static void RemoveLastNonAdminUser()
{
    var user = _dbConnection.GetLastUser();
    if (user.Username == "admin") return;
    user.RemoveFromDb(_dbConnection);
}

Each time this method is run the last user in the database is dropped.

After another run of RemoveLastNonAdminUser()

Source Code

The source code for this project can be found on my code-samples GitHub repository.

I welcome feedback and pull requests and am always open to any suggestions you may have.

I hope that you found this informative.

This post is licensed under CC BY 4.0 by the author.

Comments powered by Disqus.