Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
234f9c2
WIP: Implement feature Optimistic Concurrency Support via Concurrency…
rent-a-developer Feb 2, 2026
67bb3b3
WIP: Implement feature Optimistic Concurrency Support via Concurrency…
rent-a-developer Feb 3, 2026
dd5c5b0
WIP: Implement feature Optimistic Concurrency Support via Concurrency…
rent-a-developer Feb 3, 2026
5c0d55b
WIP: Implement feature Optimistic Concurrency Support via Concurrency…
rent-a-developer Feb 3, 2026
22c9b31
WIP: Implement feature Optimistic Concurrency Support via Concurrency…
rent-a-developer Feb 4, 2026
0872576
WIP: Implement feature Optimistic Concurrency Support via Concurrency…
rent-a-developer Feb 4, 2026
24988d8
WIP: Implement feature Optimistic Concurrency Support via Concurrency…
rent-a-developer Feb 4, 2026
5664a4e
WIP: Implement feature Optimistic Concurrency Support via Concurrency…
rent-a-developer Feb 4, 2026
751c91b
WIP: Implement feature Optimistic Concurrency Support via Concurrency…
rent-a-developer Feb 6, 2026
abf72cd
WIP: Implement feature Optimistic Concurrency Support via Concurrency…
rent-a-developer Feb 6, 2026
8f0e613
WIP: Implement feature Optimistic Concurrency Support via Concurrency…
rent-a-developer Feb 6, 2026
e52ecc6
WIP: Implement feature Optimistic Concurrency Support via Concurrency…
rent-a-developer Feb 7, 2026
fec5b90
WIP: Implement feature Optimistic Concurrency Support via Concurrency…
rent-a-developer Feb 8, 2026
fc804c1
WIP: Implement feature Optimistic Concurrency Support via Concurrency…
rent-a-developer Feb 8, 2026
5d7befc
WIP: Implement feature Optimistic Concurrency Support via Concurrency…
rent-a-developer Feb 10, 2026
6cc3f45
WIP: Implement feature Optimistic Concurrency Support via Concurrency…
rent-a-developer Feb 10, 2026
c5030b9
WIP: Implement feature Optimistic Concurrency Support via Concurrency…
rent-a-developer Feb 11, 2026
1a65b9f
WIP: Implement feature Optimistic Concurrency Support via Concurrency…
rent-a-developer Feb 11, 2026
b8a501c
feat: Implement feature Optimistic Concurrency Support via Concurrenc…
rent-a-developer Feb 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ dotnet_diagnostic.RCS1124.severity = suggestion
# IDE0290: Use primary constructor
dotnet_diagnostic.IDE0290.severity = none

# CA2100: Review SQL queries for security vulnerabilities
dotnet_diagnostic.CA2100.severity = none

# RCS1222: Merge preprocessor directives
dotnet_diagnostic.RCS1222.severity = none

[*.{cs,vb}]
#### Naming styles ####

Expand Down
23 changes: 17 additions & 6 deletions .github/workflows/documentation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,31 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Dotnet Setup
uses: actions/setup-dotnet@v3
uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.x
dotnet-version: 8.0.x

- name: Restore dependencies
run: dotnet restore

- name: Build
run: dotnet build --no-restore --configuration Release

- run: dotnet tool update -g docfx
- run: docfx docs/docfx.json
- name: Install docfx
run: dotnet tool update -g docfx

- name: Run docfx
run: docfx docs/docfx.json

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
# Upload entire repository
path: 'docs/_site'

- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/) and
this project adheres to [Semantic Versioning](https://semver.org/).


## [1.2.0] - 2026-02-14

### Added
- Optimistic Concurrency Support via Concurrency Tokens (Fixes [issue #5](https://github.com/rent-a-developer/DbConnectionPlus/issues/5))

### Changed
- Switched benchmarks to SQLite for more stable results.

## [1.1.0] - 2026-02-01

### Added
Expand Down
2 changes: 1 addition & 1 deletion docs/DESIGN-DECISIONS.md → DESIGN-DECISIONS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# DbConnectionPlus - Design Decisions Document

**Version:** 1.1.0
**Version:** 1.2.0
**Last Updated:** February 2026
**Author:** David Liebeherr

Expand Down
2 changes: 1 addition & 1 deletion DbConnectionPlus.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<File Path="CHANGELOG.md" />
<File Path="CONTRIBUTING.md" />
<File Path="CREATERELEASE.md" />
<File Path="docs/DESIGN-DECISIONS.md" />
<File Path="DESIGN-DECISIONS.md" />
<File Path="LICENSE.md" />
<File Path="README.md" />
</Folder>
Expand Down
228 changes: 151 additions & 77 deletions README.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// ReSharper disable InvokeAsExtensionMethod
// ReSharper disable InconsistentNaming

#pragma warning disable RCS1196

namespace RentADeveloper.DbConnectionPlus.Benchmarks;

public partial class Benchmarks
{
[IterationCleanup(
Targets =
[
nameof(DeleteEntities_Command),
nameof(DeleteEntities_Dapper),
nameof(DeleteEntities_DbConnectionPlus)
]
)]
public void DeleteEntities__Cleanup() =>
this.connection.Dispose();

[IterationSetup(
Targets =
[
nameof(DeleteEntities_Command),
nameof(DeleteEntities_Dapper),
nameof(DeleteEntities_DbConnectionPlus)
]
)]
public void DeleteEntities__Setup() =>
this.SetupDatabase(DeleteEntities_EntitiesPerOperation * DeleteEntities_OperationsPerInvoke);

[Benchmark(Baseline = true)]
[BenchmarkCategory(DeleteEntities_Category)]
public void DeleteEntities_Command()
{
for (var i = 0; i < DeleteEntities_OperationsPerInvoke; i++)
{
using var command = this.connection.CreateCommand();
command.CommandText = "DELETE FROM Entity WHERE Id = @Id";

var idParameter = command.CreateParameter();
idParameter.ParameterName = "@Id";
command.Parameters.Add(idParameter);

var entities = this.entitiesInDb.Take(DeleteEntities_EntitiesPerOperation).ToList();

foreach (var entity in entities)
{
idParameter.Value = entity.Id;

command.ExecuteNonQuery();
}

this.entitiesInDb.RemoveRange(0, DeleteEntities_EntitiesPerOperation);
}
}

[Benchmark(Baseline = false)]
[BenchmarkCategory(DeleteEntities_Category)]
public void DeleteEntities_Dapper()
{
for (var i = 0; i < DeleteEntities_OperationsPerInvoke; i++)
{
var entities = this.entitiesInDb.Take(DeleteEntities_EntitiesPerOperation).ToList();

SqlMapperExtensions.Delete(this.connection, entities);

this.entitiesInDb.RemoveRange(0, DeleteEntities_EntitiesPerOperation);
}
}

[Benchmark(Baseline = false)]
[BenchmarkCategory(DeleteEntities_Category)]
public void DeleteEntities_DbConnectionPlus()
{
for (var i = 0; i < DeleteEntities_OperationsPerInvoke; i++)
{
var entities = this.entitiesInDb.Take(DeleteEntities_EntitiesPerOperation).ToList();

this.connection.DeleteEntities(entities);

this.entitiesInDb.RemoveRange(0, DeleteEntities_EntitiesPerOperation);
}
}

private const String DeleteEntities_Category = "DeleteEntities";
private const Int32 DeleteEntities_EntitiesPerOperation = 250;
private const Int32 DeleteEntities_OperationsPerInvoke = 20;
}
87 changes: 87 additions & 0 deletions benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.DeleteEntity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// ReSharper disable InvokeAsExtensionMethod
// ReSharper disable InconsistentNaming

#pragma warning disable RCS1196

namespace RentADeveloper.DbConnectionPlus.Benchmarks;

public partial class Benchmarks
{
[IterationCleanup(
Targets =
[
nameof(DeleteEntity_Command),
nameof(DeleteEntity_Dapper),
nameof(DeleteEntity_DbConnectionPlus)
]
)]
public void DeleteEntity__Cleanup() =>
this.SetupDatabase(DeleteEntity_OperationsPerInvoke);

[IterationSetup(
Targets =
[
nameof(DeleteEntity_Command),
nameof(DeleteEntity_Dapper),
nameof(DeleteEntity_DbConnectionPlus)
]
)]
public void DeleteEntity__Setup() =>
this.SetupDatabase(DeleteEntity_OperationsPerInvoke);

[Benchmark(Baseline = true, OperationsPerInvoke = DeleteEntity_OperationsPerInvoke)]
[BenchmarkCategory(DeleteEntity_Category)]
public void DeleteEntity_Command()
{
for (var i = 0; i < DeleteEntity_OperationsPerInvoke; i++)
{
var entityToDelete = this.entitiesInDb[0];

using var command = this.connection.CreateCommand();

command.CommandText = "DELETE FROM Entity WHERE Id = @Id";

var idParameter = command.CreateParameter();

idParameter.ParameterName = "@Id";
idParameter.Value = entityToDelete.Id;

command.Parameters.Add(idParameter);

command.ExecuteNonQuery();

this.entitiesInDb.Remove(entityToDelete);
}
}

[Benchmark(Baseline = false, OperationsPerInvoke = DeleteEntity_OperationsPerInvoke)]
[BenchmarkCategory(DeleteEntity_Category)]
public void DeleteEntity_Dapper()
{
for (var i = 0; i < DeleteEntity_OperationsPerInvoke; i++)
{
var entityToDelete = this.entitiesInDb[0];

SqlMapperExtensions.Delete(this.connection, entityToDelete);

this.entitiesInDb.Remove(entityToDelete);
}
}

[Benchmark(Baseline = false, OperationsPerInvoke = DeleteEntity_OperationsPerInvoke)]
[BenchmarkCategory(DeleteEntity_Category)]
public void DeleteEntity_DbConnectionPlus()
{
for (var i = 0; i < DeleteEntity_OperationsPerInvoke; i++)
{
var entityToDelete = this.entitiesInDb[0];

this.connection.DeleteEntity(entityToDelete);

this.entitiesInDb.Remove(entityToDelete);
}
}

private const String DeleteEntity_Category = "DeleteEntity";
private const Int32 DeleteEntity_OperationsPerInvoke = 8000;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// ReSharper disable InvokeAsExtensionMethod
// ReSharper disable InconsistentNaming

#pragma warning disable RCS1196

namespace RentADeveloper.DbConnectionPlus.Benchmarks;

public partial class Benchmarks
{
[GlobalCleanup(
Targets =
[
nameof(ExecuteNonQuery_Command),
nameof(ExecuteNonQuery_Dapper),
nameof(ExecuteNonQuery_DbConnectionPlus)
]
)]
public void ExecuteNonQuery__Cleanup() =>
this.connection.Dispose();

[GlobalSetup(
Targets =
[
nameof(ExecuteNonQuery_Command),
nameof(ExecuteNonQuery_Dapper),
nameof(ExecuteNonQuery_DbConnectionPlus)
]
)]
public void ExecuteNonQuery__Setup() =>
this.SetupDatabase(0);

[Benchmark(Baseline = true)]
[BenchmarkCategory(ExecuteNonQuery_Category)]
public void ExecuteNonQuery_Command()
{
using var command = this.connection.CreateCommand();

command.CommandText = "DELETE FROM Entity WHERE Id = @Id";

var idParameter = command.CreateParameter();

idParameter.ParameterName = "@Id";
idParameter.Value = -1;

command.Parameters.Add(idParameter);

command.ExecuteNonQuery();
}

[Benchmark(Baseline = false)]
[BenchmarkCategory(ExecuteNonQuery_Category)]
public void ExecuteNonQuery_Dapper() =>
SqlMapper.Execute(this.connection, "DELETE FROM Entity WHERE Id = @Id", new { Id = -1 });

[Benchmark(Baseline = false)]
[BenchmarkCategory(ExecuteNonQuery_Category)]
public void ExecuteNonQuery_DbConnectionPlus() =>
this.connection.ExecuteNonQuery($"DELETE FROM Entity WHERE Id = {Parameter(-1)}");

private const String ExecuteNonQuery_Category = "ExecuteNonQuery";
}
Loading