diff --git a/.editorconfig b/.editorconfig
index 133850c..d8982c2 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -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 ####
diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml
index 954fb3a..724859a 100644
--- a/.github/workflows/documentation.yml
+++ b/.github/workflows/documentation.yml
@@ -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
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fea0f5a..e056b49 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/docs/DESIGN-DECISIONS.md b/DESIGN-DECISIONS.md
similarity index 99%
rename from docs/DESIGN-DECISIONS.md
rename to DESIGN-DECISIONS.md
index e678f2c..9ced144 100644
--- a/docs/DESIGN-DECISIONS.md
+++ b/DESIGN-DECISIONS.md
@@ -1,6 +1,6 @@
# DbConnectionPlus - Design Decisions Document
-**Version:** 1.1.0
+**Version:** 1.2.0
**Last Updated:** February 2026
**Author:** David Liebeherr
diff --git a/DbConnectionPlus.slnx b/DbConnectionPlus.slnx
index bffd51f..8327e94 100644
--- a/DbConnectionPlus.slnx
+++ b/DbConnectionPlus.slnx
@@ -4,7 +4,7 @@
-
+
diff --git a/README.md b/README.md
index 5a7df58..01d051c 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
[](https://www.nuget.org/packages/RentADeveloper.DbConnectionPlus/)
[](https://sonarcloud.io/summary/new_code?id=rent-a-developer_DbConnectionPlus)
[](LICENSE.md)
-
+
#  DbConnectionPlus
A lightweight .NET ORM and extension library for the type
@@ -9,11 +9,14 @@ A lightweight .NET ORM and extension library for the type
that adds high-performance, type-safe helpers to reduce boilerplate code, boost productivity, and make working with
SQL databases in C# more enjoyable.
+If you frequently write SQL queries in your C# code and want to avoid boilerplate code, you will love DbConnectionPlus!
+
Highlights:
-- Parameterized interpolated-string support
-- On-the-fly temporary tables from in-memory collections
+- [Parameterized interpolated-string support](#parameters-via-interpolated-strings)
+- [On-the-fly temporary tables](#on-the-fly-temporary-tables-via-interpolated-strings) from in-memory collections
- Entity mapping helpers (insert, update, delete, query)
- Designed to be used in synchronous and asynchronous code paths
+- Minimal performance and allocation overhead
The following database systems are supported out of the box:
- MySQL (via [MySqlConnector](https://www.nuget.org/packages/MySqlConnector/))
@@ -182,7 +185,7 @@ This prevents SQL injection and keeps the SQL readable.
> `RentADeveloper.DbConnectionPlus.DatabaseAdapters.Oracle.OracleDatabaseAdapter.AllowTemporaryTables` to `true`.
> [!NOTE]
-> **Note for MySQL users**
+> **Note for MySQL users**
> The temporary tables feature of DbConnectionPlus uses `MySqlBulkCopy` to populate temporary tables.
> Therefore, the option `AllowLoadLocalInfile=true` must be set in the connection string and the server side
> option `local_infile` must be enabled (e.g. via the statement `SET GLOBAL local_infile=1`).
@@ -378,7 +381,7 @@ DbConnectionExtensions.Configure(config =>
```
> [!NOTE]
-> `DbConnectionExtensions.Configure` can only be called once.
+> To prevent multi-threading issues `DbConnectionExtensions.Configure` can only be called once during the application lifetime.
> After it has been called the configuration of DbConnectionPlus is frozen and cannot be changed anymore.
#### EnumSerializationMode
@@ -488,9 +491,47 @@ DbConnectionExtensions.Configure(config =>
config.Entity()
.Property(a => a.IsOnSale)
.IsIgnored();
+
+ config.Entity()
+ .Property(a => a.Version)
+ .IsRowVersion();
+
+ config.Entity()
+ .Property(a => a.ConcurrencyToken)
+ .IsConcurrencyToken();
});
```
+###### `Entity()`
+Use this method to start configuring the mapping for the entity type `TEntity`.
+
+###### `ToTable(tableName)`
+Use this method to specify the name of the table where entities of the entity type are stored in the database.
+
+###### `Property(propertyExpression)`
+Use this method to start configuring the mapping for a property of the entity type.
+
+###### `HasColumnName(columnName)`
+Use this method to specify the name of the column where the property is stored in the database.
+
+###### `IsKey()`
+Use this method to specify that the property is part of the key by which entities of the entity type are identified.
+
+###### `IsIdentity()`
+Use this method to specify that the property is generated by the database on insert.
+
+###### `IsComputed()`
+Use this method to specify that the property is generated by the database on insert and update.
+
+###### `IsRowVersion()`
+Use this method to specify that the property is a native database-generated concurrency token.
+
+###### `IsConcurrencyToken()`
+Use this method to specify that the property is an application-managed concurrency token.
+
+###### `IsIgnored()`
+Use this method to specify that the property should be ignored and not mapped to a column.
+
##### Data annotation attributes
You can use the following attributes to configure how entity types are mapped to database tables and columns:
@@ -538,6 +579,31 @@ class Product
Properties marked with this attribute are ignored (unless `DatabaseGeneratedOption.None` is used) when inserting new
entities into the database or updating existing entities.
When an entity is inserted or updated, the value of the property is read back from the database and set on the entity.
+
+###### `System.ComponentModel.DataAnnotations.TimestampAttribute`
+Use this attribute to specify that a property of an entity type is a native database-generated concurrency token:
+```csharp
+class Product
+{
+ [Timestamp]
+ public Byte[] Version { get; set; }
+}
+```
+Properties marked with this attribute will be checked during delete and update operations.
+When their values in the database do not match the original values, the delete or update will fail.
+When an entity is inserted or updated, the value of the property is read back from the database and set on the entity.
+
+###### `System.ComponentModel.DataAnnotations.ConcurrencyCheckAttribute`
+Use this attribute to specify that a property of an entity type is a application-managed concurrency token:
+```csharp
+class Product
+{
+ [ConcurrencyCheck]
+ public Byte[] ConcurrencyToken { get; set; }
+}
+```
+Properties marked with this attribute will be checked during delete and update operations.
+When their values in the database do not match the original values, the delete or update will fail.
###### `System.ComponentModel.DataAnnotations.Schema.NotMappedAttribute`
Use this attribute to specify that a property of an entity type should be ignored and not mapped to a column:
@@ -1141,88 +1207,96 @@ See [SqlServerDatabaseAdapter](https://github.com/rent-a-developer/DbConnectionP
for an example implementation of a database adapter.
## Benchmarks
-DbConnectionPlus is designed to have a minimal performance and allocation overhead compared to using
-`DbCommand` manually.
+DbConnectionPlus is designed to have a minimal performance and allocation overhead compared to using `DbCommand`
+manually.
+
+All benchmarks are performed using SQLite in-memory databases, which is a worst-case scenario for DbConnectionPlus
+because the overhead of using DbConnectionPlus is more noticeable when the executed SQL statements are very fast.
```
+
BenchmarkDotNet v0.15.8, Windows 11 (10.0.26100.7623/24H2/2024Update/HudsonValley)
12th Gen Intel Core i9-12900K 3.19GHz, 1 CPU, 24 logical and 16 physical cores
.NET SDK 10.0.102
[Host] : .NET 8.0.23 (8.0.23, 8.0.2325.60607), X64 RyuJIT x86-64-v3
- Job-ADQEJE : .NET 8.0.23 (8.0.23, 8.0.2325.60607), X64 RyuJIT x86-64-v3
-
-MinIterationTime=100ms OutlierMode=DontRemove Server=True
-InvocationCount=1 MaxIterationCount=20 UnrollFactor=1
-WarmupCount=10
-```
+ Job-HTGOBL : .NET 8.0.23 (8.0.23, 8.0.2325.60607), X64 RyuJIT x86-64-v3
+ Job-CYDVOR : .NET 8.0.23 (8.0.23, 8.0.2325.60607), X64 RyuJIT x86-64-v3
-| Method | Mean | Error | StdDev | Median | P90 | P95 | Ratio | RatioSD | Allocated | Alloc Ratio |
-|----------------------------------------------- |-------------:|-------------:|-------------:|-------------:|-------------:|-------------:|-------------:|--------:|----------:|------------:|
-| **DeleteEntities_Manually** | **14,672.73 μs** | **3,387.316 μs** | **3,900.839 μs** | **14,243.07 μs** | **19,825.26 μs** | **20,144.38 μs** | **baseline** | **** | **101.62 KB** | **** |
-| DeleteEntities_DbConnectionPlus | 6,717.47 μs | 721.336 μs | 830.692 μs | 6,372.96 μs | 7,698.83 μs | 8,539.65 μs | 2.21x faster | 0.62x | 17.17 KB | 5.92x less |
-| | | | | | | | | | | |
-| **DeleteEntity_Manually** | **188.68 μs** | **24.244 μs** | **27.920 μs** | **198.98 μs** | **212.97 μs** | **217.73 μs** | **baseline** | **** | **2.1 KB** | **** |
-| DeleteEntity_DbConnectionPlus | 191.09 μs | 27.642 μs | 31.833 μs | 197.78 μs | 230.76 μs | 235.60 μs | 1.04x slower | 0.24x | 2.1 KB | 1.00x more |
-| | | | | | | | | | | |
-| **ExecuteNonQuery_Manually** | **158.13 μs** | **24.189 μs** | **27.856 μs** | **157.52 μs** | **169.30 μs** | **178.27 μs** | **baseline** | **** | **2.1 KB** | **** |
-| ExecuteNonQuery_DbConnectionPlus | 165.12 μs | 13.165 μs | 15.161 μs | 166.52 μs | 177.50 μs | 180.82 μs | 1.07x slower | 0.19x | 2.81 KB | 1.33x more |
-| | | | | | | | | | | |
-| **ExecuteReader_Manually** | **183.91 μs** | **9.815 μs** | **11.303 μs** | **179.93 μs** | **203.46 μs** | **211.49 μs** | **baseline** | **** | **50.54 KB** | **** |
-| ExecuteReader_DbConnectionPlus | 173.84 μs | 4.810 μs | 5.539 μs | 173.00 μs | 180.74 μs | 186.21 μs | 1.06x faster | 0.07x | 50.83 KB | 1.01x more |
-| | | | | | | | | | | |
-| **ExecuteScalar_Manually** | **73.79 μs** | **2.411 μs** | **2.777 μs** | **73.54 μs** | **78.35 μs** | **78.58 μs** | **baseline** | **** | **3.04 KB** | **** |
-| ExecuteScalar_DbConnectionPlus | 77.81 μs | 5.661 μs | 6.519 μs | 76.63 μs | 81.00 μs | 87.09 μs | 1.06x slower | 0.09x | 3.77 KB | 1.24x more |
-| | | | | | | | | | | |
-| **Exists_Manually** | **56.36 μs** | **13.725 μs** | **15.806 μs** | **48.61 μs** | **78.16 μs** | **86.30 μs** | **baseline** | **** | **2.63 KB** | **** |
-| Exists_DbConnectionPlus | 51.36 μs | 2.946 μs | 3.392 μs | 50.43 μs | 53.15 μs | 55.69 μs | 1.10x faster | 0.31x | 3.34 KB | 1.27x more |
-| | | | | | | | | | | |
-| **InsertEntities_Manually** | **17,619.46 μs** | **2,472.686 μs** | **2,847.548 μs** | **18,691.91 μs** | **20,290.38 μs** | **20,702.41 μs** | **baseline** | **** | **517.03 KB** | **** |
-| InsertEntities_DbConnectionPlus | 21,575.08 μs | 2,280.957 μs | 2,626.754 μs | 23,062.28 μs | 23,656.92 μs | 24,692.07 μs | 1.25x slower | 0.24x | 437.87 KB | 1.18x less |
-| | | | | | | | | | | |
-| **InsertEntity_Manually** | **256.13 μs** | **16.084 μs** | **18.522 μs** | **257.27 μs** | **264.82 μs** | **285.02 μs** | **baseline** | **** | **8.57 KB** | **** |
-| InsertEntity_DbConnectionPlus | 280.06 μs | 37.113 μs | 42.740 μs | 259.51 μs | 341.86 μs | 355.55 μs | 1.10x slower | 0.18x | 8.72 KB | 1.02x more |
-| | | | | | | | | | | |
-| **Parameter_Manually** | **57.55 μs** | **10.088 μs** | **11.618 μs** | **56.99 μs** | **65.92 μs** | **67.72 μs** | **baseline** | **** | **5.43 KB** | **** |
-| Parameter_DbConnectionPlus | 52.35 μs | 5.561 μs | 6.404 μs | 50.31 μs | 55.65 μs | 57.76 μs | 1.11x faster | 0.24x | 7.34 KB | 1.35x more |
-| | | | | | | | | | | |
-| **Query_Dynamic_Manually** | **315.14 μs** | **12.468 μs** | **14.358 μs** | **312.52 μs** | **327.40 μs** | **333.20 μs** | **baseline** | **** | **195.41 KB** | **** |
-| Query_Dynamic_DbConnectionPlus | 203.51 μs | 16.883 μs | 19.442 μs | 197.45 μs | 215.26 μs | 224.23 μs | 1.56x faster | 0.13x | 136.38 KB | 1.43x less |
-| | | | | | | | | | | |
-| **Query_Scalars_Manually** | **74.03 μs** | **2.179 μs** | **2.510 μs** | **73.53 μs** | **77.74 μs** | **77.97 μs** | **baseline** | **** | **2.11 KB** | **** |
-| Query_Scalars_DbConnectionPlus | 90.07 μs | 11.385 μs | 13.111 μs | 89.36 μs | 102.01 μs | 104.18 μs | 1.22x slower | 0.18x | 7.26 KB | 3.44x more |
-| | | | | | | | | | | |
-| **Query_Entities_Manually** | **251.81 μs** | **6.020 μs** | **6.933 μs** | **250.85 μs** | **260.06 μs** | **262.91 μs** | **baseline** | **** | **51.3 KB** | **** |
-| Query_Entities_DbConnectionPlus | 263.71 μs | 6.792 μs | 7.822 μs | 260.52 μs | 271.74 μs | 274.68 μs | 1.05x slower | 0.04x | 54.37 KB | 1.06x more |
-| | | | | | | | | | | |
-| **Query_ValueTuples_Manually** | **180.00 μs** | **8.115 μs** | **9.345 μs** | **177.02 μs** | **185.67 μs** | **194.46 μs** | **baseline** | **** | **18.07 KB** | **** |
-| Query_ValueTuples_DbConnectionPlus | 190.84 μs | 9.986 μs | 11.499 μs | 188.72 μs | 200.44 μs | 217.74 μs | 1.06x slower | 0.08x | 29.45 KB | 1.63x more |
-| | | | | | | | | | | |
-| **TemporaryTable_ComplexObjects_Manually** | **8,267.76 μs** | **2,480.979 μs** | **2,857.099 μs** | **7,983.17 μs** | **11,502.49 μs** | **11,944.48 μs** | **baseline** | **** | **132.52 KB** | **** |
-| TemporaryTable_ComplexObjects_DbConnectionPlus | 6,636.36 μs | 614.018 μs | 707.104 μs | 6,582.66 μs | 7,309.96 μs | 7,595.85 μs | 1.26x faster | 0.44x | 137.92 KB | 1.04x more |
-| | | | | | | | | | | |
-| **TemporaryTable_ScalarValues_Manually** | **4,784.75 μs** | **566.815 μs** | **652.745 μs** | **4,620.07 μs** | **4,950.02 μs** | **5,609.07 μs** | **baseline** | **** | **177.18 KB** | **** |
-| TemporaryTable_ScalarValues_DbConnectionPlus | 4,897.28 μs | 393.307 μs | 452.933 μs | 4,735.95 μs | 5,696.50 μs | 5,701.09 μs | 1.04x slower | 0.14x | 304.21 KB | 1.72x more |
-| | | | | | | | | | | |
-| **UpdateEntities_Manually** | **23,744.24 μs** | **3,367.021 μs** | **3,877.466 μs** | **22,203.37 μs** | **30,059.37 μs** | **32,188.80 μs** | **baseline** | **** | **530.26 KB** | **** |
-| UpdateEntities_DbConnectionPlus | 34,624.61 μs | 3,734.617 μs | 4,300.790 μs | 34,084.29 μs | 35,478.88 μs | 39,301.47 μs | 1.49x slower | 0.28x | 450.27 KB | 1.18x less |
-| | | | | | | | | | | |
-| **UpdateEntity_Manually** | **300.87 μs** | **28.337 μs** | **32.633 μs** | **291.67 μs** | **350.76 μs** | **366.50 μs** | **baseline** | **** | **9.5 KB** | **** |
-| UpdateEntity_DbConnectionPlus | 344.98 μs | 49.278 μs | 56.749 μs | 356.24 μs | 393.93 μs | 408.69 μs | 1.16x slower | 0.22x | 9.67 KB | 1.02x more |
-
-Please keep in mind that benchmarking is tricky when SQL Server is involved.
-So take these benchmark results with a grain of salt.
+Server=True MaxIterationCount=20
-### Running the benchmarks
-To run the benchmarks, ensure you have an SQL Server instance available.
-The benchmarks will create a database named `DbConnectionPlusTests`, so make sure your SQL user has the necessary
-rights.
-
-Set the environment variable `ConnectionString_SqlServer` to the connection string to the SQL Server instance:
-```shell
-set ConnectionString_SqlServer="Data Source=.\SqlServer;Integrated Security=True;Encrypt=False;MultipleActiveResultSets=True"
```
+| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
+|----------------------------------------------- |---------------:|--------------:|--------------:|-------------:|--------:|--------:|-----------:|------------:|
+| **DeleteEntities_DbCommand** | **223,628.340 μs** | **3,095.0138 μs** | **2,895.0778 μs** | **baseline** | **** | **-** | **1412000 B** | **** |
+| DeleteEntities_Dapper | 218,830.227 μs | 3,503.0632 μs | 3,276.7675 μs | 1.02x faster | 0.02x | - | 2710584 B | 1.92x more |
+| DeleteEntities_DbConnectionPlus | 225,066.323 μs | 3,714.7493 μs | 3,474.7788 μs | 1.01x slower | 0.02x | - | 2560160 B | 1.81x more |
+| | | | | | | | | |
+| **DeleteEntity_DbCommand** | **73.778 μs** | **0.6793 μs** | **0.6022 μs** | **baseline** | **** | **-** | **768 B** | **** |
+| DeleteEntity_Dapper | 75.820 μs | 1.1366 μs | 1.0632 μs | 1.03x slower | 0.02x | - | 1704 B | 2.22x more |
+| DeleteEntity_DbConnectionPlus | 75.308 μs | 0.9598 μs | 0.8014 μs | 1.02x slower | 0.01x | - | 1312 B | 1.71x more |
+| | | | | | | | | |
+| **ExecuteNonQuery_DbCommand** | **88.582 μs** | **19.6701 μs** | **22.6521 μs** | **baseline** | **** | **-** | **768 B** | **** |
+| ExecuteNonQuery_Dapper | 72.316 μs | 0.9734 μs | 0.9106 μs | 1.23x faster | 0.31x | - | 1072 B | 1.40x more |
+| ExecuteNonQuery_DbConnectionPlus | 71.852 μs | 0.9360 μs | 0.8755 μs | 1.23x faster | 0.31x | - | 1704 B | 2.22x more |
+| | | | | | | | | |
+| **ExecuteReader_DbCommand** | **650.394 μs** | **5.8340 μs** | **5.4571 μs** | **baseline** | **** | **0.9766** | **857945 B** | **** |
+| ExecuteReader_Dapper | 649.543 μs | 6.7084 μs | 6.2750 μs | 1.00x faster | 0.01x | 0.9766 | 857417 B | 1.00x less |
+| ExecuteReader_DbConnectionPlus | 657.459 μs | 6.0157 μs | 5.6271 μs | 1.01x slower | 0.01x | - | 859169 B | 1.00x more |
+| | | | | | | | | |
+| **ExecuteScalar_DbCommand** | **2.090 μs** | **0.0199 μs** | **0.0186 μs** | **baseline** | **** | **-** | **1072 B** | **** |
+| ExecuteScalar_Dapper | 2.410 μs | 0.0239 μs | 0.0199 μs | 1.15x slower | 0.01x | - | 1464 B | 1.37x more |
+| ExecuteScalar_DbConnectionPlus | 2.779 μs | 0.0356 μs | 0.0333 μs | 1.33x slower | 0.02x | 0.0038 | 2128 B | 1.99x more |
+| | | | | | | | | |
+| **Exists_DbCommand** | **1.824 μs** | **0.0183 μs** | **0.0172 μs** | **baseline** | **** | **-** | **1000 B** | **** |
+| Exists_Dapper | 2.102 μs | 0.0165 μs | 0.0138 μs | 1.15x slower | 0.01x | - | 1336 B | 1.34x more |
+| Exists_DbConnectionPlus | 2.457 μs | 0.0416 μs | 0.0389 μs | 1.35x slower | 0.02x | 0.0038 | 1944 B | 1.94x more |
+| | | | | | | | | |
+| **InsertEntities_DbCommand** | **5,739.618 μs** | **29.8202 μs** | **26.4348 μs** | **baseline** | **** | **15.6250** | **8586537 B** | **** |
+| InsertEntities_Dapper | 6,495.939 μs | 58.2679 μs | 54.5038 μs | 1.13x slower | 0.01x | 15.6250 | 9602018 B | 1.12x more |
+| InsertEntities_DbConnectionPlus | 6,518.117 μs | 56.0311 μs | 52.4115 μs | 1.14x slower | 0.01x | 15.6250 | 9468391 B | 1.10x more |
+| | | | | | | | | |
+| **InsertEntity_DbCommand** | **32.660 μs** | **0.4831 μs** | **0.4282 μs** | **baseline** | **** | **-** | **45267 B** | **** |
+| InsertEntity_Dapper | 43.533 μs | 0.6281 μs | 0.5875 μs | 1.33x slower | 0.02x | - | 60446 B | 1.34x more |
+| InsertEntity_DbConnectionPlus | 37.122 μs | 0.4297 μs | 0.3809 μs | 1.14x slower | 0.02x | - | 50039 B | 1.11x more |
+| | | | | | | | | |
+| **Parameter_DbCommand** | **2.672 μs** | **0.0394 μs** | **0.0350 μs** | **baseline** | **** | **0.0038** | **1864 B** | **** |
+| Parameter_Dapper | 3.395 μs | 0.0375 μs | 0.0333 μs | 1.27x slower | 0.02x | - | 2984 B | 1.60x more |
+| Parameter_DbConnectionPlus | 4.102 μs | 0.0802 μs | 0.0711 μs | 1.54x slower | 0.03x | 0.0076 | 4464 B | 2.39x more |
+| | | | | | | | | |
+| **Query_Dynamic_DbCommand** | **793.944 μs** | **6.6057 μs** | **6.1790 μs** | **baseline** | **** | **1.9531** | **975378 B** | **** |
+| Query_Dynamic_Dapper | 246.576 μs | 3.5537 μs | 3.3241 μs | 3.22x faster | 0.05x | - | 91560 B | 10.65x less |
+| Query_Dynamic_DbConnectionPlus | 323.036 μs | 3.4959 μs | 3.2701 μs | 2.46x faster | 0.03x | - | 149368 B | 6.53x less |
+| | | | | | | | | |
+| **Query_Entities_DbCommand** | **652.021 μs** | **5.8298 μs** | **5.1680 μs** | **baseline** | **** | **0.9766** | **858825 B** | **** |
+| Query_Entities_Dapper | 287.951 μs | 4.6809 μs | 4.3785 μs | 2.26x faster | 0.04x | - | 98056 B | 8.76x less |
+| Query_Entities_DbConnectionPlus | 307.558 μs | 3.6913 μs | 3.4528 μs | 2.12x faster | 0.03x | - | 99560 B | 8.63x less |
+| | | | | | | | | |
+| **Query_Scalars_DbCommand** | **80.531 μs** | **0.6016 μs** | **0.5333 μs** | **baseline** | **** | **-** | **17384 B** | **** |
+| Query_Scalars_Dapper | 113.098 μs | 1.2660 μs | 1.1842 μs | 1.40x slower | 0.02x | - | 37072 B | 2.13x more |
+| Query_Scalars_DbConnectionPlus | 111.844 μs | 1.3095 μs | 1.2249 μs | 1.39x slower | 0.02x | - | 32464 B | 1.87x more |
+| | | | | | | | | |
+| **Query_ValueTuples_DbCommand** | **120.574 μs** | **1.3583 μs** | **1.2041 μs** | **baseline** | **** | **-** | **49200 B** | **** |
+| Query_ValueTuples_Dapper | 142.430 μs | 1.7777 μs | 1.6628 μs | 1.18x slower | 0.02x | - | 73584 B | 1.50x more |
+| Query_ValueTuples_DbConnectionPlus | 145.854 μs | 2.0993 μs | 1.9637 μs | 1.21x slower | 0.02x | - | 58752 B | 1.19x more |
+| | | | | | | | | |
+| **TemporaryTable_ComplexObjects_DbCommand** | **9,029.496 μs** | **109.0053 μs** | **101.9636 μs** | **baseline** | **** | **-** | **12899278 B** | **** |
+| TemporaryTable_ComplexObjects_Dapper | 8,120.420 μs | 106.3880 μs | 99.5154 μs | 1.11x faster | 0.02x | 15.6250 | 10968565 B | 1.18x less |
+| TemporaryTable_ComplexObjects_DbConnectionPlus | 9,348.308 μs | 104.4113 μs | 97.6664 μs | 1.04x slower | 0.02x | 15.6250 | 12074972 B | 1.07x less |
+| | | | | | | | | |
+| **TemporaryTable_ScalarValues_DbCommand** | **5,080.907 μs** | **101.4218 μs** | **99.6098 μs** | **baseline** | **** | **-** | **1723968 B** | **** |
+| TemporaryTable_ScalarValues_Dapper | 5,165.566 μs | 48.4921 μs | 45.3595 μs | 1.02x slower | 0.02x | - | 1764568 B | 1.02x more |
+| TemporaryTable_ScalarValues_DbConnectionPlus | 6,644.777 μs | 125.8456 μs | 123.5973 μs | 1.31x slower | 0.03x | - | 2806416 B | 1.63x more |
+| | | | | | | | | |
+| **UpdateEntities_DbCommand** | **3,113.671 μs** | **29.0143 μs** | **27.1400 μs** | **baseline** | **** | **-** | **4325925 B** | **** |
+| UpdateEntities_Dapper | 3,467.305 μs | 39.2788 μs | 36.7414 μs | 1.11x slower | 0.01x | 7.8125 | 4872590 B | 1.13x more |
+| UpdateEntities_DbConnectionPlus | 3,602.616 μs | 46.1440 μs | 43.1631 μs | 1.16x slower | 0.02x | - | 4766990 B | 1.10x more |
+| | | | | | | | | |
+| **UpdateEntity_DbCommand** | **35.296 μs** | **0.2973 μs** | **0.2636 μs** | **baseline** | **** | **-** | **45469 B** | **** |
+| UpdateEntity_Dapper | 40.460 μs | 0.8024 μs | 0.7113 μs | 1.15x slower | 0.02x | - | 54299 B | 1.19x more |
+| UpdateEntity_DbConnectionPlus | 38.464 μs | 0.3082 μs | 0.2883 μs | 1.09x slower | 0.01x | - | 50254 B | 1.11x more |
-Then run the following command:
+### Running the benchmarks
+To run the benchmarks, run the following command:
```shell
dotnet run --configuration Release --project benchmarks\DbConnectionPlus.Benchmarks\DbConnectionPlus.Benchmarks.csproj
```
diff --git a/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.DeleteEntities.cs b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.DeleteEntities.cs
new file mode 100644
index 0000000..437accf
--- /dev/null
+++ b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.DeleteEntities.cs
@@ -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;
+}
diff --git a/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.DeleteEntity.cs b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.DeleteEntity.cs
new file mode 100644
index 0000000..1907490
--- /dev/null
+++ b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.DeleteEntity.cs
@@ -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;
+}
diff --git a/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.ExecuteNonQuery.cs b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.ExecuteNonQuery.cs
new file mode 100644
index 0000000..9dfba35
--- /dev/null
+++ b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.ExecuteNonQuery.cs
@@ -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";
+}
diff --git a/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.ExecuteReader.cs b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.ExecuteReader.cs
new file mode 100644
index 0000000..2d1db3c
--- /dev/null
+++ b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.ExecuteReader.cs
@@ -0,0 +1,85 @@
+// ReSharper disable InvokeAsExtensionMethod
+// ReSharper disable InconsistentNaming
+
+#pragma warning disable RCS1196
+
+namespace RentADeveloper.DbConnectionPlus.Benchmarks;
+
+public partial class Benchmarks
+{
+ [GlobalCleanup(
+ Targets =
+ [
+ nameof(ExecuteReader_Command),
+ nameof(ExecuteReader_Dapper),
+ nameof(ExecuteReader_DbConnectionPlus)
+ ]
+ )]
+ public void ExecuteReader__Cleanup() =>
+ this.connection.Dispose();
+
+ [GlobalSetup(
+ Targets =
+ [
+ nameof(ExecuteReader_Command),
+ nameof(ExecuteReader_Dapper),
+ nameof(ExecuteReader_DbConnectionPlus)
+ ]
+ )]
+ public void ExecuteReader__Setup() =>
+ this.SetupDatabase(100);
+
+ [Benchmark(Baseline = true)]
+ [BenchmarkCategory(ExecuteReader_Category)]
+ public List ExecuteReader_Command()
+ {
+ var result = new List();
+
+ using var command = this.connection.CreateCommand();
+
+ command.CommandText = "SELECT * FROM Entity";
+
+ using var dataReader = command.ExecuteReader();
+
+ while (dataReader.Read())
+ {
+ result.Add(ReadEntity(dataReader));
+ }
+
+ return result;
+ }
+
+ [Benchmark(Baseline = false)]
+ [BenchmarkCategory(ExecuteReader_Category)]
+ public List ExecuteReader_Dapper()
+ {
+ var result = new List();
+
+ using var dataReader = SqlMapper.ExecuteReader(this.connection, "SELECT * FROM Entity");
+
+ while (dataReader.Read())
+ {
+ result.Add(ReadEntity(dataReader));
+ }
+
+ return result;
+ }
+
+ [Benchmark(Baseline = false)]
+ [BenchmarkCategory(ExecuteReader_Category)]
+ public List ExecuteReader_DbConnectionPlus()
+ {
+ var result = new List();
+
+ using var dataReader = this.connection.ExecuteReader("SELECT * FROM Entity");
+
+ while (dataReader.Read())
+ {
+ result.Add(ReadEntity(dataReader));
+ }
+
+ return result;
+ }
+
+ private const String ExecuteReader_Category = "ExecuteReader";
+}
diff --git a/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.ExecuteScalar.cs b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.ExecuteScalar.cs
new file mode 100644
index 0000000..b56cc05
--- /dev/null
+++ b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.ExecuteScalar.cs
@@ -0,0 +1,76 @@
+// ReSharper disable InvokeAsExtensionMethod
+// ReSharper disable InconsistentNaming
+
+#pragma warning disable RCS1196
+
+namespace RentADeveloper.DbConnectionPlus.Benchmarks;
+
+public partial class Benchmarks
+{
+ [GlobalCleanup(
+ Targets =
+ [
+ nameof(ExecuteScalar_Command),
+ nameof(ExecuteScalar_Dapper),
+ nameof(ExecuteScalar_DbConnectionPlus)
+ ]
+ )]
+ public void ExecuteScalar__Cleanup() =>
+ this.connection.Dispose();
+
+ [GlobalSetup(
+ Targets =
+ [
+ nameof(ExecuteScalar_Command),
+ nameof(ExecuteScalar_Dapper),
+ nameof(ExecuteScalar_DbConnectionPlus)
+ ]
+ )]
+ public void ExecuteScalar__Setup() =>
+ this.SetupDatabase(1);
+
+ [Benchmark(Baseline = true)]
+ [BenchmarkCategory(ExecuteScalar_Category)]
+ public String ExecuteScalar_Command()
+ {
+ var entity = this.entitiesInDb[0];
+
+ using var command = this.connection.CreateCommand();
+
+ command.CommandText = "SELECT StringValue FROM Entity WHERE Id = @Id";
+
+ var idParameter = command.CreateParameter();
+ idParameter.ParameterName = "@Id";
+ idParameter.Value = entity.Id;
+
+ command.Parameters.Add(idParameter);
+
+ return (String)command.ExecuteScalar()!;
+ }
+
+ [Benchmark(Baseline = false)]
+ [BenchmarkCategory(ExecuteScalar_Category)]
+ public String ExecuteScalar_Dapper()
+ {
+ var entity = this.entitiesInDb[0];
+
+ return SqlMapper.ExecuteScalar(
+ this.connection,
+ "SELECT StringValue FROM Entity WHERE Id = @Id",
+ new { entity.Id }
+ )!;
+ }
+
+ [Benchmark(Baseline = false)]
+ [BenchmarkCategory(ExecuteScalar_Category)]
+ public String ExecuteScalar_DbConnectionPlus()
+ {
+ var entity = this.entitiesInDb[0];
+
+ return this.connection.ExecuteScalar(
+ $"SELECT StringValue FROM Entity WHERE Id = {Parameter(entity.Id)}"
+ );
+ }
+
+ private const String ExecuteScalar_Category = "ExecuteScalar";
+}
diff --git a/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.Exists.cs b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.Exists.cs
new file mode 100644
index 0000000..65e6233
--- /dev/null
+++ b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.Exists.cs
@@ -0,0 +1,77 @@
+// ReSharper disable InvokeAsExtensionMethod
+// ReSharper disable InconsistentNaming
+
+#pragma warning disable RCS1196
+
+namespace RentADeveloper.DbConnectionPlus.Benchmarks;
+
+public partial class Benchmarks
+{
+ [GlobalCleanup(
+ Targets =
+ [
+ nameof(Exists_Command),
+ nameof(Exists_Dapper),
+ nameof(Exists_DbConnectionPlus)
+ ]
+ )]
+ public void Exists__Cleanup() =>
+ this.connection.Dispose();
+
+ [GlobalSetup(
+ Targets =
+ [
+ nameof(Exists_Command),
+ nameof(Exists_Dapper),
+ nameof(Exists_DbConnectionPlus)
+ ]
+ )]
+ public void Exists__Setup() =>
+ this.SetupDatabase(1);
+
+ [Benchmark(Baseline = true)]
+ [BenchmarkCategory(Exists_Category)]
+ public Boolean Exists_Command()
+ {
+ var entityId = this.entitiesInDb[0].Id;
+
+ using var command = this.connection.CreateCommand();
+ command.CommandText = "SELECT 1 FROM Entity WHERE Id = @Id";
+
+ var idParameter = command.CreateParameter();
+ idParameter.ParameterName = "@Id";
+ idParameter.Value = entityId;
+
+ command.Parameters.Add(idParameter);
+
+ using var dataReader = command.ExecuteReader(CommandBehavior.SingleResult | CommandBehavior.SingleRow);
+
+ return dataReader.Read();
+ }
+
+ [Benchmark(Baseline = false)]
+ [BenchmarkCategory(Exists_Category)]
+ public Boolean Exists_Dapper()
+ {
+ var entityId = this.entitiesInDb[0].Id;
+
+ using var dataReader = SqlMapper.ExecuteReader(
+ this.connection,
+ "SELECT 1 FROM Entity WHERE Id = @Id",
+ new { Id = entityId }
+ );
+
+ return dataReader.Read();
+ }
+
+ [Benchmark(Baseline = false)]
+ [BenchmarkCategory(Exists_Category)]
+ public Boolean Exists_DbConnectionPlus()
+ {
+ var entityId = this.entitiesInDb[0].Id;
+
+ return this.connection.Exists($"SELECT 1 FROM Entity WHERE Id = {Parameter(entityId)}");
+ }
+
+ private const String Exists_Category = "Exists";
+}
diff --git a/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.InsertEntities.cs b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.InsertEntities.cs
new file mode 100644
index 0000000..86d4e9b
--- /dev/null
+++ b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.InsertEntities.cs
@@ -0,0 +1,126 @@
+// ReSharper disable InvokeAsExtensionMethod
+// ReSharper disable InconsistentNaming
+
+#pragma warning disable RCS1196
+
+namespace RentADeveloper.DbConnectionPlus.Benchmarks;
+
+public partial class Benchmarks
+{
+ [GlobalCleanup(
+ Targets =
+ [
+ nameof(InsertEntities_Command),
+ nameof(InsertEntities_Dapper),
+ nameof(InsertEntities_DbConnectionPlus)
+ ]
+ )]
+ public void InsertEntities__Cleanup() =>
+ this.connection.Dispose();
+
+ [GlobalSetup(
+ Targets =
+ [
+ nameof(InsertEntities_Command),
+ nameof(InsertEntities_Dapper),
+ nameof(InsertEntities_DbConnectionPlus)
+ ]
+ )]
+ public void InsertEntities__Setup() =>
+ this.SetupDatabase(0);
+
+ [Benchmark(Baseline = true)]
+ [BenchmarkCategory(InsertEntities_Category)]
+ public void InsertEntities_Command()
+ {
+ using var command = this.connection.CreateCommand();
+
+ command.CommandText = InsertEntitySql;
+
+ var parameters = new Dictionary
+ {
+ { "Id", new("Id", null) },
+ { "BooleanValue", new("BooleanValue", null) },
+ { "BytesValue", new("BytesValue", null) },
+ { "ByteValue", new("ByteValue", null) },
+ { "CharValue", new("CharValue", null) },
+ { "DateTimeValue", new("DateTimeValue", null) },
+ { "DecimalValue", new("DecimalValue", null) },
+ { "DoubleValue", new("DoubleValue", null) },
+ { "EnumValue", new("EnumValue", null) },
+ { "GuidValue", new("GuidValue", null) },
+ { "Int16Value", new("Int16Value", null) },
+ { "Int32Value", new("Int32Value", null) },
+ { "Int64Value", new("Int64Value", null) },
+ { "SingleValue", new("SingleValue", null) },
+ { "StringValue", new("StringValue", null) },
+ { "TimeSpanValue", new("TimeSpanValue", null) }
+ };
+
+ command.Parameters.AddRange(parameters.Values);
+
+ foreach (var entity in this.insertEntities_entitiesToInsert)
+ {
+ PopulateEntityParameters(entity, parameters);
+
+ command.ExecuteNonQuery();
+ }
+ }
+
+ [Benchmark(Baseline = false)]
+ [BenchmarkCategory(InsertEntities_Category)]
+ public void InsertEntities_Dapper() =>
+ SqlMapperExtensions.Insert(this.connection, this.insertEntities_entitiesToInsert);
+
+ [Benchmark(Baseline = false)]
+ [BenchmarkCategory(InsertEntities_Category)]
+ public void InsertEntities_DbConnectionPlus() =>
+ this.connection.InsertEntities(this.insertEntities_entitiesToInsert);
+
+ private readonly List insertEntities_entitiesToInsert =
+ Generate.Multiple(InsertEntities_EntitiesPerOperation);
+
+ private const String InsertEntities_Category = "InsertEntities";
+ private const Int32 InsertEntities_EntitiesPerOperation = 200;
+
+ private const String InsertEntitySql = """
+ INSERT INTO Entity
+ (
+ Id,
+ BooleanValue,
+ BytesValue,
+ ByteValue,
+ CharValue,
+ DateTimeValue,
+ DecimalValue,
+ DoubleValue,
+ EnumValue,
+ GuidValue,
+ Int16Value,
+ Int32Value,
+ Int64Value,
+ SingleValue,
+ StringValue,
+ TimeSpanValue
+ )
+ VALUES
+ (
+ @Id,
+ @BooleanValue,
+ @BytesValue,
+ @ByteValue,
+ @CharValue,
+ @DateTimeValue,
+ @DecimalValue,
+ @DoubleValue,
+ @EnumValue,
+ @GuidValue,
+ @Int16Value,
+ @Int32Value,
+ @Int64Value,
+ @SingleValue,
+ @StringValue,
+ @TimeSpanValue
+ )
+ """;
+}
diff --git a/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.InsertEntity.cs b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.InsertEntity.cs
new file mode 100644
index 0000000..3eaee1b
--- /dev/null
+++ b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.InsertEntity.cs
@@ -0,0 +1,79 @@
+// ReSharper disable InvokeAsExtensionMethod
+// ReSharper disable InconsistentNaming
+
+#pragma warning disable RCS1196
+
+namespace RentADeveloper.DbConnectionPlus.Benchmarks;
+
+public partial class Benchmarks
+{
+ [GlobalCleanup(
+ Targets =
+ [
+ nameof(InsertEntity_Command),
+ nameof(InsertEntity_Dapper),
+ nameof(InsertEntity_DbConnectionPlus)
+ ]
+ )]
+ public void InsertEntity__Cleanup() =>
+ this.connection.Dispose();
+
+ [GlobalSetup(
+ Targets =
+ [
+ nameof(InsertEntity_Command),
+ nameof(InsertEntity_Dapper),
+ nameof(InsertEntity_DbConnectionPlus)
+ ]
+ )]
+ public void InsertEntity__Setup() =>
+ this.SetupDatabase(0);
+
+ [Benchmark(Baseline = true)]
+ [BenchmarkCategory(InsertEntity_Category)]
+ public void InsertEntity_Command()
+ {
+ using var command = this.connection.CreateCommand();
+
+ command.CommandText = InsertEntitySql;
+
+ var parameters = new Dictionary
+ {
+ { "Id", new("Id", null) },
+ { "BooleanValue", new("BooleanValue", null) },
+ { "BytesValue", new("BytesValue", null) },
+ { "ByteValue", new("ByteValue", null) },
+ { "CharValue", new("CharValue", null) },
+ { "DateTimeValue", new("DateTimeValue", null) },
+ { "DecimalValue", new("DecimalValue", null) },
+ { "DoubleValue", new("DoubleValue", null) },
+ { "EnumValue", new("EnumValue", null) },
+ { "GuidValue", new("GuidValue", null) },
+ { "Int16Value", new("Int16Value", null) },
+ { "Int32Value", new("Int32Value", null) },
+ { "Int64Value", new("Int64Value", null) },
+ { "SingleValue", new("SingleValue", null) },
+ { "StringValue", new("StringValue", null) },
+ { "TimeSpanValue", new("TimeSpanValue", null) }
+ };
+
+ command.Parameters.AddRange(parameters.Values);
+
+ PopulateEntityParameters(this.insertEntity_entityToInsert, parameters);
+
+ command.ExecuteNonQuery();
+ }
+
+ [Benchmark(Baseline = false)]
+ [BenchmarkCategory(InsertEntity_Category)]
+ public void InsertEntity_Dapper() =>
+ SqlMapperExtensions.Insert(this.connection, this.insertEntity_entityToInsert);
+
+ [Benchmark(Baseline = false)]
+ [BenchmarkCategory(InsertEntity_Category)]
+ public void InsertEntity_DbConnectionPlus() =>
+ this.connection.InsertEntity(this.insertEntity_entityToInsert);
+
+ private readonly BenchmarkEntity insertEntity_entityToInsert = Generate.Single();
+ private const String InsertEntity_Category = "InsertEntity";
+}
diff --git a/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.Parameter.cs b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.Parameter.cs
new file mode 100644
index 0000000..8daba2e
--- /dev/null
+++ b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.Parameter.cs
@@ -0,0 +1,74 @@
+// ReSharper disable InvokeAsExtensionMethod
+// ReSharper disable InconsistentNaming
+
+#pragma warning disable RCS1196
+
+namespace RentADeveloper.DbConnectionPlus.Benchmarks;
+
+public partial class Benchmarks
+{
+ [GlobalCleanup(
+ Targets =
+ [
+ nameof(Parameter_Command),
+ nameof(Parameter_Dapper),
+ nameof(Parameter_DbConnectionPlus)
+ ]
+ )]
+ public void Parameter__Cleanup() =>
+ this.connection.Dispose();
+
+ [GlobalSetup(
+ Targets =
+ [
+ nameof(Parameter_Command),
+ nameof(Parameter_Dapper),
+ nameof(Parameter_DbConnectionPlus)
+ ]
+ )]
+ public void Parameter__Setup() =>
+ this.SetupDatabase(0);
+
+ [Benchmark(Baseline = true)]
+ [BenchmarkCategory(Parameter_Category)]
+ public Int64 Parameter_Command()
+ {
+ using var command = this.connection.CreateCommand();
+
+ command.CommandText = "SELECT @P1 + @P2 + @P3 + @P4 + @P5 + @P6 + @P7 + @P8 + @P9 + @P10";
+
+ command.Parameters.Add(new("@P1", 1));
+ command.Parameters.Add(new("@P2", 2));
+ command.Parameters.Add(new("@P3", 3));
+ command.Parameters.Add(new("@P4", 4));
+ command.Parameters.Add(new("@P5", 5));
+ command.Parameters.Add(new("@P6", 6));
+ command.Parameters.Add(new("@P7", 7));
+ command.Parameters.Add(new("@P8", 8));
+ command.Parameters.Add(new("@P9", 9));
+ command.Parameters.Add(new("@P10", 10));
+
+ return (Int64)command.ExecuteScalar()!;
+ }
+
+ [Benchmark(Baseline = false)]
+ [BenchmarkCategory(Parameter_Category)]
+ public Int64 Parameter_Dapper() =>
+ SqlMapper.ExecuteScalar(
+ this.connection,
+ "SELECT @P1 + @P2 + @P3 + @P4 + @P5 + @P6 + @P7 + @P8 + @P9 + @P10",
+ new { P1 = 1, P2 = 2, P3 = 3, P4 = 4, P5 = 5, P6 = 6, P7 = 7, P8 = 8, P9 = 9, P10 = 10 }
+ );
+
+ [Benchmark(Baseline = false)]
+ [BenchmarkCategory(Parameter_Category)]
+ public Int64 Parameter_DbConnectionPlus() =>
+ this.connection.ExecuteScalar(
+ $"""
+ SELECT {Parameter(1)} + {Parameter(2)} + {Parameter(3)} + {Parameter(4)} + {Parameter(5)} +
+ {Parameter(6)} + {Parameter(7)} + {Parameter(8)} + {Parameter(9)} + {Parameter(10)}
+ """
+ );
+
+ private const String Parameter_Category = "Parameter";
+}
diff --git a/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.Query_Dynamic.cs b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.Query_Dynamic.cs
new file mode 100644
index 0000000..d0c17e6
--- /dev/null
+++ b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.Query_Dynamic.cs
@@ -0,0 +1,88 @@
+// ReSharper disable InvokeAsExtensionMethod
+// ReSharper disable InconsistentNaming
+
+#pragma warning disable RCS1196
+
+using DataRow = RentADeveloper.DbConnectionPlus.Dynamic.DataRow;
+
+namespace RentADeveloper.DbConnectionPlus.Benchmarks;
+
+public partial class Benchmarks
+{
+ [GlobalCleanup(
+ Targets =
+ [
+ nameof(Query_Dynamic_Command),
+ nameof(Query_Dynamic_Dapper),
+ nameof(Query_Dynamic_DbConnectionPlus)
+ ]
+ )]
+ public void Query_Dynamic__Cleanup() =>
+ this.connection.Dispose();
+
+ [GlobalSetup(
+ Targets =
+ [
+ nameof(Query_Dynamic_Command),
+ nameof(Query_Dynamic_Dapper),
+ nameof(Query_Dynamic_DbConnectionPlus)
+ ]
+ )]
+ public void Query_Dynamic__Setup() =>
+ this.SetupDatabase(Query_Dynamic_EntitiesPerOperation);
+
+ [Benchmark(Baseline = true)]
+ [BenchmarkCategory(Query_Dynamic_Category)]
+ public List Query_Dynamic_Command()
+ {
+ var entities = new List();
+
+ using var dataReader = this.connection.ExecuteReader("SELECT * FROM Entity");
+
+ while (dataReader.Read())
+ {
+ var charBuffer = new Char[1];
+
+ var ordinal = 0;
+
+ var dictionary = new Dictionary
+ {
+ ["Id"] = dataReader.GetInt64(ordinal++),
+ ["BooleanValue"] = dataReader.GetInt64(ordinal++) == 1,
+ ["BytesValue"] = (Byte[])dataReader.GetValue(ordinal++),
+ ["ByteValue"] = dataReader.GetByte(ordinal++),
+ ["CharValue"] = dataReader.GetChars(ordinal++, 0, charBuffer, 0, 1) == 1
+ ? charBuffer[0]
+ : throw new(),
+ ["DateTimeValue"] = DateTime.Parse(dataReader.GetString(ordinal++), CultureInfo.InvariantCulture),
+ ["DecimalValue"] = Decimal.Parse(dataReader.GetString(ordinal++), CultureInfo.InvariantCulture),
+ ["DoubleValue"] = dataReader.GetDouble(ordinal++),
+ ["EnumValue"] = Enum.Parse(dataReader.GetString(ordinal++)),
+ ["GuidValue"] = Guid.Parse(dataReader.GetString(ordinal++)),
+ ["Int16Value"] = (Int16)dataReader.GetInt64(ordinal++),
+ ["Int32Value"] = (Int32)dataReader.GetInt64(ordinal++),
+ ["Int64Value"] = dataReader.GetInt64(ordinal++),
+ ["SingleValue"] = dataReader.GetFloat(ordinal++),
+ ["StringValue"] = dataReader.GetString(ordinal++),
+ ["TimeSpanValue"] = TimeSpan.Parse(dataReader.GetString(ordinal), CultureInfo.InvariantCulture)
+ };
+
+ entities.Add(new DataRow(dictionary));
+ }
+
+ return entities;
+ }
+
+ [Benchmark(Baseline = false)]
+ [BenchmarkCategory(Query_Dynamic_Category)]
+ public List Query_Dynamic_Dapper() =>
+ SqlMapper.Query(this.connection, "SELECT * FROM Entity").ToList();
+
+ [Benchmark(Baseline = false)]
+ [BenchmarkCategory(Query_Dynamic_Category)]
+ public List Query_Dynamic_DbConnectionPlus() =>
+ this.connection.Query("SELECT * FROM Entity").ToList();
+
+ private const String Query_Dynamic_Category = "Query_Dynamic";
+ private const Int32 Query_Dynamic_EntitiesPerOperation = 100;
+}
diff --git a/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.Query_Entities.cs b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.Query_Entities.cs
new file mode 100644
index 0000000..3909b29
--- /dev/null
+++ b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.Query_Entities.cs
@@ -0,0 +1,64 @@
+// ReSharper disable InvokeAsExtensionMethod
+// ReSharper disable InconsistentNaming
+
+#pragma warning disable RCS1196
+
+namespace RentADeveloper.DbConnectionPlus.Benchmarks;
+
+public partial class Benchmarks
+{
+ [GlobalCleanup(
+ Targets =
+ [
+ nameof(Query_Entities_Command),
+ nameof(Query_Entities_Dapper),
+ nameof(Query_Entities_DbConnectionPlus)
+ ]
+ )]
+ public void Query_Entities__Cleanup() =>
+ this.connection.Dispose();
+
+ [GlobalSetup(
+ Targets =
+ [
+ nameof(Query_Entities_Command),
+ nameof(Query_Entities_Dapper),
+ nameof(Query_Entities_DbConnectionPlus)
+ ]
+ )]
+ public void Query_Entities__Setup() =>
+ this.SetupDatabase(Query_Entities_EntitiesPerOperation);
+
+ [Benchmark(Baseline = true)]
+ [BenchmarkCategory(Query_Entities_Category)]
+ public List Query_Entities_Command()
+ {
+ var result = new List();
+
+ using var command = this.connection.CreateCommand();
+
+ command.CommandText = "SELECT * FROM Entity";
+
+ using var dataReader = command.ExecuteReader();
+
+ while (dataReader.Read())
+ {
+ result.Add(ReadEntity(dataReader));
+ }
+
+ return result;
+ }
+
+ [Benchmark(Baseline = false)]
+ [BenchmarkCategory(Query_Entities_Category)]
+ public List Query_Entities_Dapper() =>
+ SqlMapper.Query(this.connection, "SELECT * FROM Entity").ToList();
+
+ [Benchmark(Baseline = false)]
+ [BenchmarkCategory(Query_Entities_Category)]
+ public List Query_Entities_DbConnectionPlus() =>
+ this.connection.Query("SELECT * FROM Entity").ToList();
+
+ private const String Query_Entities_Category = "Query_Entities";
+ private const Int32 Query_Entities_EntitiesPerOperation = 100;
+}
diff --git a/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.Query_Scalars.cs b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.Query_Scalars.cs
new file mode 100644
index 0000000..1a880cf
--- /dev/null
+++ b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.Query_Scalars.cs
@@ -0,0 +1,64 @@
+// ReSharper disable InvokeAsExtensionMethod
+// ReSharper disable InconsistentNaming
+
+#pragma warning disable RCS1196
+
+namespace RentADeveloper.DbConnectionPlus.Benchmarks;
+
+public partial class Benchmarks
+{
+ [GlobalCleanup(
+ Targets =
+ [
+ nameof(Query_Scalars_Command),
+ nameof(Query_Scalars_Dapper),
+ nameof(Query_Scalars_DbConnectionPlus)
+ ]
+ )]
+ public void Query_Scalars__Cleanup() =>
+ this.connection.Dispose();
+
+ [GlobalSetup(
+ Targets =
+ [
+ nameof(Query_Scalars_Command),
+ nameof(Query_Scalars_Dapper),
+ nameof(Query_Scalars_DbConnectionPlus)
+ ]
+ )]
+ public void Query_Scalars__Setup() =>
+ this.SetupDatabase(Query_Scalars_EntitiesPerOperation);
+
+ [Benchmark(Baseline = true)]
+ [BenchmarkCategory(Query_Scalars_Category)]
+ public List Query_Scalars_Command()
+ {
+ var result = new List();
+
+ using var command = this.connection.CreateCommand();
+
+ command.CommandText = "SELECT Id FROM Entity";
+
+ using var dataReader = command.ExecuteReader();
+
+ while (dataReader.Read())
+ {
+ result.Add(dataReader.GetInt64(0));
+ }
+
+ return result;
+ }
+
+ [Benchmark(Baseline = false)]
+ [BenchmarkCategory(Query_Scalars_Category)]
+ public List Query_Scalars_Dapper() =>
+ SqlMapper.Query(this.connection, "SELECT Id FROM Entity").ToList();
+
+ [Benchmark(Baseline = false)]
+ [BenchmarkCategory(Query_Scalars_Category)]
+ public List Query_Scalars_DbConnectionPlus() =>
+ this.connection.Query("SELECT Id FROM Entity").ToList();
+
+ private const String Query_Scalars_Category = "Query_Scalars";
+ private const Int32 Query_Scalars_EntitiesPerOperation = 600;
+}
diff --git a/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.Query_ValueTuples.cs b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.Query_ValueTuples.cs
new file mode 100644
index 0000000..d6187b8
--- /dev/null
+++ b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.Query_ValueTuples.cs
@@ -0,0 +1,83 @@
+// ReSharper disable InvokeAsExtensionMethod
+// ReSharper disable InconsistentNaming
+
+#pragma warning disable RCS1196
+
+namespace RentADeveloper.DbConnectionPlus.Benchmarks;
+
+public partial class Benchmarks
+{
+ [GlobalCleanup(
+ Targets =
+ [
+ nameof(Query_ValueTuples_Command),
+ nameof(Query_ValueTuples_Dapper),
+ nameof(Query_ValueTuples_DbConnectionPlus)
+ ]
+ )]
+ public void Query_ValueTuples__Cleanup() =>
+ this.connection.Dispose();
+
+ [GlobalSetup(
+ Targets =
+ [
+ nameof(Query_ValueTuples_Command),
+ nameof(Query_ValueTuples_Dapper),
+ nameof(Query_ValueTuples_DbConnectionPlus)
+ ]
+ )]
+ public void Query_ValueTuples__Setup() =>
+ this.SetupDatabase(Query_ValueTuples_EntitiesPerOperation);
+
+ [Benchmark(Baseline = true)]
+ [BenchmarkCategory(Query_ValueTuples_Category)]
+ public List<(Int64 Id, DateTime DateTimeValue, TestEnum EnumValue, String StringValue)>
+ Query_ValueTuples_Command()
+ {
+ var result = new List<(Int64 Id, DateTime DateTimeValue, TestEnum EnumValue, String StringValue)>();
+
+ using var command = this.connection.CreateCommand();
+
+ command.CommandText = "SELECT Id, DateTimeValue, EnumValue, StringValue FROM Entity";
+
+ using var dataReader = command.ExecuteReader();
+
+ while (dataReader.Read())
+ {
+ result.Add(
+ (
+ dataReader.GetInt64(0),
+ DateTime.Parse(dataReader.GetString(1), CultureInfo.InvariantCulture),
+ Enum.Parse(dataReader.GetString(2)),
+ dataReader.GetString(3)
+ )
+ );
+ }
+
+ return result;
+ }
+
+ [Benchmark(Baseline = false)]
+ [BenchmarkCategory(Query_ValueTuples_Category)]
+ public List<(Int64 Id, DateTime DateTimeValue, TestEnum EnumValue, String StringValue)>
+ Query_ValueTuples_Dapper() =>
+ SqlMapper
+ .Query<(Int64 Id, DateTime DateTimeValue, TestEnum EnumValue, String StringValue)>(
+ this.connection,
+ "SELECT Id, DateTimeValue, EnumValue, StringValue FROM Entity"
+ )
+ .ToList();
+
+ [Benchmark(Baseline = false)]
+ [BenchmarkCategory(Query_ValueTuples_Category)]
+ public List<(Int64 Id, DateTime DateTimeValue, TestEnum EnumValue, String StringValue)>
+ Query_ValueTuples_DbConnectionPlus() =>
+ this.connection
+ .Query<(Int64 Id, DateTime DateTimeValue, TestEnum EnumValue, String StringValue)>(
+ "SELECT Id, DateTimeValue, EnumValue, StringValue FROM Entity"
+ )
+ .ToList();
+
+ private const String Query_ValueTuples_Category = "Query_ValueTuples";
+ private const Int32 Query_ValueTuples_EntitiesPerOperation = 150;
+}
diff --git a/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.TemporaryTable_ComplexObjects.cs b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.TemporaryTable_ComplexObjects.cs
new file mode 100644
index 0000000..1d30eb2
--- /dev/null
+++ b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.TemporaryTable_ComplexObjects.cs
@@ -0,0 +1,182 @@
+// ReSharper disable InvokeAsExtensionMethod
+// ReSharper disable InconsistentNaming
+
+#pragma warning disable RCS1196
+
+namespace RentADeveloper.DbConnectionPlus.Benchmarks;
+
+public partial class Benchmarks
+{
+ [GlobalCleanup(
+ Targets =
+ [
+ nameof(TemporaryTable_ComplexObjects_Command),
+ nameof(TemporaryTable_ComplexObjects_Dapper),
+ nameof(TemporaryTable_ComplexObjects_DbConnectionPlus)
+ ]
+ )]
+ public void TemporaryTable_ComplexObjects__Cleanup() =>
+ this.connection.Dispose();
+
+ [GlobalSetup(
+ Targets =
+ [
+ nameof(TemporaryTable_ComplexObjects_Command),
+ nameof(TemporaryTable_ComplexObjects_Dapper),
+ nameof(TemporaryTable_ComplexObjects_DbConnectionPlus)
+ ]
+ )]
+ public void TemporaryTable_ComplexObjects__Setup() =>
+ this.SetupDatabase(TemporaryTable_ComplexObjects_EntitiesPerOperation);
+
+ [Benchmark(Baseline = true)]
+ [BenchmarkCategory(TemporaryTable_ComplexObjects_Category)]
+ public List TemporaryTable_ComplexObjects_Command()
+ {
+ var result = new List();
+
+ using var createTableCommand = this.connection.CreateCommand();
+ createTableCommand.CommandText = CreateTempEntitiesTableSql;
+ createTableCommand.ExecuteNonQuery();
+
+ using var insertCommand = this.connection.CreateCommand();
+
+ insertCommand.CommandText = InsertIntoTempEntities;
+
+ var parameters = new Dictionary
+ {
+ { "Id", new("Id", null) },
+ { "BooleanValue", new("BooleanValue", null) },
+ { "BytesValue", new("BytesValue", null) },
+ { "ByteValue", new("ByteValue", null) },
+ { "CharValue", new("CharValue", null) },
+ { "DateTimeValue", new("DateTimeValue", null) },
+ { "DecimalValue", new("DecimalValue", null) },
+ { "DoubleValue", new("DoubleValue", null) },
+ { "EnumValue", new("EnumValue", null) },
+ { "GuidValue", new("GuidValue", null) },
+ { "Int16Value", new("Int16Value", null) },
+ { "Int32Value", new("Int32Value", null) },
+ { "Int64Value", new("Int64Value", null) },
+ { "SingleValue", new("SingleValue", null) },
+ { "StringValue", new("StringValue", null) },
+ { "TimeSpanValue", new("TimeSpanValue", null) }
+ };
+
+ insertCommand.Parameters.AddRange(parameters.Values);
+
+ foreach (var entity in this.temporaryTable_ComplexObjects_Entities)
+ {
+ PopulateEntityParameters(entity, parameters);
+
+ insertCommand.ExecuteNonQuery();
+ }
+
+ using var selectCommand = this.connection.CreateCommand();
+
+ selectCommand.CommandText = "SELECT * FROM temp.Entities";
+
+ using var dataReader = selectCommand.ExecuteReader();
+
+ while (dataReader.Read())
+ {
+ result.Add(ReadEntity(dataReader));
+ }
+
+ using var dropTableCommand = this.connection.CreateCommand();
+ dropTableCommand.CommandText = "DROP TABLE temp.Entities";
+ dropTableCommand.ExecuteNonQuery();
+
+ return result;
+ }
+
+ [Benchmark(Baseline = false)]
+ [BenchmarkCategory(TemporaryTable_ComplexObjects_Category)]
+ public List TemporaryTable_ComplexObjects_Dapper()
+ {
+ SqlMapper.Execute(this.connection, CreateTempEntitiesTableSql);
+
+ SqlMapperExtensions.TableNameMapper = _ => "temp.Entities";
+
+ SqlMapperExtensions.Insert(this.connection, this.temporaryTable_ComplexObjects_Entities);
+
+ var result = SqlMapper.Query(this.connection, "SELECT * FROM temp.Entities").ToList();
+
+ SqlMapper.Execute(this.connection, "DROP TABLE temp.Entities");
+
+ return result;
+ }
+
+ [Benchmark(Baseline = false)]
+ [BenchmarkCategory(TemporaryTable_ComplexObjects_Category)]
+ public List TemporaryTable_ComplexObjects_DbConnectionPlus() =>
+ this.connection
+ .Query($"SELECT * FROM {TemporaryTable(this.temporaryTable_ComplexObjects_Entities)}")
+ .ToList();
+
+ private readonly List temporaryTable_ComplexObjects_Entities =
+ Generate.Multiple(TemporaryTable_ComplexObjects_EntitiesPerOperation);
+
+ private const String CreateTempEntitiesTableSql = """
+ CREATE TEMP TABLE Entities (
+ Id INTEGER,
+ BooleanValue INTEGER,
+ BytesValue BLOB,
+ ByteValue INTEGER,
+ CharValue TEXT,
+ DateTimeValue TEXT,
+ DecimalValue TEXT,
+ DoubleValue REAL,
+ EnumValue TEXT,
+ GuidValue TEXT,
+ Int16Value INTEGER,
+ Int32Value INTEGER,
+ Int64Value INTEGER,
+ SingleValue REAL,
+ StringValue TEXT,
+ TimeSpanValue TEXT
+ )
+ """;
+
+ private const String InsertIntoTempEntities = """
+ INSERT INTO temp.Entities (
+ Id,
+ BooleanValue,
+ BytesValue,
+ ByteValue,
+ CharValue,
+ DateTimeValue,
+ DecimalValue,
+ DoubleValue,
+ EnumValue,
+ GuidValue,
+ Int16Value,
+ Int32Value,
+ Int64Value,
+ SingleValue,
+ StringValue,
+ TimeSpanValue
+ )
+ VALUES (
+ @Id,
+ @BooleanValue,
+ @BytesValue,
+ @ByteValue,
+ @CharValue,
+ @DateTimeValue,
+ @DecimalValue,
+ @DoubleValue,
+ @EnumValue,
+ @GuidValue,
+ @Int16Value,
+ @Int32Value,
+ @Int64Value,
+ @SingleValue,
+ @StringValue,
+ @TimeSpanValue
+ )
+ """;
+
+ private const String TemporaryTable_ComplexObjects_Category = "TemporaryTable_ComplexObjects";
+ private const Int32 TemporaryTable_ComplexObjects_EntitiesPerOperation = 250;
+}
diff --git a/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.TemporaryTable_ScalarValues.cs b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.TemporaryTable_ScalarValues.cs
new file mode 100644
index 0000000..7f10528
--- /dev/null
+++ b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.TemporaryTable_ScalarValues.cs
@@ -0,0 +1,110 @@
+// ReSharper disable InvokeAsExtensionMethod
+// ReSharper disable InconsistentNaming
+
+#pragma warning disable RCS1196
+
+namespace RentADeveloper.DbConnectionPlus.Benchmarks;
+
+public partial class Benchmarks
+{
+ [GlobalCleanup(
+ Targets =
+ [
+ nameof(TemporaryTable_ScalarValues_Command),
+ nameof(TemporaryTable_ScalarValues_Dapper),
+ nameof(TemporaryTable_ScalarValues_DbConnectionPlus)
+ ]
+ )]
+ public void TemporaryTable_ScalarValues__Cleanup() =>
+ this.connection.Dispose();
+
+ [GlobalSetup(
+ Targets =
+ [
+ nameof(TemporaryTable_ScalarValues_Command),
+ nameof(TemporaryTable_ScalarValues_Dapper),
+ nameof(TemporaryTable_ScalarValues_DbConnectionPlus)
+ ]
+ )]
+ public void TemporaryTable_ScalarValues__Setup() =>
+ this.SetupDatabase(0);
+
+ [Benchmark(Baseline = true)]
+ [BenchmarkCategory(TemporaryTable_ScalarValues_Category)]
+ public List TemporaryTable_ScalarValues_Command()
+ {
+ using var createTableCommand = this.connection.CreateCommand();
+ createTableCommand.CommandText = "CREATE TEMP TABLE \"Values\" (Value INTEGER)";
+ createTableCommand.ExecuteNonQuery();
+
+ using var insertCommand = this.connection.CreateCommand();
+ insertCommand.CommandText = "INSERT INTO temp.\"Values\" (Value) VALUES (@Value)";
+
+ var valueParameter = new SqliteParameter
+ {
+ ParameterName = "@Value"
+ };
+
+ insertCommand.Parameters.Add(valueParameter);
+
+ foreach (var value in this.temporaryTable_ScalarValues_Values)
+ {
+ valueParameter.Value = value;
+
+ insertCommand.ExecuteNonQuery();
+ }
+
+ using var selectCommand = this.connection.CreateCommand();
+
+ selectCommand.CommandText = "SELECT Value FROM temp.\"Values\"";
+
+ using var dataReader = selectCommand.ExecuteReader();
+
+ var result = new List();
+
+ while (dataReader.Read())
+ {
+ result.Add(dataReader.GetInt64(0));
+ }
+
+ using var dropTableCommand = this.connection.CreateCommand();
+ dropTableCommand.CommandText = "DROP TABLE temp.\"Values\"";
+ dropTableCommand.ExecuteNonQuery();
+
+ return result;
+ }
+
+ [Benchmark(Baseline = false)]
+ [BenchmarkCategory(TemporaryTable_ScalarValues_Category)]
+ public List TemporaryTable_ScalarValues_Dapper()
+ {
+ SqlMapper.Execute(this.connection, "CREATE TEMP TABLE \"Values\" (Value INTEGER)");
+
+ SqlMapperExtensions.TableNameMapper = _ => "temp.\"Values\"";
+
+ SqlMapperExtensions.Insert(
+ this.connection,
+ this.temporaryTable_ScalarValues_Values.Select(a => new { Value = a })
+ );
+
+ var result = SqlMapper.Query(this.connection, "SELECT Value FROM temp.\"Values\"").ToList();
+
+ SqlMapper.Execute(this.connection, "DROP TABLE temp.\"Values\"");
+
+ return result;
+ }
+
+ [Benchmark(Baseline = false)]
+ [BenchmarkCategory(TemporaryTable_ScalarValues_Category)]
+ public List TemporaryTable_ScalarValues_DbConnectionPlus() =>
+ this.connection.Query($"SELECT Value FROM {TemporaryTable(this.temporaryTable_ScalarValues_Values)}")
+ .ToList();
+
+ private readonly List temporaryTable_ScalarValues_Values = Enumerable
+ .Range(0, TemporaryTable_ScalarValues_ValuesPerOperation)
+ .Select(a => (Int64)a)
+ .ToList();
+
+ private const String TemporaryTable_ScalarValues_Category = "TemporaryTable_ScalarValues";
+ private const Int32 TemporaryTable_ScalarValues_ValuesPerOperation = 5000;
+}
diff --git a/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.UpdateEntities.cs b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.UpdateEntities.cs
new file mode 100644
index 0000000..a261885
--- /dev/null
+++ b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.UpdateEntities.cs
@@ -0,0 +1,110 @@
+// ReSharper disable InvokeAsExtensionMethod
+// ReSharper disable InconsistentNaming
+
+#pragma warning disable RCS1196
+
+namespace RentADeveloper.DbConnectionPlus.Benchmarks;
+
+public partial class Benchmarks
+{
+ [GlobalCleanup(
+ Targets =
+ [
+ nameof(UpdateEntities_Command),
+ nameof(UpdateEntities_Dapper),
+ nameof(UpdateEntities_DbConnectionPlus)
+ ]
+ )]
+ public void UpdateEntities__Cleanup() =>
+ this.connection.Dispose();
+
+ [GlobalSetup(
+ Targets =
+ [
+ nameof(UpdateEntities_Command),
+ nameof(UpdateEntities_Dapper),
+ nameof(UpdateEntities_DbConnectionPlus)
+ ]
+ )]
+ public void UpdateEntities__Setup() =>
+ this.SetupDatabase(UpdateEntities_EntitiesPerOperation);
+
+ [Benchmark(Baseline = true)]
+ [BenchmarkCategory(UpdateEntities_Category)]
+ public void UpdateEntities_Command()
+ {
+ var updatedEntities = Generate.UpdateFor(this.entitiesInDb);
+
+ using var command = this.connection.CreateCommand();
+
+ command.CommandText = """
+ UPDATE Entity
+ SET BooleanValue = @BooleanValue,
+ BytesValue = @BytesValue,
+ ByteValue = @ByteValue,
+ CharValue = @CharValue,
+ DateTimeValue = @DateTimeValue,
+ DecimalValue = @DecimalValue,
+ DoubleValue = @DoubleValue,
+ EnumValue = @EnumValue,
+ GuidValue = @GuidValue,
+ Int16Value = @Int16Value,
+ Int32Value = @Int32Value,
+ Int64Value = @Int64Value,
+ SingleValue = @SingleValue,
+ StringValue = @StringValue,
+ TimeSpanValue = @TimeSpanValue
+ WHERE Id = @Id
+ """;
+
+ var parameters = new Dictionary
+ {
+ { "Id", new("Id", null) },
+ { "BooleanValue", new("BooleanValue", null) },
+ { "BytesValue", new("BytesValue", null) },
+ { "ByteValue", new("ByteValue", null) },
+ { "CharValue", new("CharValue", null) },
+ { "DateTimeValue", new("DateTimeValue", null) },
+ { "DecimalValue", new("DecimalValue", null) },
+ { "DoubleValue", new("DoubleValue", null) },
+ { "EnumValue", new("EnumValue", null) },
+ { "GuidValue", new("GuidValue", null) },
+ { "Int16Value", new("Int16Value", null) },
+ { "Int32Value", new("Int32Value", null) },
+ { "Int64Value", new("Int64Value", null) },
+ { "SingleValue", new("SingleValue", null) },
+ { "StringValue", new("StringValue", null) },
+ { "TimeSpanValue", new("TimeSpanValue", null) }
+ };
+
+ command.Parameters.AddRange(parameters.Values);
+
+ foreach (var updatedEntity in updatedEntities)
+ {
+ PopulateEntityParameters(updatedEntity, parameters);
+
+ command.ExecuteNonQuery();
+ }
+ }
+
+ [Benchmark(Baseline = false)]
+ [BenchmarkCategory(UpdateEntities_Category)]
+ public void UpdateEntities_Dapper()
+ {
+ var updatesEntities = Generate.UpdateFor(this.entitiesInDb);
+
+ SqlMapperExtensions.Update(this.connection, updatesEntities);
+ }
+
+ [Benchmark(Baseline = false)]
+ [BenchmarkCategory(UpdateEntities_Category)]
+ public void UpdateEntities_DbConnectionPlus()
+ {
+ var updatesEntities = Generate.UpdateFor(this.entitiesInDb);
+
+ this.connection.UpdateEntities(updatesEntities);
+ }
+
+ private const String UpdateEntities_Category = "UpdateEntities";
+ private const Int32 UpdateEntities_EntitiesPerOperation = 100;
+}
diff --git a/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.UpdateEntity.cs b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.UpdateEntity.cs
new file mode 100644
index 0000000..d84c98d
--- /dev/null
+++ b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.UpdateEntity.cs
@@ -0,0 +1,112 @@
+// ReSharper disable InvokeAsExtensionMethod
+// ReSharper disable InconsistentNaming
+
+#pragma warning disable RCS1196
+
+namespace RentADeveloper.DbConnectionPlus.Benchmarks;
+
+public partial class Benchmarks
+{
+ [GlobalCleanup(
+ Targets =
+ [
+ nameof(UpdateEntity_Command),
+ nameof(UpdateEntity_Dapper),
+ nameof(UpdateEntity_DbConnectionPlus)
+ ]
+ )]
+ public void UpdateEntity__Cleanup() =>
+ this.connection.Dispose();
+
+ [GlobalSetup(
+ Targets =
+ [
+ nameof(UpdateEntity_Command),
+ nameof(UpdateEntity_Dapper),
+ nameof(UpdateEntity_DbConnectionPlus)
+ ]
+ )]
+ public void UpdateEntity__Setup() =>
+ this.SetupDatabase(1);
+
+ [Benchmark(Baseline = true)]
+ [BenchmarkCategory(UpdateEntity_Category)]
+ public void UpdateEntity_Command()
+ {
+ var entity = this.entitiesInDb[0];
+
+ var updatedEntity = Generate.UpdateFor(entity);
+
+ using var command = this.connection.CreateCommand();
+
+ command.CommandText = """
+ UPDATE Entity
+ SET BooleanValue = @BooleanValue,
+ BytesValue = @BytesValue,
+ ByteValue = @ByteValue,
+ CharValue = @CharValue,
+ DateTimeValue = @DateTimeValue,
+ DecimalValue = @DecimalValue,
+ DoubleValue = @DoubleValue,
+ EnumValue = @EnumValue,
+ GuidValue = @GuidValue,
+ Int16Value = @Int16Value,
+ Int32Value = @Int32Value,
+ Int64Value = @Int64Value,
+ SingleValue = @SingleValue,
+ StringValue = @StringValue,
+ TimeSpanValue = @TimeSpanValue
+ WHERE Id = @Id
+ """;
+
+ var parameters = new Dictionary
+ {
+ { "Id", new("Id", null) },
+ { "BooleanValue", new("BooleanValue", null) },
+ { "BytesValue", new("BytesValue", null) },
+ { "ByteValue", new("ByteValue", null) },
+ { "CharValue", new("CharValue", null) },
+ { "DateTimeValue", new("DateTimeValue", null) },
+ { "DecimalValue", new("DecimalValue", null) },
+ { "DoubleValue", new("DoubleValue", null) },
+ { "EnumValue", new("EnumValue", null) },
+ { "GuidValue", new("GuidValue", null) },
+ { "Int16Value", new("Int16Value", null) },
+ { "Int32Value", new("Int32Value", null) },
+ { "Int64Value", new("Int64Value", null) },
+ { "SingleValue", new("SingleValue", null) },
+ { "StringValue", new("StringValue", null) },
+ { "TimeSpanValue", new("TimeSpanValue", null) }
+ };
+
+ command.Parameters.AddRange(parameters.Values);
+
+ PopulateEntityParameters(updatedEntity, parameters);
+
+ command.ExecuteNonQuery();
+ }
+
+ [Benchmark(Baseline = false)]
+ [BenchmarkCategory(UpdateEntity_Category)]
+ public void UpdateEntity_Dapper()
+ {
+ var entity = this.entitiesInDb[0];
+
+ var updatedEntity = Generate.UpdateFor(entity);
+
+ SqlMapperExtensions.Update(this.connection, updatedEntity);
+ }
+
+ [Benchmark(Baseline = false)]
+ [BenchmarkCategory(UpdateEntity_Category)]
+ public void UpdateEntity_DbConnectionPlus()
+ {
+ var entity = this.entitiesInDb[0];
+
+ var updatedEntity = Generate.UpdateFor(entity);
+
+ this.connection.UpdateEntity(updatedEntity);
+ }
+
+ private const String UpdateEntity_Category = "UpdateEntity";
+}
diff --git a/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.cs b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.cs
index 06797d5..b135666 100644
--- a/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.cs
+++ b/benchmarks/DbConnectionPlus.Benchmarks/Benchmarks.cs
@@ -1,1644 +1,108 @@
-// @formatter:off
-// ReSharper disable InconsistentNaming
-#pragma warning disable IDE0017, IDE0305
+namespace RentADeveloper.DbConnectionPlus.Benchmarks;
-using System.Dynamic;
-using BenchmarkDotNet.Attributes;
-using FastMember;
-using Microsoft.Data.SqlClient;
-using RentADeveloper.DbConnectionPlus.Entities;
-using RentADeveloper.DbConnectionPlus.IntegrationTests.TestDatabase;
-using RentADeveloper.DbConnectionPlus.Readers;
-using RentADeveloper.DbConnectionPlus.UnitTests.TestData;
-using static RentADeveloper.DbConnectionPlus.DbConnectionExtensions;
-
-namespace RentADeveloper.DbConnectionPlus.Benchmarks;
-
-// Note: All settings (i.e. *_EntitiesPerOperation and *_OperationsPerInvoke) are chosen so that each invoke
+// Note: All benchmark settings (i.e. *_EntitiesPerOperation and *_OperationsPerInvoke) are chosen so that each invoke
// takes at least 100 milliseconds to complete on a reasonably fast machine.
[MemoryDiagnoser]
[Config(typeof(BenchmarksConfig))]
-public class Benchmarks
+public partial class Benchmarks
{
- [GlobalSetup]
- public void Setup_Global()
+ static Benchmarks()
{
- this.testDatabaseProvider.ResetDatabase();
-
- // Warm up connection pool.
- for (var i = 0; i < 20; i++)
- {
- using var warmUpConnection = this.CreateConnection();
- warmUpConnection.ExecuteScalar("SELECT 1");
- }
-
- using var connection = this.CreateConnection();
- connection.ExecuteNonQuery("CHECKPOINT");
- connection.ExecuteNonQuery("DBCC DROPCLEANBUFFERS");
- connection.ExecuteNonQuery("DBCC FREEPROCCACHE");
+ SqlMapper.AddTypeHandler(new GuidTypeHandler());
+ SqlMapper.AddTypeHandler(new TimeSpanTypeHandler());
}
- private SqlConnection CreateConnection() =>
- (SqlConnection)this.testDatabaseProvider.CreateConnection();
+ public Benchmarks() =>
+ SqlMapperExtensions.TableNameMapper = null;
- private void PrepareEntitiesInDb(Int32 numberOfEntities)
+ private void SetupDatabase(Int32 numberOfEntities)
{
- using var connection = this.CreateConnection();
+ this.connection = new("Data Source=:memory:");
+ this.connection.Open();
- using var transaction = connection.BeginTransaction();
+ using var createEntityTableCommand = this.connection.CreateCommand();
+ createEntityTableCommand.CommandText = CreateEntityTableSql;
+ createEntityTableCommand.ExecuteNonQuery();
- connection.ExecuteNonQuery("DELETE FROM Entity", transaction);
+ using var transaction = this.connection.BeginTransaction();
- this.entitiesInDb = Generate.Multiple(numberOfEntities);
- connection.InsertEntities(this.entitiesInDb, transaction);
+ this.entitiesInDb = Generate.Multiple(numberOfEntities);
+ this.connection.InsertEntities(this.entitiesInDb, transaction);
transaction.Commit();
}
- private List entitiesInDb = [];
-
- #region DeleteEntities
- private const String DeleteEntities_Category = "DeleteEntities";
- private const Int32 DeleteEntities_EntitiesPerOperation = 100;
- private const Int32 DeleteEntities_OperationsPerInvoke = 20;
-
- [IterationSetup(Targets = [nameof(DeleteEntities_Manually), nameof(DeleteEntities_DbConnectionPlus)])]
- public void DeleteEntities_Setup() =>
- this.PrepareEntitiesInDb(DeleteEntities_OperationsPerInvoke * DeleteEntities_EntitiesPerOperation);
-
- [Benchmark(Baseline = true, OperationsPerInvoke = DeleteEntities_OperationsPerInvoke)]
- [BenchmarkCategory(DeleteEntities_Category)]
- public void DeleteEntities_Manually()
- {
- using var connection = this.CreateConnection();
-
- for (var i = 0; i < DeleteEntities_OperationsPerInvoke; i++)
- {
- using var command = connection.CreateCommand();
- command.CommandText = "DELETE FROM Entity WHERE Id = @Id";
-
- var idParameter = new SqlParameter();
- idParameter.ParameterName = "@Id";
- command.Parameters.Add(idParameter);
-
- foreach (var entity in this.entitiesInDb.Take(DeleteEntities_EntitiesPerOperation).ToList())
- {
- idParameter.Value = entity.Id;
-
- command.ExecuteNonQuery();
-
- this.entitiesInDb.Remove(entity);
- }
- }
- }
-
- [Benchmark(Baseline = false, OperationsPerInvoke = DeleteEntities_OperationsPerInvoke)]
- [BenchmarkCategory(DeleteEntities_Category)]
- public void DeleteEntities_DbConnectionPlus()
- {
- using var connection = this.CreateConnection();
-
- for (var i = 0; i < DeleteEntities_OperationsPerInvoke; i++)
- {
- var entities = this.entitiesInDb.Take(DeleteEntities_EntitiesPerOperation).ToList();
-
- connection.DeleteEntities(entities);
-
- foreach (var entity in entities)
- {
- this.entitiesInDb.Remove(entity);
- }
- }
- }
- #endregion DeleteEntities
-
- #region DeleteEntity
- private const String DeleteEntity_Category = "DeleteEntity";
- private const Int32 DeleteEntity_OperationsPerInvoke = 1200;
-
- [IterationSetup(Targets = [nameof(DeleteEntity_Manually), nameof(DeleteEntity_DbConnectionPlus)])]
- public void DeleteEntity_Setup() =>
- this.PrepareEntitiesInDb(DeleteEntity_OperationsPerInvoke);
-
- [Benchmark(Baseline = true, OperationsPerInvoke = DeleteEntity_OperationsPerInvoke)]
- [BenchmarkCategory(DeleteEntity_Category)]
- public void DeleteEntity_Manually()
- {
- using var connection = this.CreateConnection();
-
- for (var i = 0; i < DeleteEntity_OperationsPerInvoke; i++)
- {
- var entityToDelete = this.entitiesInDb[0];
-
- using var command = connection.CreateCommand();
-
- command.CommandText = "DELETE FROM Entity WHERE Id = @Id";
- command.Parameters.Add(new("@Id", entityToDelete.Id));
-
- command.ExecuteNonQuery();
-
- this.entitiesInDb.Remove(entityToDelete);
- }
- }
-
- [Benchmark(Baseline = false, OperationsPerInvoke = DeleteEntity_OperationsPerInvoke)]
- [BenchmarkCategory(DeleteEntity_Category)]
- public void DeleteEntity_DbConnectionPlus()
- {
- using var connection = this.CreateConnection();
-
- for (var i = 0; i < DeleteEntity_OperationsPerInvoke; i++)
- {
- var entityToDelete = this.entitiesInDb[0];
-
- connection.DeleteEntity(entityToDelete);
-
- this.entitiesInDb.Remove(entityToDelete);
- }
- }
- #endregion DeleteEntity
-
- #region ExecuteNonQuery
- private const String ExecuteNonQuery_Category = "ExecuteNonQuery";
- private const Int32 ExecuteNonQuery_OperationsPerInvoke = 1100;
-
- [IterationSetup(Targets = [nameof(ExecuteNonQuery_Manually), nameof(ExecuteNonQuery_DbConnectionPlus)])]
- public void ExecuteNonQuery_Setup() =>
- this.PrepareEntitiesInDb(ExecuteNonQuery_OperationsPerInvoke);
-
- [Benchmark(Baseline = true, OperationsPerInvoke = ExecuteNonQuery_OperationsPerInvoke)]
- [BenchmarkCategory(ExecuteNonQuery_Category)]
- public void ExecuteNonQuery_Manually()
- {
- using var connection = this.CreateConnection();
-
- for (var i = 0; i < ExecuteNonQuery_OperationsPerInvoke; i++)
- {
- var entity = this.entitiesInDb[0];
-
- using var command = connection.CreateCommand();
-
- command.CommandText = "DELETE FROM Entity WHERE Id = @Id";
- command.Parameters.Add(new("@Id", entity.Id));
-
- command.ExecuteNonQuery();
-
- this.entitiesInDb.Remove(entity);
- }
- }
-
- [Benchmark(Baseline = false, OperationsPerInvoke = ExecuteNonQuery_OperationsPerInvoke)]
- [BenchmarkCategory(ExecuteNonQuery_Category)]
- public void ExecuteNonQuery_DbConnectionPlus()
- {
- using var connection = this.CreateConnection();
-
- for (var i = 0; i < ExecuteNonQuery_OperationsPerInvoke; i++)
- {
- var entity = this.entitiesInDb[0];
-
- connection.ExecuteNonQuery($"DELETE FROM Entity WHERE Id = {Parameter(entity.Id)}");
-
- this.entitiesInDb.Remove(entity);
- }
- }
- #endregion ExecuteNonQuery
-
- #region ExecuteReader
- private const String ExecuteReader_Category = "ExecuteReader";
- private const Int32 ExecuteReader_OperationsPerInvoke = 700;
- private const Int32 ExecuteReader_EntitiesPerOperation = 100;
-
- [GlobalSetup(Targets = [nameof(ExecuteReader_Manually), nameof(ExecuteReader_DbConnectionPlus)])]
- public void ExecuteReader_Setup()
- {
- this.Setup_Global();
- this.PrepareEntitiesInDb(ExecuteReader_EntitiesPerOperation);
- }
-
- [Benchmark(Baseline = true, OperationsPerInvoke = ExecuteReader_OperationsPerInvoke)]
- [BenchmarkCategory(ExecuteReader_Category)]
- public List ExecuteReader_Manually()
- {
- using var connection = this.CreateConnection();
-
- var entities = new List();
-
- for (var i = 0; i < ExecuteReader_OperationsPerInvoke; i++)
- {
- entities.Clear();
-
- using var command = connection.CreateCommand();
- command.CommandText = $"""
- SELECT
- TOP ({ExecuteReader_EntitiesPerOperation})
- [Id],
- [BooleanValue],
- [ByteValue],
- [CharValue],
- [DateOnlyValue],
- [DateTimeValue],
- [DecimalValue],
- [DoubleValue],
- [EnumValue],
- [GuidValue],
- [Int16Value],
- [Int32Value],
- [Int64Value],
- [SingleValue],
- [StringValue],
- [TimeOnlyValue],
- [TimeSpanValue]
- FROM
- Entity
- """;
-
- using var dataReader = command.ExecuteReader();
-
- while (dataReader.Read())
- {
- var charBuffer = new Char[1];
-
- var ordinal = 0;
- entities.Add(new()
- {
- Id = dataReader.GetInt64(ordinal++),
- BooleanValue = dataReader.GetBoolean(ordinal++),
- ByteValue = dataReader.GetByte(ordinal++),
- CharValue = dataReader.GetChars(ordinal++, 0, charBuffer, 0, 1) == 1 ? charBuffer[0] : throw new(),
- DateOnlyValue = DateOnly.FromDateTime((DateTime) dataReader.GetValue(ordinal++)),
- DateTimeValue = dataReader.GetDateTime(ordinal++),
- DecimalValue = dataReader.GetDecimal(ordinal++),
- DoubleValue = dataReader.GetDouble(ordinal++),
- EnumValue = Enum.Parse(dataReader.GetString(ordinal++)),
- GuidValue = dataReader.GetGuid(ordinal++),
- Int16Value = dataReader.GetInt16(ordinal++),
- Int32Value = dataReader.GetInt32(ordinal++),
- Int64Value = dataReader.GetInt64(ordinal++),
- SingleValue = dataReader.GetFloat(ordinal++),
- StringValue = dataReader.GetString(ordinal++),
- TimeOnlyValue = TimeOnly.FromTimeSpan((TimeSpan)dataReader.GetValue(ordinal++)),
- TimeSpanValue = (TimeSpan)dataReader.GetValue(ordinal)
- });
- }
- }
-
- return entities;
- }
-
- [Benchmark(Baseline = false, OperationsPerInvoke = ExecuteReader_OperationsPerInvoke)]
- [BenchmarkCategory(ExecuteReader_Category)]
- public List ExecuteReader_DbConnectionPlus()
- {
- using var connection = this.CreateConnection();
-
- var entities = new List();
-
- for (var i = 0; i < ExecuteReader_OperationsPerInvoke; i++)
- {
- entities.Clear();
-
- using var dataReader = connection.ExecuteReader(
- $"""
- SELECT
- TOP ({ExecuteReader_EntitiesPerOperation})
- [Id],
- [BooleanValue],
- [ByteValue],
- [CharValue],
- [DateOnlyValue],
- [DateTimeValue],
- [DecimalValue],
- [DoubleValue],
- [EnumValue],
- [GuidValue],
- [Int16Value],
- [Int32Value],
- [Int64Value],
- [SingleValue],
- [StringValue],
- [TimeOnlyValue],
- [TimeSpanValue]
- FROM
- Entity
- """
- );
-
- while (dataReader.Read())
- {
- var charBuffer = new Char[1];
-
- var ordinal = 0;
- entities.Add(new()
- {
- Id = dataReader.GetInt64(ordinal++),
- BooleanValue = dataReader.GetBoolean(ordinal++),
- ByteValue = dataReader.GetByte(ordinal++),
- CharValue = dataReader.GetChars(ordinal++, 0, charBuffer, 0, 1) == 1 ? charBuffer[0] : throw new(),
- DateOnlyValue = DateOnly.FromDateTime((DateTime) dataReader.GetValue(ordinal++)),
- DateTimeValue = dataReader.GetDateTime(ordinal++),
- DecimalValue = dataReader.GetDecimal(ordinal++),
- DoubleValue = dataReader.GetDouble(ordinal++),
- EnumValue = Enum.Parse(dataReader.GetString(ordinal++)),
- GuidValue = dataReader.GetGuid(ordinal++),
- Int16Value = dataReader.GetInt16(ordinal++),
- Int32Value = dataReader.GetInt32(ordinal++),
- Int64Value = dataReader.GetInt64(ordinal++),
- SingleValue = dataReader.GetFloat(ordinal++),
- StringValue = dataReader.GetString(ordinal++),
- TimeOnlyValue = TimeOnly.FromTimeSpan((TimeSpan)dataReader.GetValue(ordinal++)),
- TimeSpanValue = (TimeSpan)dataReader.GetValue(ordinal)
- });
- }
- }
-
- return entities;
- }
- #endregion ExecuteReader
-
- #region ExecuteScalar
- private const String ExecuteScalar_Category = "ExecuteScalar";
- private const Int32 ExecuteScalar_OperationsPerInvoke = 5000;
-
- [GlobalSetup(Targets = [nameof(ExecuteScalar_Manually), nameof(ExecuteScalar_DbConnectionPlus)])]
- public void ExecuteScalar_Setup()
- {
- this.Setup_Global();
- this.PrepareEntitiesInDb(ExecuteScalar_OperationsPerInvoke);
- }
-
- [Benchmark(Baseline = true, OperationsPerInvoke = ExecuteScalar_OperationsPerInvoke)]
- [BenchmarkCategory(ExecuteScalar_Category)]
- public String ExecuteScalar_Manually()
- {
- using var connection = this.CreateConnection();
-
- String result = null!;
-
- for (var i = 0; i < ExecuteScalar_OperationsPerInvoke; i++)
- {
- var entity = this.entitiesInDb[i];
-
- using var command = connection.CreateCommand();
-
- command.CommandText = "SELECT StringValue FROM Entity WHERE Id = @Id";
- command.Parameters.Add(new("@Id", entity.Id));
-
- result = (String)command.ExecuteScalar()!;
- }
-
- return result;
- }
-
- [Benchmark(Baseline = false, OperationsPerInvoke = ExecuteScalar_OperationsPerInvoke)]
- [BenchmarkCategory(ExecuteScalar_Category)]
- public String ExecuteScalar_DbConnectionPlus()
- {
- using var connection = this.CreateConnection();
-
- String result = null!;
-
- for (var i = 0; i < ExecuteScalar_OperationsPerInvoke; i++)
- {
- var entity = this.entitiesInDb[i];
-
- result = connection.ExecuteScalar(
- $"SELECT StringValue FROM Entity WHERE Id = {Parameter(entity.Id)}"
- );
- }
-
- return result;
- }
- #endregion ExecuteScalar
-
- #region Exists
- private const String Exists_Category = "Exists";
- private const Int32 Exists_OperationsPerInvoke = 5000;
-
- [GlobalSetup(Targets = [nameof(Exists_Manually), nameof(Exists_DbConnectionPlus)])]
- public void Exists_Setup()
- {
- this.Setup_Global();
- this.PrepareEntitiesInDb(Exists_OperationsPerInvoke);
- }
-
- [Benchmark(Baseline = true, OperationsPerInvoke = Exists_OperationsPerInvoke)]
- [BenchmarkCategory(Exists_Category)]
- public Boolean Exists_Manually()
- {
- using var connection = this.CreateConnection();
-
- var result = false;
-
- for (var i = 0; i < Exists_OperationsPerInvoke; i++)
- {
- var entityId = this.entitiesInDb[i].Id;
-
- using var command = connection.CreateCommand();
- command.CommandText = "SELECT 1 FROM Entity WHERE Id = @Id";
- command.Parameters.Add(new("@Id", entityId));
-
- using var dataReader = command.ExecuteReader();
-
- result = dataReader.HasRows;
- }
-
- return result;
- }
-
- [Benchmark(Baseline = false, OperationsPerInvoke = Exists_OperationsPerInvoke)]
- [BenchmarkCategory(Exists_Category)]
- public Boolean Exists_DbConnectionPlus()
- {
- using var connection = this.CreateConnection();
-
- var result = false;
-
- for (var i = 0; i < Exists_OperationsPerInvoke; i++)
- {
- var entityId = this.entitiesInDb[i].Id;
-
- result = connection.Exists($"SELECT 1 FROM Entity WHERE Id = {Parameter(entityId)}");
- }
-
- return result;
- }
- #endregion Exists
-
- #region InsertEntities
- private const String InsertEntities_Category = "InsertEntities";
- private const Int32 InsertEntities_OperationsPerInvoke = 20;
- private const Int32 InsertEntities_EntitiesPerOperation = 100;
-
- [GlobalSetup(Targets = [nameof(InsertEntities_Manually), nameof(InsertEntities_DbConnectionPlus)])]
- public void InsertEntities_Setup()
- {
- this.Setup_Global();
- this.PrepareEntitiesInDb(0);
- }
-
- [Benchmark(Baseline = true, OperationsPerInvoke = InsertEntities_OperationsPerInvoke)]
- [BenchmarkCategory(InsertEntities_Category)]
- public void InsertEntities_Manually()
- {
- using var connection = this.CreateConnection();
-
- for (var i = 0; i < InsertEntities_OperationsPerInvoke; i++)
- {
- var entities = Generate.Multiple(InsertEntities_EntitiesPerOperation);
-
- using var command = connection.CreateCommand();
- command.CommandText = """
- INSERT INTO [Entity]
- (
- [Id],
- [BooleanValue],
- [ByteValue],
- [CharValue],
- [DateOnlyValue],
- [DateTimeValue],
- [DecimalValue],
- [DoubleValue],
- [EnumValue],
- [GuidValue],
- [Int16Value],
- [Int32Value],
- [Int64Value],
- [SingleValue],
- [StringValue],
- [TimeOnlyValue],
- [TimeSpanValue]
- )
- VALUES
- (
- @Id,
- @BooleanValue,
- @ByteValue,
- @CharValue,
- @DateOnlyValue,
- @DateTimeValue,
- @DecimalValue,
- @DoubleValue,
- @EnumValue,
- @GuidValue,
- @Int16Value,
- @Int32Value,
- @Int64Value,
- @SingleValue,
- @StringValue,
- @TimeOnlyValue,
- @TimeSpanValue
- )
- """;
-
- var idParameter = new SqlParameter();
- idParameter.ParameterName = "@Id";
-
- var booleanValueParameter = new SqlParameter();
- booleanValueParameter.ParameterName = "@BooleanValue";
-
- var byteValueParameter = new SqlParameter();
- byteValueParameter.ParameterName = "@ByteValue";
-
- var charValueParameter = new SqlParameter();
- charValueParameter.ParameterName = "@CharValue";
-
- var dateOnlyParameter = new SqlParameter();
- dateOnlyParameter.ParameterName = "@DateOnlyValue";
-
- var dateTimeValueParameter = new SqlParameter();
- dateTimeValueParameter.ParameterName = "@DateTimeValue";
-
- var decimalValueParameter = new SqlParameter();
- decimalValueParameter.ParameterName = "@DecimalValue";
-
- var doubleValueParameter = new SqlParameter();
- doubleValueParameter.ParameterName = "@DoubleValue";
-
- var enumValueParameter = new SqlParameter();
- enumValueParameter.ParameterName = "@EnumValue";
-
- var guidValueParameter = new SqlParameter();
- guidValueParameter.ParameterName = "@GuidValue";
-
- var int16ValueParameter = new SqlParameter();
- int16ValueParameter.ParameterName = "@Int16Value";
-
- var int32ValueParameter = new SqlParameter();
- int32ValueParameter.ParameterName = "@Int32Value";
-
- var int64ValueParameter = new SqlParameter();
- int64ValueParameter.ParameterName = "@Int64Value";
-
- var singleValueParameter = new SqlParameter();
- singleValueParameter.ParameterName = "@SingleValue";
-
- var stringValueParameter = new SqlParameter();
- stringValueParameter.ParameterName = "@StringValue";
-
- var timeOnlyValueParameter = new SqlParameter();
- timeOnlyValueParameter.ParameterName = "@TimeOnlyValue";
-
- var timeSpanValueParameter = new SqlParameter();
- timeSpanValueParameter.ParameterName = "@TimeSpanValue";
-
- command.Parameters.Add(idParameter);
- command.Parameters.Add(booleanValueParameter);
- command.Parameters.Add(byteValueParameter);
- command.Parameters.Add(charValueParameter);
- command.Parameters.Add(dateOnlyParameter);
- command.Parameters.Add(dateTimeValueParameter);
- command.Parameters.Add(decimalValueParameter);
- command.Parameters.Add(doubleValueParameter);
- command.Parameters.Add(enumValueParameter);
- command.Parameters.Add(guidValueParameter);
- command.Parameters.Add(int16ValueParameter);
- command.Parameters.Add(int32ValueParameter);
- command.Parameters.Add(int64ValueParameter);
- command.Parameters.Add(singleValueParameter);
- command.Parameters.Add(stringValueParameter);
- command.Parameters.Add(timeOnlyValueParameter);
- command.Parameters.Add(timeSpanValueParameter);
-
- foreach (var entity in entities)
- {
- idParameter.Value = entity.Id;
- booleanValueParameter.Value = entity.BooleanValue;
- byteValueParameter.Value = entity.ByteValue;
- charValueParameter.Value = entity.CharValue;
- dateOnlyParameter.Value = entity.DateOnlyValue;
- dateTimeValueParameter.Value = entity.DateTimeValue;
- decimalValueParameter.Value = entity.DecimalValue;
- doubleValueParameter.Value = entity.DoubleValue;
- enumValueParameter.Value = entity.EnumValue.ToString();
- guidValueParameter.Value = entity.GuidValue;
- int16ValueParameter.Value = entity.Int16Value;
- int32ValueParameter.Value = entity.Int32Value;
- int64ValueParameter.Value = entity.Int64Value;
- singleValueParameter.Value = entity.SingleValue;
- stringValueParameter.Value = entity.StringValue;
- timeOnlyValueParameter.Value = entity.TimeOnlyValue;
- timeSpanValueParameter.Value = entity.TimeSpanValue;
-
- command.ExecuteNonQuery();
- }
- }
- }
-
- [Benchmark(Baseline = false, OperationsPerInvoke = InsertEntities_OperationsPerInvoke)]
- [BenchmarkCategory(InsertEntities_Category)]
- public void InsertEntities_DbConnectionPlus()
- {
- using var connection = this.CreateConnection();
-
- for (var i = 0; i < InsertEntities_OperationsPerInvoke; i++)
- {
- var entitiesToInsert = Generate.Multiple(InsertEntities_EntitiesPerOperation);
-
- connection.InsertEntities(entitiesToInsert);
- }
- }
- #endregion InsertEntities
-
- #region InsertEntity
- private const String InsertEntity_Category = "InsertEntity";
- private const Int32 InsertEntity_OperationsPerInvoke = 700;
-
- [GlobalSetup(Targets = [nameof(InsertEntity_Manually), nameof(InsertEntity_DbConnectionPlus)])]
- public void InsertEntity_Setup()
- {
- this.Setup_Global();
- this.PrepareEntitiesInDb(0);
- }
-
- [Benchmark(Baseline = true, OperationsPerInvoke = InsertEntity_OperationsPerInvoke)]
- [BenchmarkCategory(InsertEntity_Category)]
- public void InsertEntity_Manually()
- {
- using var connection = this.CreateConnection();
-
- for (var i = 0; i < InsertEntity_OperationsPerInvoke; i++)
- {
- var entity = Generate.Single();
-
- using var command = connection.CreateCommand();
- command.CommandText = """
- INSERT INTO [Entity]
- (
- [Id],
- [BooleanValue],
- [ByteValue],
- [CharValue],
- [DateOnlyValue],
- [DateTimeValue],
- [DecimalValue],
- [DoubleValue],
- [EnumValue],
- [GuidValue],
- [Int16Value],
- [Int32Value],
- [Int64Value],
- [SingleValue],
- [StringValue],
- [TimeOnlyValue],
- [TimeSpanValue]
- )
- VALUES
- (
- @Id,
- @BooleanValue,
- @ByteValue,
- @CharValue,
- @DateOnlyValue,
- @DateTimeValue,
- @DecimalValue,
- @DoubleValue,
- @EnumValue,
- @GuidValue,
- @Int16Value,
- @Int32Value,
- @Int64Value,
- @SingleValue,
- @StringValue,
- @TimeOnlyValue,
- @TimeSpanValue
- )
- """;
- command.Parameters.Add(new("@Id", entity.Id));
- command.Parameters.Add(new("@BooleanValue", entity.BooleanValue));
- command.Parameters.Add(new("@ByteValue", entity.ByteValue));
- command.Parameters.Add(new("@CharValue", entity.CharValue));
- command.Parameters.Add(new("@DateOnlyValue", entity.DateOnlyValue));
- command.Parameters.Add(new("@DateTimeValue", entity.DateTimeValue));
- command.Parameters.Add(new("@DecimalValue", entity.DecimalValue));
- command.Parameters.Add(new("@DoubleValue", entity.DoubleValue));
- command.Parameters.Add(new("@EnumValue", entity.EnumValue.ToString()));
- command.Parameters.Add(new("@GuidValue", entity.GuidValue));
- command.Parameters.Add(new("@Int16Value", entity.Int16Value));
- command.Parameters.Add(new("@Int32Value", entity.Int32Value));
- command.Parameters.Add(new("@Int64Value", entity.Int64Value));
- command.Parameters.Add(new("@SingleValue", entity.SingleValue));
- command.Parameters.Add(new("@StringValue", entity.StringValue));
- command.Parameters.Add(new("@TimeOnlyValue", entity.TimeOnlyValue));
- command.Parameters.Add(new("@TimeSpanValue", entity.TimeSpanValue));
-
- command.ExecuteNonQuery();
- }
- }
-
- [Benchmark(Baseline = false, OperationsPerInvoke = InsertEntity_OperationsPerInvoke)]
- [BenchmarkCategory(InsertEntity_Category)]
- public void InsertEntity_DbConnectionPlus()
- {
- using var connection = this.CreateConnection();
-
- for (var i = 0; i < InsertEntity_OperationsPerInvoke; i++)
- {
- var entity = Generate.Single();
-
- connection.InsertEntity(entity);
- }
- }
- #endregion InsertEntity
-
- #region Parameter
- private const String Parameter_Category = "Parameter";
- private const Int32 Parameter_OperationsPerInvoke = 2500;
-
- [GlobalSetup(Targets = [nameof(Parameter_Manually), nameof(Parameter_DbConnectionPlus)])]
- public void Parameter_Setup()
- {
- this.Setup_Global();
- this.PrepareEntitiesInDb(0);
- }
-
- [Benchmark(Baseline = true, OperationsPerInvoke = Parameter_OperationsPerInvoke)]
- [BenchmarkCategory(Parameter_Category)]
- public Object Parameter_Manually()
- {
- using var connection = this.CreateConnection();
-
- var result = new List