Skip to content

Commit

Permalink
Added support for other database types than SQLite
Browse files Browse the repository at this point in the history
  • Loading branch information
igece committed May 8, 2022
1 parent 22ee703 commit bb26e52
Show file tree
Hide file tree
Showing 5 changed files with 334 additions and 219 deletions.
49 changes: 48 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
[![Tests](https://github.com/igece/SettingsDb/actions/workflows/tests.yml/badge.svg)](https://github.com/igece/SettingsDb/actions/workflows/tests.yml)

# SettingsDb
SettingsDb is a .NET Standard library that allows to manage persistence of application settings in a simple and fast way. Settings values are serialized as JSON objects and stored in a [SQLite](http://sqlite.org) database located in the same directory as the application. This offers a great level of flexibility in terms of what type of data can be stored.
SettingsDb is a .NET Standard library that allows to manage persistence of application settings in a simple and fast way.
Settings values are serialized as JSON objects and stored in a database.
This offers a great level of flexibility in terms of what type of data can be stored.

Any type of database can be used, as long as there is an specialization of
[DbConnection](https://docs.microsoft.com/en-us/dotnet/api/system.data.common.dbconnection) for it.


## Installation
Expand All @@ -24,6 +29,44 @@ dotnet add package SettingsDb

## Usage

Just create an instance of the `SettingsDb` class specifying the type of connection, the connection string and (optionally) the database table to use. If no table name is specified, "Settings" will be used.

``` C#
// Use a SQL Server database to store the settings.
var settings = new DbSettings<SqlConnection>("Database=myDataBase");
```

``` C#
// Use a SQL Server database to store the settings in the "AppSettings" table.
var settings = new DbSettings<SqlConnection>("Database=myDataBase", "AppSettings");
```

When using a [SQLite](http://sqlite.org) database, `Settings` class can be used instead. `Settings` acts as a shortcut for `DbSettings<SqliteConnection>` and provides backwards compatibility with previous versions of the library.

``` C#
// Use SQLite to store the settings.
var settings = new Settings();

// This is equivalent to use:
var settings = new DbSettings<SqliteConnection>($"Data Source={Assembly.GetEntryAssembly().GetName().Name}.Settings.db");
```

``` C#
// Use SQLite to store the settings in the "AppSettings" table.
var settings = new Settings("AppSettings");

// This is equivalent to use:
var settings = new DbSettings<SqliteConnection>($"Data Source={Assembly.GetEntryAssembly().GetName().Name}.Settings.db", "AppSettings");
```

``` C#
// Use SQLite to store the settings in the "AppSettings" table, using "Settings.db" as the database file name.
var settings = new Settings("Settings", "AppSettings");

// This is equivalent to use:
var settings = new DbSettings<SqliteConnection>("Data Source=Settings.db", "AppSettings");
```

When a new instance of the `Settings` class is created, it checks for existence of the database file and creates it if necessary. By default, the assembly name of the application will be used as the database name, but you can specify any other name in the constructor.

### Storing a Value
Expand All @@ -34,6 +77,7 @@ To store a value, just call the `Store` method (or its async version `StoreAsync
public void Store<T>(string settingName, T value);
public async Task StoreAsync<T>(string settingName, T value);
```

``` C#
var settings = new Settings();

Expand Down Expand Up @@ -67,6 +111,7 @@ and, optionally, a default value to be used if the specified setting is not foun
public T Read<T>(string settingName, T defaultValue = default);
public async Task<T> ReadAsync<T>(string settingName, T defaultValue = default);
```

``` C#
var settings = new Settings();

Expand All @@ -82,10 +127,12 @@ If no default value is specified and the setting name is not found, SettingsDb w
public void Clear(string settingName);
public Task ClearAsync(string settingName);
```

``` C#
public void ClearAll();
public Task ClearAllAsync();
```

``` C#
public long Count();
public long CountAsync();
Expand Down
19 changes: 19 additions & 0 deletions src/SettingsDb/Extensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Data.Common;


namespace SettingsDb
{
internal static class Extensions
{
public static DbCommand AddParameter(this DbCommand sqlCommand, string name, string value)
{
var sqlParam = sqlCommand.CreateParameter();
sqlParam.ParameterName = name;
sqlParam.Value = value;

sqlCommand.Parameters.Add(sqlParam);

return sqlCommand;
}
}
}
219 changes: 4 additions & 215 deletions src/SettingsDb/Settings.cs
Original file line number Diff line number Diff line change
@@ -1,233 +1,22 @@
using System;
using System.IO;
using System.IO;
using System.Reflection;
using System.Text.Json;
using System.Threading.Tasks;

using Microsoft.Data.Sqlite;


namespace SettingsDb
{
public class Settings
public class Settings : SettingsDb<SqliteConnection>
{
public const string DefaultSettingsTable = "Settings";


private readonly string _connectionString;

private readonly string _settingsTable;


public Settings()
: base($"Data Source={Assembly.GetEntryAssembly().GetName().Name}.Settings.db")
{
_connectionString = $"Data Source={Assembly.GetEntryAssembly().GetName().Name}.Settings.db";
_settingsTable = DefaultSettingsTable;

InitDatabase();
}


public Settings(string databaseName, string settingsTable = DefaultSettingsTable)
: base($"Data Source={Path.GetFileNameWithoutExtension(databaseName)}.db", settingsTable)
{
if (databaseName == null)
throw new ArgumentNullException(nameof(databaseName));

_connectionString = $"Data Source={Path.GetFileNameWithoutExtension(databaseName)}.db";
_settingsTable = settingsTable;

InitDatabase();
}

/// <summary>
/// Create the settings table if it doesn't exist yet and
/// check its structure to ensure is correct.
/// </summary>
/// <exception cref="SettingsDbException"></exception>
private void InitDatabase()
{
using (var dbConnection = new SqliteConnection(_connectionString))
{
dbConnection.Open();

using (var sqlCmd = new SqliteCommand($"CREATE TABLE IF NOT EXISTS \"{_settingsTable}\" (Id INTEGER, Name TEXT UNIQUE NOT NULL, Value TEXT, PRIMARY KEY(Id))", dbConnection))
{
sqlCmd.ExecuteNonQuery();
}

if (!IsSettingsTableOk(dbConnection))
{
dbConnection.Close();
throw new SettingsDbException($"{_settingsTable}: Invalid table schema");
}

dbConnection.Close();
}
}

/// <summary>
/// Checks the schema of the settings table, to make sure its is using the correct table.
/// </summary>
/// <param name="dbConnection"></param>
/// <returns>True, if the table schema is the expected</returns>.
private bool IsSettingsTableOk(SqliteConnection dbConnection)
{
using (var sqlCmd = new SqliteCommand("SELECT COUNT() FROM PRAGMA_TABLE_INFO(@SettingsTable)", dbConnection))
{
sqlCmd.Parameters.Add(new SqliteParameter("SettingsTable", _settingsTable));

if ((long)sqlCmd.ExecuteScalar() != 3)
return false;

sqlCmd.CommandText = $"SELECT COUNT() FROM PRAGMA_TABLE_INFO(@SettingsTable) WHERE name='Id'";

if ((long)sqlCmd.ExecuteScalar() != 1)
return false;

sqlCmd.CommandText = $"SELECT COUNT() FROM PRAGMA_TABLE_INFO(@SettingsTable) WHERE name='Name'";

if ((long)sqlCmd.ExecuteScalar() != 1)
return false;

sqlCmd.CommandText = $"SELECT COUNT() FROM PRAGMA_TABLE_INFO(@SettingsTable) WHERE name='Value'";

if ((long)sqlCmd.ExecuteScalar() != 1)
return false;
}

return true;
}


public void Store<T>(string settingName, T value)
{
if (settingName == null)
throw new ArgumentNullException(nameof(settingName));

using (var dbConnection = new SqliteConnection(_connectionString))
{
dbConnection.Open();

using (var sqlCmd = new SqliteCommand($"INSERT INTO \"{_settingsTable}\" (Name, Value) VALUES (@Name, @Value) ON CONFLICT(Name) DO UPDATE SET Value = @Value", dbConnection))
{
sqlCmd.Parameters.Add(new SqliteParameter("Name", settingName));
sqlCmd.Parameters.Add(new SqliteParameter("Value", JsonSerializer.Serialize(value)));
sqlCmd.ExecuteNonQuery();
}

dbConnection.Close();
}
}


public async Task StoreAsync<T>(string settingName, T value)
{
await Task.Run(() => Store(settingName, value));
}


public T Read<T>(string settingName, T defaultValue = default)
{
if (settingName == null)
throw new ArgumentNullException(nameof(settingName));

T value = defaultValue;

using (var dbConnection = new SqliteConnection(_connectionString))
{
dbConnection.Open();

using (var sqlCmd = new SqliteCommand($"SELECT Value FROM \"{_settingsTable}\" WHERE Name = @Name LIMIT 1", dbConnection))
{
sqlCmd.Parameters.Add(new SqliteParameter("Name", settingName));

using (var reader = sqlCmd.ExecuteReader())
{
if (reader.Read())
value = JsonSerializer.Deserialize<T>(reader.GetString(0));
}
}

dbConnection.Close();
}

return value;
}


public async Task<T> ReadAsync<T>(string settingName, T defaultValue = default)
{
return await Task.Run(() => Read(settingName, defaultValue));
}


public void Clear(string settingName)
{
if (settingName == null)
throw new ArgumentNullException(nameof(settingName));

using (var dbConnection = new SqliteConnection(_connectionString))
{
dbConnection.Open();

using (var sqlCmd = new SqliteCommand($"DELETE FROM \"{_settingsTable}\" WHERE Name = @Name LIMIT 1", dbConnection))
{
sqlCmd.Parameters.Add(new SqliteParameter("Name", settingName));
sqlCmd.ExecuteNonQuery();
}

dbConnection.Close();
}
}


public async Task ClearAsync(string settingName)
{
await Task.Run(() => Clear(settingName));
}


public void ClearAll()
{
using (var dbConnection = new SqliteConnection(_connectionString))
{
dbConnection.Open();

using (var sqlCmd = new SqliteCommand($"DELETE FROM \"{_settingsTable}\"", dbConnection))
sqlCmd.ExecuteNonQuery();

dbConnection.Close();
}
}


public async Task ClearAllAsync()
{
await Task.Run(() => ClearAll());
}


public long Count()
{
long count = 0;

using (var dbConnection = new SqliteConnection(_connectionString))
{
dbConnection.Open();

using (var sqlCmd = new SqliteCommand($"SELECT COUNT() FROM \"{_settingsTable}\"", dbConnection))
count = (long)sqlCmd.ExecuteScalar();

dbConnection.Close();
}

return count;
}


public async Task<long> CountAsync()
{
return await Task.Run(() => Count());
}
}
}
Loading

0 comments on commit bb26e52

Please sign in to comment.