Last active
February 8, 2023 20:47
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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();
}