Skip to content

Instantly share code, notes, and snippets.

@Maxstupo
Last active February 8, 2023 20:47
Show Gist options
  • Save Maxstupo/1686bf6b3638103822cea7891bb08702 to your computer and use it in GitHub Desktop.
Save Maxstupo/1686bf6b3638103822cea7891bb08702 to your computer and use it in GitHub Desktop.
Concept for a Data Access Layer using SQLite, Dapper, and the Repository Pattern.
using Dapper;
using Dal;
using System.Collections.Generic;
using System.Threading.Tasks;
// Example implementation of the GenericRepository
// Our model.
public class Customer {
public int Id { get; set; }
public int FirstName { get; set; }
public int LastName { get; set; }
}
namespace Dal.Repositories {
public sealed class CustomerRepository : Repository<Customer> {
public CustomerRepository(IDatabaseProvider dbProvider) : base(dbProvider) { }
public override async Task<bool> Insert(Customer entity) {
int newId = await Connection.QuerySingleAsync<int>("INSERT INTO customers (firstName, lastName) VALUES (@firstName, @lastName); SELECT last_insert_rowid()", new {
firstName = entity.FirstName,
lastName = entity.LastName
}, Transaction);
entity.Id = newId;
return true;
}
public override async Task<Customer> Get(int id) {
return await Connection.QuerySingleAsync<Customer>("SELECT * FROM customers WHERE id = @id", new { id }, Transaction);
}
public override async Task<IEnumerable<Customer>> GetAll() {
return await Connection.QueryAsync<Customer>("SELECT * FROM customers", null, Transaction);
}
public override async Task<bool> Update(Customer entity) {
// Could check for unset id, and throw exception.
return (await Connection.ExecuteAsync("UPDATE customers SET firstName = @firstName, lastName = @lastName WHERE id = @id", new {
id = entity.Id,
firstName = entity.FirstName,
lastName = entity.LastName
}, Transaction)) == 1;
}
public override async Task<bool> Delete(Customer entity) {
// Could check for unset id, and throw exception.
return (await Connection.ExecuteAsync("DELETE FROM customers WHERE id = @id", new { id = entity.Id }, Transaction)) == 1;
}
}
}
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Dal.Repositories {
public interface IRepository<T> : IDisposable where T : class {
Task<bool> Insert(T entity);
Task<T> Get(int id);
Task<IEnumerable<T>> GetAll();
Task<bool> Update(T entity);
Task<bool> Delete(T entity);
void Commit();
void Revert();
}
}
using System;
using System.Collections.Generic;
using System.Data;
using System.Threading.Tasks;
namespace Dal.Repositories {
public abstract class Repository<T> : IRepository<T> where T : class {
protected IDbConnection Connection { get; }
protected IDbTransaction Transaction { get; private set; }
public GenericRepository(IDatabaseProvider dbProvider) {
Connection = dbProvider.ObtainConnection();
Connection.Open();
Transaction = Connection.BeginTransaction();
}
/// <summary>
/// Commits the current transaction or reverts on failure.
/// </summary>
public void Commit() {
try {
Transaction.Commit();
} catch (Exception) {
Revert(false);
} finally {
Transaction = Connection.BeginTransaction();
}
}
/// <summary>
/// Rolls back the transaction, and begins a new transaction afterwards.
/// </summary>
public void Revert() {
Revert(true);
}
/// <summary>
/// Rolls back the transaction and optionally begins a new transaction afterwards.
/// </summary>
/// <param name="renewTransaction">If true a new transaction will be started once the previous has been rolled back.</param>
private void Revert(bool renewTransaction) {
Transaction.Rollback();
if (renewTransaction)
Transaction = Connection.BeginTransaction();
}
public abstract Task<bool> Insert(T entity);
public abstract Task<T> Get(int id);
public abstract Task<IEnumerable<T>> GetAll();
public abstract Task<bool> Update(T entity);
public abstract Task<bool> Delete(T entity);
#region Dispose Implementation
private bool disposed = false;
~GenericRepository() {
Dispose(false);
}
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing) {
if (disposed)
return;
if (disposing) {
Transaction.Dispose();
Connection.Dispose();
}
disposed = true;
}
#endregion
}
}
using System.Data;
using System.Data.SQLite;
namespace Dal {
public interface IDatabaseProvider {
IDbConnection ObtainConnection();
}
public class SQLiteDatabase : IDatabaseProvider {
public bool IsConnected => Filepath != null;
public string Filepath { get; private set; }
public SQLiteDatabase(string filepath = null) {
if (filepath != null)
Connect(filepath);
}
protected virtual void OnConnected() { }
public virtual bool Connect(string filepath) {
if (IsConnected)
return false;
Filepath = filepath ?? throw new ArgumentNullException(nameof(filepath));
OnConnected();
return true;
}
public virtual bool Disconnect() {
if (IsConnected)
return false;
Filepath = null;
return true;
}
public IDbConnection ObtainConnection() {
if (!IsConnected)
throw new InvalidOperationException("Cant obtain a connection when disconnected!");
return new SQLiteConnection($"Data Source=\"{Filepath}\";Version=3");
}
}
}
@vicken78
Copy link

Hello, I know this is old but there is one bug in your code. Line 60 of Repository ~GenericRepository() { should be ~Repository() {

@vicken78
Copy link

Also I question why are you beginning another transaction after commit. If another transaction is needed. It should be called from code.

} finally {
Transaction = Connection.BeginTransaction();
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment