├── .editorconfig
├── .github
└── FUNDING.yml
├── .gitignore
├── .travis.yml
├── Directory.Build.props
├── Dommel.sln
├── LICENSE
├── NuGet.Config
├── README.md
├── assets
├── dapper-plus-sponsor.png
└── entity-framework-extensions-sponsor.png
├── build.ps1
├── src
├── Dommel.Json
│ ├── AssemblyInfo.cs
│ ├── Dommel.Json.csproj
│ ├── DommelJsonMapper.cs
│ ├── DommelJsonOptions.cs
│ ├── IJsonSqlBuilder.cs
│ ├── JsonDataAttribute.cs
│ ├── JsonObjectTypeHandler.cs
│ ├── JsonPropertyResolver.cs
│ └── JsonSqlExpression.cs
└── Dommel
│ ├── Any.cs
│ ├── AssemblyInfo.cs
│ ├── AutoMultiMap.cs
│ ├── Cache.cs
│ ├── ColumnPropertyInfo.cs
│ ├── Count.cs
│ ├── DateOnlyTypeHandler.cs
│ ├── DefaultColumnNameResolver.cs
│ ├── DefaultForeignKeyPropertyResolver.cs
│ ├── DefaultKeyPropertyResolver.cs
│ ├── DefaultPropertyResolver.cs
│ ├── DefaultTableNameResolver.cs
│ ├── Delete.cs
│ ├── Dommel.csproj
│ ├── DommelMapper.cs
│ ├── ForeignKeyRelation.cs
│ ├── From.cs
│ ├── Get.cs
│ ├── IColumnNameResolver.cs
│ ├── IForeignKeyPropertyResolver.cs
│ ├── IKeyPropertyResolver.cs
│ ├── IPropertyResolver.cs
│ ├── ISqlBuilder.cs
│ ├── ITableNameResolver.cs
│ ├── IgnoreAttribute.cs
│ ├── Insert.cs
│ ├── MultiMap.cs
│ ├── MySqlSqlBuilder.cs
│ ├── PostgresSqlBuilder.cs
│ ├── Project.cs
│ ├── Resolvers.cs
│ ├── Select.cs
│ ├── SelectAutoMultiMap.cs
│ ├── SelectAutoMultiMapAsync.cs
│ ├── SelectMultiMap.cs
│ ├── SelectMultiMapAsync.cs
│ ├── SqlExpression.cs
│ ├── SqlServerCeSqlBuilder.cs
│ ├── SqlServerSqlBuilder.cs
│ ├── SqliteSqlBuilder.cs
│ └── Update.cs
└── test
├── Dommel.IntegrationTests
├── AnyTests.cs
├── AutoMultiMapTests.cs
├── CountTests.cs
├── Databases
│ ├── DatabaseDriver.cs
│ ├── MySqlDatabaseDriver.cs
│ ├── PostgresDatabaseDriver.cs
│ └── SqlServerDatabaseDriver.cs
├── DeleteTests.cs
├── Dommel.IntegrationTests.csproj
├── FirstOrDefaultTests.cs
├── FromTests.cs
├── GetAllTests.cs
├── GetTests.cs
├── Infrastructure
│ ├── CI.cs
│ ├── DatabaseCollection.cs
│ ├── DatabaseFixture.cs
│ └── DatabaseTestData.cs
├── InsertNonGeneratedColumnTests.cs
├── InsertOutputParameterTests.cs
├── InsertTests.cs
├── Models.cs
├── MultiMapTests.cs
├── PagingTests.cs
├── ProjectTests.cs
├── ResolversTests.cs
├── SelectAutoMultiMapTests.cs
├── SelectMultiMapTests.cs
├── SelectTests.cs
├── TestSample.cs
├── UpdateTests.cs
└── coverage.cmd
├── Dommel.Json.IntegrationTests
├── Databases
│ ├── JsonMySqlDatabaseDriver.cs
│ ├── JsonPostgresDatabaseDriver.cs
│ └── JsonSqlServerDatabaseDriver.cs
├── DeleteTests.cs
├── Dommel.Json.IntegrationTests.csproj
├── Infrastructure
│ ├── JsonDatabaseCollection.cs
│ ├── JsonDatabaseFixture.cs
│ └── JsonDatabaseTestData.cs
├── InsertTests.cs
├── Models.cs
├── SelectTests.cs
└── UpdateTests.cs
├── Dommel.Json.Tests
├── Dommel.Json.Tests.csproj
├── DommelJsonMapperTests.cs
├── DommelJsonOptionsTests.cs
├── JsonObjectTypeHandlerTests.cs
├── JsonPropertyResolverTests.cs
├── JsonSqlExpressionTests.cs
├── LikeTests.cs
└── Models.cs
└── Dommel.Tests
├── AnyTests.cs
├── AutoMultiMapTests.cs
├── CacheTests.cs
├── ColumnPropertyInfoTests.cs
├── CountTests.cs
├── DefaultColumnNameResolverTests.cs
├── DefaultForeignKeyPropertyResolverTests.cs
├── DefaultKeyPropertyResolverTests.cs
├── DefaultPropertyResolverTests.cs
├── DefaultTableNameResolverTests.cs
├── Dommel.Tests.csproj
├── DummySqlBuilder.cs
├── Models.cs
├── MultiMapTests.cs
├── MySqlSqlBuilderTests.cs
├── ParameterPrefixTest.cs
├── PostgresSqlBuilderTests.cs
├── ProjectTests.cs
├── ResolversTests.cs
├── SqlExpressions
├── BooleanExpressionTests.cs
├── DynamicExpressionTests.cs
├── LikeTests.cs
├── NullExpressionTests.cs
├── PageTests.cs
├── SelectExpressionTests.cs
├── SqlExpressionTests.cs
└── WhereExpressionTests.cs
├── SqlServerCeSqlBuilderTests.cs
├── SqlServerSqlBuilderTests.cs
├── SqliteSqlBuilderTests.cs
├── TypeMapProviderTests.cs
└── coverage.cmd
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: henkmollema
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | [Oo]bj/
2 | [Bb]in/
3 | .nuget/
4 | packages/
5 | artifacts/
6 | pack/
7 | PublishProfiles/
8 | .vs/
9 | debugSettings.json
10 | project.lock.json
11 | *.user
12 | *.suo
13 | nuget.exe
14 | *.userprefs
15 | *DS_Store
16 | *.*sdf
17 | *.ipch
18 | .settings
19 | *.sln.ide
20 | .build/
21 | *.ldf
22 | coveragereport/
23 | coverage*.opencover.xml
24 | coverage*.json
25 | .idea/
26 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: csharp
2 | mono: none
3 | dotnet: 6.0
4 | dist: xenial
5 | env:
6 | global:
7 | - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
8 | - DOTNET_CLI_TELEMETRY_OPTOUT: true
9 | - TRAVIS: true
10 | services:
11 | - mysql
12 | - postgresql
13 | branches:
14 | only:
15 | - master
16 | script:
17 | - dotnet restore
18 | - dotnet build
19 | - dotnet test test/Dommel.Tests --no-build
20 | - dotnet test test/Dommel.IntegrationTests --no-build
21 | - dotnet test test/Dommel.Json.Tests --no-build
22 | - dotnet test test/Dommel.Json.IntegrationTests --no-build
23 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | Copyright © Henk Mollema 2014
4 | 3.4.0
5 | Henk Mollema
6 | latest
7 | enable
8 | https://github.com/henkmollema/Dommel
9 | MIT
10 | git
11 | https://github.com/henkmollema/Dommel
12 | true
13 | true
14 | embedded
15 | README.md
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 Henk Mollema
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/NuGet.Config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Dommel
2 | CRUD operations with Dapper made simple.
3 |
4 | | Build | NuGet | MyGet | Test Coverage |
5 | | ----- | ----- | ----- | ------------- |
6 | | [](https://app.travis-ci.com/github/henkmollema/Dommel) | [](https://www.nuget.org/packages/Dommel) | [](https://www.myget.org/feed/dommel-ci/package/nuget/Dommel) | [](https://codecov.io/gh/henkmollema/Dommel) |
7 |
8 |
9 |
10 | Dommel provides a convenient API for CRUD operations using extension methods on the `IDbConnection` interface. The SQL queries are generated based on your POCO entities. Dommel also supports LINQ expressions which are being translated to SQL expressions. [Dapper](https://github.com/StackExchange/Dapper) is used for query execution and object mapping.
11 |
12 | There are several extensibility points available to change the behavior of resolving table names, column names, the key property and POCO properties. See [Extensibility](https://www.learndapper.com/extensions/dommel#extensibility) for more details.
13 |
14 | ## Installing Dommel
15 |
16 | Dommel is available on [NuGet](https://www.nuget.org/packages/Dommel).
17 |
18 | ### Install using the .NET CLI:
19 | ```
20 | dotnet add package Dommel
21 | ```
22 |
23 | ### Install using the NuGet Package Manager:
24 | ```
25 | Install-Package Dommel
26 | ```
27 |
28 | ## Documentation
29 |
30 | The documentation is available at **[Learn Dapper](https://www.learndapper.com/extensions/dommel)**.
31 |
32 | ## Sponsors
33 | [Dapper Plus](https://dapper-plus.net/) and [Entity Framework Extensions](https://entityframework-extensions.net/) are major sponsors and are proud to contribute to the development of Dommel.
34 |
35 | [](https://dapper-plus.net/bulk-insert)
36 |
37 | [](https://entityframework-extensions.net/bulk-insert)
38 |
--------------------------------------------------------------------------------
/assets/dapper-plus-sponsor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henkmollema/Dommel/ece095d3dcbd67a02c6f31b26861434da35bc669/assets/dapper-plus-sponsor.png
--------------------------------------------------------------------------------
/assets/entity-framework-extensions-sponsor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/henkmollema/Dommel/ece095d3dcbd67a02c6f31b26861434da35bc669/assets/entity-framework-extensions-sponsor.png
--------------------------------------------------------------------------------
/build.ps1:
--------------------------------------------------------------------------------
1 | function Exec
2 | {
3 | [CmdletBinding()]
4 | param(
5 | [Parameter(Position=0,Mandatory=1)][scriptblock]$cmd,
6 | [Parameter(Position=1,Mandatory=0)][string]$errorMessage = ($msgs.error_bad_command -f $cmd)
7 | )
8 | & $cmd
9 | if ($lastexitcode -ne 0) {
10 | throw ("Exec: " + $errorMessage)
11 | }
12 | }
13 |
14 | if(Test-Path .\artifacts) { Remove-Item .\artifacts -Force -Recurse }
15 |
16 | exec { & dotnet restore }
17 |
18 | #
19 | # Determine version numbers
20 | $branch = @{ $true = $env:APPVEYOR_REPO_BRANCH; $false = $(git symbolic-ref --short -q HEAD) }[$env:APPVEYOR_REPO_BRANCH -ne $NULL];
21 | $revision = @{ $true = "{0:00000}" -f [convert]::ToInt32("0" + $env:APPVEYOR_BUILD_NUMBER, 10); $false = "local" }[$env:APPVEYOR_BUILD_NUMBER -ne $NULL];
22 | $suffix = @{ $true = ""; $false = "$($branch.Substring(0, [math]::Min(10,$branch.Length)))-$revision"}[$branch -eq "master" -and $revision -ne "local"]
23 | $commitHash = $(git rev-parse --short HEAD)
24 | $buildSuffix = @{ $true = "$($suffix)-$($commitHash)"; $false = "$($branch)-$($commitHash)" }[$suffix -ne ""]
25 | echo "build: Build version suffix is $buildSuffix"
26 |
27 | exec { & dotnet build Dommel.sln -c Release --version-suffix=$buildSuffix /p:CI=true }
28 |
29 | #
30 | # Execute tests
31 | echo "build: Executing tests"
32 | exec { & dotnet test test/Dommel.Tests -c Release --no-build }
33 | exec { & dotnet test test/Dommel.IntegrationTests -c Release --no-build }
34 | exec { & dotnet test test/Dommel.Json.Tests -c Release --no-build }
35 | exec { & dotnet test test/Dommel.Json.IntegrationTests -c Release --no-build }
36 |
37 | echo "build: Calculating code coverage metrics"
38 |
39 | #
40 | # Test coverage for Dommel
41 |
42 | # Create the first coverage in the coverlet JSON format to allow merging
43 | exec { & dotnet test test/Dommel.Tests -c Release --no-build /p:CollectCoverage=true }
44 |
45 | # Merge this coverage output with the previous coverage output, this time
46 | # create a report using the opencover format which codecov can parse
47 | Push-Location -Path "test/Dommel.IntegrationTests"
48 | exec { & dotnet test -c Release --no-build /p:CollectCoverage=true /p:MergeWith="..\Dommel.Tests\coverage.json" /p:CoverletOutputFormat=opencover }
49 | if ($env:APPVEYOR_BUILD_NUMBER) {
50 | exec { & codecov -f "coverage.opencover.xml" }
51 | }
52 | Pop-Location
53 |
54 | #
55 | # Test coverage for Dommel.Json
56 | exec { & dotnet test test/Dommel.Json.Tests -c Release --no-build /p:CollectCoverage=true /p:Include="[Dommel.Json]*" }
57 |
58 | Push-Location -Path "test/Dommel.Json.IntegrationTests"
59 | exec { & dotnet test -c Release --no-build /p:CollectCoverage=true /p:Include="[Dommel.Json]*" /p:MergeWith="..\Dommel.Json.Tests\coverage.json" /p:CoverletOutputFormat=opencover }
60 | if ($env:APPVEYOR_BUILD_NUMBER) {
61 | exec { & codecov -f "coverage.opencover.xml" }
62 | }
63 | Pop-Location
64 |
65 | #
66 | # Create artifacts
67 | if ($env:APPVEYOR_BUILD_NUMBER) {
68 | $versionSuffix = "beta.{0}" -f [convert]::ToInt32("0" + $env:APPVEYOR_BUILD_NUMBER, 10)
69 | }
70 | else {
71 | $versionSuffix = $suffix
72 | }
73 |
74 | echo "build: Creating NuGet package with suffix $versionSuffix"
75 | exec { & dotnet pack .\src\Dommel\Dommel.csproj -c Release -o .\artifacts --no-build --version-suffix=$versionSuffix }
76 | exec { & dotnet pack .\src\Dommel.Json\Dommel.Json.csproj -c Release -o .\artifacts --no-build --version-suffix=$versionSuffix }
77 |
--------------------------------------------------------------------------------
/src/Dommel.Json/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 | [assembly: InternalsVisibleTo("Dommel.Json.Tests")]
3 | [assembly: InternalsVisibleTo("Dommel.Json.IntegrationTests")]
--------------------------------------------------------------------------------
/src/Dommel.Json/Dommel.Json.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netstandard2.0;net8.0;net9.0
4 | JSON support for Dommel.
5 | dommel;dapper;json
6 | true
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/Dommel.Json/DommelJsonMapper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Reflection;
4 | using Dapper;
5 |
6 | namespace Dommel.Json;
7 |
8 | ///
9 | /// Extensions to configure JSON support for Dommel.
10 | ///
11 | public static class DommelJsonMapper
12 | {
13 | ///
14 | /// Configures Dommel JSON support on the entities in the specified .
15 | ///
16 | /// The assembly to scan
17 | public static void AddJson(params Assembly[] assemblies) => AddJson(new DommelJsonOptions { EntityAssemblies = assemblies });
18 |
19 | ///
20 | /// Configures Dommel JSON support using the specified .
21 | ///
22 | ///
23 | public static void AddJson(DommelJsonOptions options)
24 | {
25 | if (options == null)
26 | {
27 | throw new ArgumentNullException(nameof(options));
28 | }
29 | if (options.EntityAssemblies == null || options.EntityAssemblies.Length == 0)
30 | {
31 | throw new ArgumentException("No entity assemblies specified.", nameof(options));
32 | }
33 |
34 | // Add SQL builders with JSON value support
35 | DommelMapper.AddSqlBuilder("sqlconnection", new SqlServerSqlBuilder());
36 | DommelMapper.AddSqlBuilder("sqlceconnection", new SqlServerCeSqlBuilder());
37 | DommelMapper.AddSqlBuilder("sqliteconnection", new SqliteSqlBuilder());
38 | DommelMapper.AddSqlBuilder("npgsqlconnection", new PostgresSqlBuilder());
39 | DommelMapper.AddSqlBuilder("mysqlconnection", new MySqlSqlBuilder());
40 |
41 | // Add a custom SqlExpression factory with JSON support
42 | DommelMapper.SqlExpressionFactory = (type, sqlBuilder) =>
43 | {
44 | if (sqlBuilder is not IJsonSqlBuilder)
45 | {
46 | throw new InvalidOperationException($"The specified SQL builder type should be assignable from {nameof(IJsonSqlBuilder)}.");
47 | }
48 |
49 | var sqlExpression = typeof(JsonSqlExpression<>).MakeGenericType(type);
50 | return Activator.CreateInstance(sqlExpression, sqlBuilder, options)!;
51 | };
52 |
53 | // Add a Dapper type mapper with JSON support for
54 | // properties annotated with the [JsonData] attribute.
55 | var jsonTypeHander = options.JsonTypeHandler?.Invoke() ?? new JsonObjectTypeHandler();
56 | var jsonTypes = new List();
57 | foreach (var assembly in options.EntityAssemblies)
58 | {
59 | foreach (var type in assembly.ExportedTypes)
60 | {
61 | foreach (var property in type.GetRuntimeProperties())
62 | {
63 | var jsonDataAttr = property.GetCustomAttribute(options.JsonDataAttributeType);
64 | if (jsonDataAttr != null)
65 | {
66 | SqlMapper.AddTypeHandler(property.PropertyType, jsonTypeHander);
67 | jsonTypes.Add(property.PropertyType);
68 | }
69 | }
70 | }
71 | }
72 |
73 | // Set a property resolver which considers the types discovered above
74 | // as primitive types so they will be used in insert and update queries.
75 | DommelMapper.SetPropertyResolver(new JsonPropertyResolver(jsonTypes));
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/Dommel.Json/DommelJsonOptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 | using Dapper;
4 |
5 | namespace Dommel.Json;
6 |
7 | ///
8 | /// Options for Dommel JSON support.
9 | ///
10 | public class DommelJsonOptions
11 | {
12 | ///
13 | /// Gets or sets the set of assemblies to scan for
14 | /// entities with [] properties.
15 | ///
16 | public Assembly[]? EntityAssemblies { get; set; }
17 |
18 | ///
19 | /// Gets or sets the Dapper type handler being used to handle JSON objects.
20 | ///
21 | public Func? JsonTypeHandler { get; set; }
22 |
23 | ///
24 | /// Gets or sets the type of the attribute which indicates that a property is a JSON data type.
25 | /// This defaults to .
26 | ///
27 | public Type JsonDataAttributeType { get; set; } = typeof(JsonDataAttribute);
28 | }
29 |
--------------------------------------------------------------------------------
/src/Dommel.Json/IJsonSqlBuilder.cs:
--------------------------------------------------------------------------------
1 | namespace Dommel.Json;
2 |
3 | ///
4 | /// Extends the with support for
5 | /// creating JSON value expressions.
6 | ///
7 | public interface IJsonSqlBuilder : ISqlBuilder
8 | {
9 | ///
10 | /// Creates a JSON value expression for the specified and .
11 | ///
12 | /// The column which contains the JSON data.
13 | /// The path of the JSON value to query.
14 | /// A JSON value expression.
15 | string JsonValue(string column, string path);
16 | }
17 |
18 | ///
19 | /// JSON SQL builder for SQL server.
20 | ///
21 | public class SqlServerSqlBuilder : Dommel.SqlServerSqlBuilder, IJsonSqlBuilder
22 | {
23 | ///
24 | public string JsonValue(string column, string path) => $"JSON_VALUE({column}, '$.{path}')";
25 | }
26 |
27 | ///
28 | /// JSON SQL builder for MySQL.
29 | ///
30 | public class MySqlSqlBuilder : Dommel.MySqlSqlBuilder, IJsonSqlBuilder
31 | {
32 | ///
33 | public string JsonValue(string column, string path) => $"{column}->'$.{path}'";
34 | }
35 |
36 | ///
37 | /// JSON SQL builder for PostgreSQL.
38 | ///
39 | public class PostgresSqlBuilder : Dommel.PostgresSqlBuilder, IJsonSqlBuilder
40 | {
41 | ///
42 | public string JsonValue(string column, string path) => $"{column}->>'{path}'";
43 | }
44 |
45 | ///
46 | /// JSON SQL builder for SQLite.
47 | ///
48 | public class SqliteSqlBuilder : Dommel.SqliteSqlBuilder, IJsonSqlBuilder
49 | {
50 | ///
51 | public string JsonValue(string column, string path) => $"JSON_EXTRACT({column}, '$.{path}')";
52 | }
53 |
54 | ///
55 | /// JSON SQL builder for SQL Server CE.
56 | ///
57 | public class SqlServerCeSqlBuilder : Dommel.SqlServerCeSqlBuilder, IJsonSqlBuilder
58 | {
59 | ///
60 | public string JsonValue(string column, string path) => $"JSON_VALUE({column}, '$.{path}')";
61 | }
62 |
--------------------------------------------------------------------------------
/src/Dommel.Json/JsonDataAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Dommel.Json;
4 |
5 | ///
6 | /// Specifies that a property is persisted as a JSON document.
7 | ///
8 | [AttributeUsage(AttributeTargets.Property)]
9 | public class JsonDataAttribute : Attribute
10 | {
11 | }
12 |
--------------------------------------------------------------------------------
/src/Dommel.Json/JsonObjectTypeHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Data;
3 | using System.Text.Json;
4 | using System.Text.Json.Serialization;
5 | using Dapper;
6 |
7 | namespace Dommel.Json;
8 |
9 | internal class JsonObjectTypeHandler : SqlMapper.ITypeHandler
10 | {
11 | private static readonly JsonSerializerOptions JsonOptions = new()
12 | {
13 | AllowTrailingCommas = true,
14 | ReadCommentHandling = JsonCommentHandling.Skip,
15 | NumberHandling = JsonNumberHandling.AllowReadingFromString,
16 | };
17 |
18 | public void SetValue(IDbDataParameter parameter, object? value)
19 | {
20 | parameter.Value = value is null || value is DBNull
21 | ? DBNull.Value
22 | : JsonSerializer.Serialize(value, JsonOptions);
23 | parameter.DbType = DbType.String;
24 | }
25 |
26 | public object? Parse(Type destinationType, object? value) =>
27 | value is string str ? JsonSerializer.Deserialize(str, destinationType, JsonOptions) : null;
28 | }
29 |
--------------------------------------------------------------------------------
/src/Dommel.Json/JsonPropertyResolver.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace Dommel.Json;
6 |
7 | internal class JsonPropertyResolver : DefaultPropertyResolver
8 | {
9 | private readonly HashSet _jsonPrimitiveTypes;
10 |
11 | public JsonPropertyResolver(IReadOnlyCollection jsonTypes)
12 | {
13 | // Append the given types to the base set of types Dommel considers
14 | // primitive so they will be used in insert and update queries.
15 | _jsonPrimitiveTypes = new HashSet(base.PrimitiveTypes.Concat(jsonTypes));
16 | JsonTypes = jsonTypes;
17 | }
18 |
19 | // Internal for testing
20 | internal IReadOnlyCollection JsonTypes { get; }
21 |
22 | protected override HashSet PrimitiveTypes => _jsonPrimitiveTypes;
23 | }
24 |
--------------------------------------------------------------------------------
/src/Dommel.Json/JsonSqlExpression.cs:
--------------------------------------------------------------------------------
1 | using System.Linq.Expressions;
2 | using System.Reflection;
3 |
4 | namespace Dommel.Json;
5 |
6 | internal class JsonSqlExpression : SqlExpression
7 | where T : class
8 | {
9 | private readonly DommelJsonOptions _options;
10 |
11 | public JsonSqlExpression(IJsonSqlBuilder sqlBuilder, DommelJsonOptions options) : base(sqlBuilder)
12 | {
13 | _options = options;
14 | }
15 |
16 | public new IJsonSqlBuilder SqlBuilder => (IJsonSqlBuilder)base.SqlBuilder;
17 |
18 | protected override object VisitMemberAccess(MemberExpression expression)
19 | {
20 | if (expression.Member is PropertyInfo jsonValue &&
21 | expression.Expression is MemberExpression jsonContainerExpr &&
22 | jsonContainerExpr.Member is PropertyInfo jsonContainer &&
23 | jsonContainer.IsDefined(_options.JsonDataAttributeType))
24 | {
25 | return SqlBuilder.JsonValue(
26 | VisitMemberAccess(jsonContainerExpr).ToString()!,
27 | ColumnNameResolver.ResolveColumnName(jsonValue));
28 | }
29 |
30 | return base.VisitMemberAccess(expression);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Dommel/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 | [assembly: InternalsVisibleTo("Dommel.Tests")]
3 | [assembly: InternalsVisibleTo("Dommel.IntegrationTests")]
4 | [assembly: InternalsVisibleTo("Dommel.Json.Tests")]
5 | [assembly: InternalsVisibleTo("Dommel.Json.IntegrationTests")]
--------------------------------------------------------------------------------
/src/Dommel/Cache.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 |
4 | namespace Dommel;
5 |
6 | internal enum QueryCacheType
7 | {
8 | Get,
9 | GetByMultipleIds,
10 | GetAll,
11 | Project,
12 | ProjectAll,
13 | Count,
14 | Insert,
15 | Update,
16 | Delete,
17 | DeleteAll,
18 | Any,
19 | }
20 |
21 | internal readonly struct QueryCacheKey : IEquatable
22 | {
23 | public QueryCacheKey(QueryCacheType cacheType, ISqlBuilder sqlBuilder, MemberInfo memberInfo)
24 | {
25 | SqlBuilderType = sqlBuilder.GetType();
26 | CacheType = cacheType;
27 | MemberInfo = memberInfo;
28 | }
29 |
30 | public QueryCacheType CacheType { get; }
31 |
32 | public Type SqlBuilderType { get; }
33 |
34 | public MemberInfo MemberInfo { get; }
35 |
36 | public readonly bool Equals(QueryCacheKey other) => CacheType == other.CacheType && SqlBuilderType == other.SqlBuilderType && MemberInfo == other.MemberInfo;
37 |
38 | public override bool Equals(object? obj) => obj is QueryCacheKey key && Equals(key);
39 |
40 | public override int GetHashCode() => CacheType.GetHashCode() + SqlBuilderType.GetHashCode() + MemberInfo.GetHashCode();
41 | }
42 |
--------------------------------------------------------------------------------
/src/Dommel/ColumnPropertyInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel.DataAnnotations.Schema;
3 | using System.Reflection;
4 |
5 | namespace Dommel;
6 |
7 | ///
8 | /// Represents the column of an entity.
9 | ///
10 | public class ColumnPropertyInfo
11 | {
12 | ///
13 | /// Initializes a new instance from the
14 | /// specified instance.
15 | ///
16 | ///
17 | /// The property which represents the database column. The is
18 | /// determined from the option specified on
19 | /// the property. Defaults to when
20 | /// is true; otherwise, .
21 | ///
22 | /// Indicates whether a property is a key column.
23 | public ColumnPropertyInfo(PropertyInfo property, bool isKey = false)
24 | {
25 | Property = property ?? throw new ArgumentNullException(nameof(property));
26 | GeneratedOption = property.GetCustomAttribute()?.DatabaseGeneratedOption
27 | ?? (isKey ? DatabaseGeneratedOption.Identity : DatabaseGeneratedOption.None);
28 | }
29 |
30 | ///
31 | /// Initializes a new instance from the
32 | /// specified instance using the specified
33 | /// .
34 | ///
35 | /// The property which represents the database column.
36 | ///
37 | /// The which specifies whether the value of
38 | /// the column this property represents is generated by the database.
39 | ///
40 | public ColumnPropertyInfo(PropertyInfo property, DatabaseGeneratedOption generatedOption)
41 | {
42 | Property = property ?? throw new ArgumentNullException(nameof(property));
43 | GeneratedOption = generatedOption;
44 | }
45 |
46 | ///
47 | /// Gets a reference to the instance.
48 | ///
49 | public PropertyInfo Property { get; }
50 |
51 | ///
52 | /// Gets the which specifies whether the value of
53 | /// the column this property represents is generated by the database.
54 | ///
55 | public DatabaseGeneratedOption GeneratedOption { get; }
56 |
57 | ///
58 | /// Gets a value indicating whether this key property's value is generated by the database.
59 | ///
60 | public bool IsGenerated => GeneratedOption != DatabaseGeneratedOption.None;
61 | }
62 |
--------------------------------------------------------------------------------
/src/Dommel/Count.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Data;
3 | using System.Linq.Expressions;
4 | using System.Threading.Tasks;
5 | using Dapper;
6 |
7 | namespace Dommel;
8 |
9 | public static partial class DommelMapper
10 | {
11 | ///
12 | /// Returns the number of all entities.
13 | ///
14 | /// The type of the entity.
15 | /// The connection to the database. This can either be open or closed.
16 | /// Optional transaction for the command.
17 | /// The number of entities matching the specified predicate.
18 | public static long Count(this IDbConnection connection, IDbTransaction? transaction = null)
19 | {
20 | var sql = BuildCountAllSql(GetSqlBuilder(connection), typeof(TEntity));
21 | LogQuery(sql);
22 | return connection.ExecuteScalar(sql, transaction);
23 | }
24 |
25 | ///
26 | /// Returns the number of all entities.
27 | ///
28 | /// The type of the entity.
29 | /// The connection to the database. This can either be open or closed.
30 | /// Optional transaction for the command.
31 | /// The number of entities matching the specified predicate.
32 | public static Task CountAsync(this IDbConnection connection, IDbTransaction? transaction = null)
33 | {
34 | var sql = BuildCountAllSql(GetSqlBuilder(connection), typeof(TEntity));
35 | LogQuery(sql);
36 | return connection.ExecuteScalarAsync(sql, transaction);
37 | }
38 |
39 | ///
40 | /// Returns the number of entities matching the specified predicate.
41 | ///
42 | /// The type of the entity.
43 | /// The connection to the database. This can either be open or closed.
44 | /// A predicate to filter the results.
45 | /// Optional transaction for the command.
46 | /// The number of entities matching the specified predicate.
47 | public static long Count(this IDbConnection connection, Expression> predicate, IDbTransaction? transaction = null)
48 | {
49 | var sql = BuildCountSql(GetSqlBuilder(connection), predicate, out var parameters);
50 | LogQuery(sql);
51 | return connection.ExecuteScalar(sql, parameters, transaction);
52 | }
53 |
54 | ///
55 | /// Returns the number of entities matching the specified predicate.
56 | ///
57 | /// The type of the entity.
58 | /// The connection to the database. This can either be open or closed.
59 | /// A predicate to filter the results.
60 | /// Optional transaction for the command.
61 | /// The number of entities matching the specified predicate.
62 | public static Task CountAsync(this IDbConnection connection, Expression> predicate, IDbTransaction? transaction = null)
63 | {
64 | var sql = BuildCountSql(GetSqlBuilder(connection), predicate, out var parameters);
65 | LogQuery(sql);
66 | return connection.ExecuteScalarAsync(sql, parameters, transaction);
67 | }
68 |
69 | internal static string BuildCountAllSql(ISqlBuilder sqlBuilder, Type type)
70 | {
71 | var cacheKey = new QueryCacheKey(QueryCacheType.Count, sqlBuilder, type);
72 | if (!QueryCache.TryGetValue(cacheKey, out var sql))
73 | {
74 | var tableName = Resolvers.Table(type, sqlBuilder);
75 | sql = $"select count(*) from {tableName}";
76 | QueryCache.TryAdd(cacheKey, sql);
77 | }
78 |
79 | return sql;
80 | }
81 |
82 | internal static string BuildCountSql(ISqlBuilder sqlBuilder, Expression> predicate, out DynamicParameters parameters)
83 | {
84 | var sql = BuildCountAllSql(sqlBuilder, typeof(TEntity));
85 | sql += CreateSqlExpression(sqlBuilder)
86 | .Where(predicate)
87 | .ToSql(out parameters);
88 | return sql;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/Dommel/DateOnlyTypeHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Data;
3 | using Dapper;
4 |
5 | namespace Dommel;
6 |
7 | #if NET6_0_OR_GREATER
8 | internal class DateOnlyTypeHandler : SqlMapper.TypeHandler
9 | {
10 | public override DateOnly Parse(object value) => DateOnly.FromDateTime((DateTime)value);
11 |
12 | public override void SetValue(IDbDataParameter parameter, DateOnly value)
13 | {
14 | parameter.DbType = DbType.Date;
15 | parameter.Value = value;
16 | }
17 | }
18 |
19 | internal class TimeOnlyTypeHandler : SqlMapper.TypeHandler
20 | {
21 | public override TimeOnly Parse(object value) => TimeOnly.FromDateTime((DateTime)value);
22 |
23 | public override void SetValue(IDbDataParameter parameter, TimeOnly value)
24 | {
25 | parameter.DbType = DbType.Time;
26 | parameter.Value = value;
27 | }
28 | }
29 | #endif
--------------------------------------------------------------------------------
/src/Dommel/DefaultColumnNameResolver.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations.Schema;
2 | using System.Reflection;
3 |
4 | namespace Dommel;
5 |
6 | ///
7 | /// Implements the .
8 | ///
9 | public class DefaultColumnNameResolver : IColumnNameResolver
10 | {
11 | ///
12 | /// Resolves the column name for the property.
13 | /// Looks for the [Column] attribute. Otherwise it's just the name of the property.
14 | ///
15 | public virtual string ResolveColumnName(PropertyInfo propertyInfo)
16 | {
17 | var columnAttr = propertyInfo.GetCustomAttribute();
18 | return columnAttr?.Name ?? propertyInfo.Name;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Dommel/DefaultForeignKeyPropertyResolver.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations.Schema;
4 | using System.Linq;
5 | using System.Reflection;
6 |
7 | namespace Dommel;
8 |
9 | ///
10 | /// Implements the interface.
11 | ///
12 | public class DefaultForeignKeyPropertyResolver : IForeignKeyPropertyResolver
13 | {
14 | ///
15 | /// Resolves the foreign key property for the specified source type and including type
16 | /// by using + Id as property name.
17 | ///
18 | /// The source type which should contain the foreign key property.
19 | /// The type of the foreign key relation.
20 | /// The foreign key relationship type.
21 | /// The foreign key property for and .
22 | public virtual PropertyInfo ResolveForeignKeyProperty(Type sourceType, Type includingType, out ForeignKeyRelation foreignKeyRelation)
23 | {
24 | var oneToOneFk = ResolveOneToOne(sourceType, includingType);
25 | if (oneToOneFk != null)
26 | {
27 | foreignKeyRelation = ForeignKeyRelation.OneToOne;
28 | return oneToOneFk;
29 | }
30 |
31 | var oneToManyFk = ResolveOneToMany(sourceType, includingType);
32 | if (oneToManyFk != null)
33 | {
34 | foreignKeyRelation = ForeignKeyRelation.OneToMany;
35 | return oneToManyFk;
36 | }
37 |
38 | throw new InvalidOperationException(
39 | $"Could not resolve foreign key property. Source type '{sourceType.FullName}'; including type: '{includingType.FullName}'.");
40 | }
41 |
42 | private static PropertyInfo? ResolveOneToOne(Type sourceType, Type includingType)
43 | {
44 | // Look for the foreign key on the source type by making an educated guess about the property name.
45 | var foreignKeyName = includingType.Name + "Id";
46 | var foreignKeyProperty = sourceType.GetProperty(foreignKeyName);
47 | if (foreignKeyProperty != null)
48 | {
49 | return foreignKeyProperty;
50 | }
51 |
52 | // Determine if the source type contains a navigation property to the including type.
53 | var navigationProperty = sourceType.GetProperties().FirstOrDefault(p => p.PropertyType == includingType);
54 | if (navigationProperty != null)
55 | {
56 | // Resolve the foreign key property from the attribute.
57 | var fkAttr = navigationProperty.GetCustomAttribute();
58 | if (fkAttr != null)
59 | {
60 | return sourceType.GetProperty(fkAttr.Name);
61 | }
62 | }
63 |
64 | return null;
65 | }
66 |
67 | private static PropertyInfo? ResolveOneToMany(Type sourceType, Type includingType)
68 | {
69 | // Look for the foreign key on the including type by making an educated guess about the property name.
70 | var foreignKeyName = sourceType.Name + "Id";
71 | var foreignKeyProperty = includingType.GetProperty(foreignKeyName);
72 | if (foreignKeyProperty != null)
73 | {
74 | return foreignKeyProperty;
75 | }
76 |
77 | var collectionType = typeof(IEnumerable<>).MakeGenericType(includingType);
78 | var navigationProperty = sourceType.GetProperties().FirstOrDefault(p => collectionType.IsAssignableFrom(p.PropertyType));
79 | if (navigationProperty != null)
80 | {
81 | // Resolve the foreign key property from the attribute.
82 | var fkAttr = navigationProperty.GetCustomAttribute();
83 | if (fkAttr != null)
84 | {
85 | return includingType.GetProperty(fkAttr.Name);
86 | }
87 | }
88 |
89 | return null;
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/Dommel/DefaultKeyPropertyResolver.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel.DataAnnotations;
3 | using System.Linq;
4 | using System.Reflection;
5 |
6 | namespace Dommel;
7 |
8 | ///
9 | /// Implements the interface by resolving key properties
10 | /// with the [] or with the name 'Id'.
11 | ///
12 | public class DefaultKeyPropertyResolver : IKeyPropertyResolver
13 | {
14 | ///
15 | /// Finds the key properties by looking for properties with the
16 | /// [] attribute or with the name 'Id'.
17 | ///
18 | public ColumnPropertyInfo[] ResolveKeyProperties(Type type)
19 | {
20 | var keyProps = Resolvers
21 | .Properties(type)
22 | .Select(x => x.Property)
23 | .Where(p => string.Equals(p.Name, "Id", StringComparison.OrdinalIgnoreCase) || p.GetCustomAttribute() != null)
24 | .ToArray();
25 |
26 | if (keyProps.Length == 0)
27 | {
28 | throw new InvalidOperationException($"Could not find the key properties for type '{type.FullName}'.");
29 | }
30 |
31 | return keyProps.Select(p => new ColumnPropertyInfo(p, isKey: true)).ToArray();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Dommel/DefaultPropertyResolver.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations.Schema;
4 | using System.Reflection;
5 |
6 | namespace Dommel;
7 |
8 | ///
9 | /// Default implemenation of the interface.
10 | ///
11 | public class DefaultPropertyResolver : IPropertyResolver
12 | {
13 | private static readonly HashSet PrimitiveTypesSet = new()
14 | {
15 | typeof(object),
16 | typeof(string),
17 | typeof(Guid),
18 | typeof(decimal),
19 | typeof(double),
20 | typeof(float),
21 | typeof(DateTime),
22 | typeof(DateTimeOffset),
23 | typeof(TimeSpan),
24 | typeof(byte[]),
25 | #if NET6_0_OR_GREATER
26 | typeof(DateOnly),
27 | typeof(TimeOnly),
28 | #endif
29 | };
30 |
31 | ///
32 | /// Resolves the properties to be mapped for the specified type.
33 | ///
34 | /// The type to resolve the properties to be mapped for.
35 | /// A collection of 's of the .
36 | public virtual IEnumerable ResolveProperties(Type type)
37 | {
38 | foreach (var property in FilterComplexTypes(type.GetRuntimeProperties()))
39 | {
40 | if (!property.IsDefined(typeof(IgnoreAttribute)) && !property.IsDefined(typeof(NotMappedAttribute)))
41 | {
42 | yield return new ColumnPropertyInfo(property);
43 | }
44 | }
45 | }
46 |
47 | ///
48 | /// Gets a collection of types that are considered 'primitive' for Dommel but are not for the CLR.
49 | /// Override this to specify your own set of types.
50 | ///
51 | protected virtual HashSet PrimitiveTypes => PrimitiveTypesSet;
52 |
53 | ///
54 | /// Filters the complex types from the specified collection of properties.
55 | ///
56 | /// A collection of properties.
57 | /// The properties that are considered 'primitive' of .
58 | protected virtual IEnumerable FilterComplexTypes(IEnumerable properties)
59 | {
60 | foreach (var property in properties)
61 | {
62 | var type = property.PropertyType;
63 | type = Nullable.GetUnderlyingType(type) ?? type;
64 | if (type.GetTypeInfo().IsPrimitive || type.GetTypeInfo().IsEnum || PrimitiveTypes.Contains(type))
65 | {
66 | yield return property;
67 | }
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Dommel/DefaultTableNameResolver.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel.DataAnnotations.Schema;
3 | using System.Reflection;
4 |
5 | namespace Dommel;
6 |
7 | ///
8 | /// Default implementation of the interface.
9 | ///
10 | public class DefaultTableNameResolver : ITableNameResolver
11 | {
12 | ///
13 | /// Resolves the table name.
14 | /// Looks for the [Table] attribute. Otherwise by making the type
15 | /// plural (eg. Product -> Products) and removing the 'I' prefix for interfaces.
16 | ///
17 | public virtual string ResolveTableName(Type type)
18 | {
19 | var typeInfo = type.GetTypeInfo();
20 | var tableAttr = typeInfo.GetCustomAttribute();
21 | if (tableAttr != null)
22 | {
23 | if (!string.IsNullOrEmpty(tableAttr.Schema))
24 | {
25 | return $"{tableAttr.Schema}.{tableAttr.Name}";
26 | }
27 |
28 | return tableAttr.Name;
29 | }
30 |
31 | // Fall back to plural of table name
32 | var name = type.Name;
33 | if (name.EndsWith("y", StringComparison.OrdinalIgnoreCase))
34 | {
35 | // Category -> Categories
36 | name = name.Remove(name.Length - 1) + "ies";
37 | }
38 | else if (!name.EndsWith("s", StringComparison.OrdinalIgnoreCase))
39 | {
40 | // Product -> Products
41 | name += "s";
42 | }
43 |
44 | return name;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Dommel/Dommel.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netstandard2.0;net8.0;net9.0
4 | Simple CRUD operations for Dapper.
5 | dommel;crud;dapper;database;orm
6 | true
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/Dommel/ForeignKeyRelation.cs:
--------------------------------------------------------------------------------
1 | namespace Dommel;
2 |
3 | ///
4 | /// Describes a foreign key relationship.
5 | ///
6 | public enum ForeignKeyRelation
7 | {
8 | ///
9 | /// Specifies a one-to-one relationship.
10 | ///
11 | OneToOne,
12 |
13 | ///
14 | /// Specifies a one-to-many relationship.
15 | ///
16 | OneToMany
17 | }
18 |
--------------------------------------------------------------------------------
/src/Dommel/From.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Data;
4 | using System.Threading.Tasks;
5 | using Dapper;
6 |
7 | namespace Dommel;
8 |
9 | public static partial class DommelMapper
10 | {
11 | ///
12 | /// Executes an expression to query data from .
13 | ///
14 | /// The entity to query data from.
15 | /// The connection to query data from.
16 | /// A callback to build a .
17 | /// Optional transaction for the command.
18 | ///
19 | /// A value indicating whether the result of the query should be executed directly,
20 | /// or when the query is materialized (using ToList() for example).
21 | ///
22 | /// The collection of entities returned from the query.
23 | public static IEnumerable From(this IDbConnection con, Action> sqlBuilder, IDbTransaction? transaction = null, bool buffered = true)
24 | {
25 | var sqlExpression = CreateSqlExpression(GetSqlBuilder(con));
26 | sqlBuilder(sqlExpression);
27 | var sql = sqlExpression.ToSql(out var parameters);
28 | LogReceived?.Invoke(sql);
29 | return con.Query(sql, parameters, transaction, buffered);
30 | }
31 |
32 | ///
33 | /// Executes an expression to query data from .
34 | ///
35 | /// The entity to query data from.
36 | /// The connection to query data from.
37 | /// A callback to build a .
38 | /// Optional transaction for the command.
39 | /// The collection of entities returned from the query.
40 | public static async Task> FromAsync(this IDbConnection con, Action> sqlBuilder, IDbTransaction? transaction = null)
41 | {
42 | var sqlExpression = CreateSqlExpression(GetSqlBuilder(con));
43 | sqlBuilder(sqlExpression);
44 | var sql = sqlExpression.ToSql(out var parameters);
45 | LogReceived?.Invoke(sql);
46 | return await con.QueryAsync(sql, parameters, transaction);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Dommel/IColumnNameResolver.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 |
3 | namespace Dommel;
4 |
5 | ///
6 | /// Defines methods for resolving column names for entities.
7 | /// Custom implementations can be registered with .
8 | ///
9 | public interface IColumnNameResolver
10 | {
11 | ///
12 | /// Resolves the column name for the specified property.
13 | ///
14 | /// The property of the entity.
15 | /// The column name for the property.
16 | string ResolveColumnName(PropertyInfo propertyInfo);
17 | }
18 |
--------------------------------------------------------------------------------
/src/Dommel/IForeignKeyPropertyResolver.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 |
4 | namespace Dommel;
5 |
6 | ///
7 | /// Defines methods for resolving foreign key properties.
8 | /// Custom implementations can be registered with .
9 | ///
10 | public interface IForeignKeyPropertyResolver
11 | {
12 | ///
13 | /// Resolves the foreign key property for the specified source type and including type.
14 | ///
15 | /// The source type which should contain the foreign key property.
16 | /// The type of the foreign key relation.
17 | /// The foreign key relationship type.
18 | /// The foreign key property for and .
19 | PropertyInfo ResolveForeignKeyProperty(Type sourceType, Type includingType, out ForeignKeyRelation foreignKeyRelation);
20 | }
21 |
--------------------------------------------------------------------------------
/src/Dommel/IKeyPropertyResolver.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 |
4 | namespace Dommel;
5 |
6 | ///
7 | /// Defines methods for resolving the key property of entities.
8 | /// Custom implementations can be registered with .
9 | ///
10 | public interface IKeyPropertyResolver
11 | {
12 | ///
13 | /// Resolves the key properties for the specified type.
14 | ///
15 | /// The type to resolve the key properties for.
16 | /// A collection of instances of the key properties of .
17 | ColumnPropertyInfo[] ResolveKeyProperties(Type type);
18 | }
19 |
--------------------------------------------------------------------------------
/src/Dommel/IPropertyResolver.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Reflection;
4 |
5 | namespace Dommel;
6 |
7 | ///
8 | /// Defines methods for resolving the properties of entities.
9 | /// Custom implementations can be registered with .
10 | ///
11 | public interface IPropertyResolver
12 | {
13 | ///
14 | /// Resolves the properties to be mapped for the specified type.
15 | ///
16 | /// The type to resolve the properties to be mapped for.
17 | /// A collection of 's of the .
18 | IEnumerable ResolveProperties(Type type);
19 | }
20 |
--------------------------------------------------------------------------------
/src/Dommel/ISqlBuilder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Dommel;
4 |
5 | ///
6 | /// Defines methods for building specialized SQL queries.
7 | ///
8 | public interface ISqlBuilder
9 | {
10 | ///
11 | /// Adds a prefix to the specified parameter.
12 | ///
13 | /// The name of the parameter to prefix.
14 | string PrefixParameter(string paramName);
15 |
16 | ///
17 | /// Builds an insert query using the specified table name, column names and parameter names.
18 | /// A query to fetch the new ID will be included as well.
19 | ///
20 | /// The type of the entity to generate the insert query for.
21 | /// The name of the table to query.
22 | /// The names of the columns in the table.
23 | /// The names of the parameters in the database command.
24 | /// An insert query including a query to fetch the new ID.
25 | string BuildInsert(Type type, string tableName, string[] columnNames, string[] paramNames);
26 |
27 | ///
28 | /// Builds the paging part to be appended to an existing select query.
29 | ///
30 | /// The order by part of the query.
31 | /// The number of the page to fetch, starting at 1.
32 | /// The page size.
33 | /// The paging part of a query.
34 | string BuildPaging(string? orderBy, int pageNumber, int pageSize);
35 |
36 | ///
37 | /// Adds quotes around (or at the start) of an identifier such as a table or column name.
38 | ///
39 | /// The identifier add quotes around. E.g. a table or column name.
40 | /// The quoted .
41 | string QuoteIdentifier(string identifier);
42 |
43 | ///
44 | /// Returns a limit clause for the specified .
45 | ///
46 | /// The count of limit clause.
47 | /// A limit clause of the specified count.
48 | string LimitClause(int count);
49 |
50 | ///
51 | /// Returns a like-expresion for the specified and .
52 | ///
53 | /// The column name of the like-expression.
54 | /// The parameter name of the like-expression.
55 | /// A like-expression.
56 | string LikeExpression(string columnName, string parameterName);
57 | }
58 |
--------------------------------------------------------------------------------
/src/Dommel/ITableNameResolver.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Dommel;
4 |
5 | ///
6 | /// Defines methods for resolving table names of entities.
7 | /// Custom implementations can be registered with .
8 | ///
9 | public interface ITableNameResolver
10 | {
11 | ///
12 | /// Resolves the table name for the specified type.
13 | ///
14 | /// The type to resolve the table name for.
15 | /// A string containing the resolved table name for for .
16 | string ResolveTableName(Type type);
17 | }
18 |
--------------------------------------------------------------------------------
/src/Dommel/IgnoreAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Dommel;
4 |
5 | ///
6 | /// Specifies that a property should be ignored.
7 | ///
8 | [AttributeUsage(AttributeTargets.Property)]
9 | public class IgnoreAttribute : Attribute
10 | {
11 | }
12 |
--------------------------------------------------------------------------------
/src/Dommel/MySqlSqlBuilder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Dommel;
4 |
5 | ///
6 | /// implementation for MySQL.
7 | ///
8 | public class MySqlSqlBuilder : ISqlBuilder
9 | {
10 | ///
11 | public virtual string BuildInsert(Type type, string tableName, string[] columnNames, string[] paramNames) =>
12 | $"insert into {tableName} ({string.Join(", ", columnNames)}) values ({string.Join(", ", paramNames)}); select LAST_INSERT_ID() id";
13 |
14 | ///
15 | public virtual string BuildPaging(string? orderBy, int pageNumber, int pageSize)
16 | {
17 | var start = pageNumber >= 1 ? (pageNumber - 1) * pageSize : 0;
18 | return $" {orderBy} limit {start}, {pageSize}";
19 | }
20 |
21 | ///
22 | public string PrefixParameter(string paramName) => $"@{paramName}";
23 |
24 | ///
25 | public string QuoteIdentifier(string identifier) => $"`{identifier}`";
26 |
27 | ///
28 | public string LimitClause(int count) => $"limit {count}";
29 |
30 | ///
31 | public string LikeExpression(string columnName, string parameterName) => $"{columnName} like {parameterName}";
32 | }
33 |
--------------------------------------------------------------------------------
/src/Dommel/PostgresSqlBuilder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 |
4 | namespace Dommel;
5 |
6 | ///
7 | /// implementation for Postgres.
8 | ///
9 | public class PostgresSqlBuilder : ISqlBuilder
10 | {
11 | ///
12 | public virtual string BuildInsert(Type type, string tableName, string[] columnNames, string[] paramNames)
13 | {
14 | if (type == null)
15 | {
16 | throw new ArgumentNullException(nameof(type));
17 | }
18 |
19 | var sql = $"insert into {tableName} ({string.Join(", ", columnNames)}) values ({string.Join(", ", paramNames)}) ";
20 |
21 | var keyColumns = Resolvers.KeyProperties(type).Where(p => p.IsGenerated).Select(p => Resolvers.Column(p.Property, this, false));
22 | if (keyColumns.Any())
23 | {
24 | sql += $"returning ({string.Join(", ", keyColumns)})";
25 | }
26 |
27 | return sql;
28 | }
29 |
30 | ///
31 | public virtual string BuildPaging(string? orderBy, int pageNumber, int pageSize)
32 | {
33 | var start = pageNumber >= 1 ? (pageNumber - 1) * pageSize : 0;
34 | return $" {orderBy} offset {start} limit {pageSize}";
35 | }
36 |
37 | ///
38 | public string PrefixParameter(string paramName) => $"@{paramName}";
39 |
40 | ///
41 | public string QuoteIdentifier(string identifier) => $"\"{identifier}\"";
42 |
43 | ///
44 | public string LimitClause(int count) => $"limit {count}";
45 |
46 | ///
47 | public string LikeExpression(string columnName, string parameterName) => $"{columnName} ilike {parameterName}";
48 | }
49 |
--------------------------------------------------------------------------------
/src/Dommel/SqlServerCeSqlBuilder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Dommel;
4 |
5 | ///
6 | /// implementation for SQL Server Compact Edition.
7 | ///
8 | public class SqlServerCeSqlBuilder : ISqlBuilder
9 | {
10 | ///
11 | public virtual string BuildInsert(Type type, string tableName, string[] columnNames, string[] paramNames) =>
12 | $"insert into {tableName} ({string.Join(", ", columnNames)}) values ({string.Join(", ", paramNames)}); select @@IDENTITY";
13 |
14 | ///
15 | public virtual string BuildPaging(string? orderBy, int pageNumber, int pageSize)
16 | {
17 | var start = pageNumber >= 1 ? (pageNumber - 1) * pageSize : 0;
18 | return $" {orderBy} offset {start} rows fetch next {pageSize} rows only";
19 | }
20 |
21 | ///
22 | public string PrefixParameter(string paramName) => $"@{paramName}";
23 |
24 | ///
25 | public string QuoteIdentifier(string identifier) => $"[{identifier}]";
26 |
27 | ///
28 | public string LimitClause(int count) => $"order by 1 offset 0 rows fetch next {count} rows only";
29 |
30 | ///
31 | public string LikeExpression(string columnName, string parameterName) => $"{columnName} like {parameterName}";
32 | }
33 |
--------------------------------------------------------------------------------
/src/Dommel/SqlServerSqlBuilder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Dommel;
4 |
5 | ///
6 | /// implementation for SQL Server.
7 | ///
8 | public class SqlServerSqlBuilder : ISqlBuilder
9 | {
10 | ///
11 | public virtual string BuildInsert(Type type, string tableName, string[] columnNames, string[] paramNames) =>
12 | $"set nocount on insert into {tableName} ({string.Join(", ", columnNames)}) values ({string.Join(", ", paramNames)}); select scope_identity()";
13 |
14 | ///
15 | public virtual string BuildPaging(string? orderBy, int pageNumber, int pageSize)
16 | {
17 | var start = pageNumber >= 1 ? (pageNumber - 1) * pageSize : 0;
18 | return $" {orderBy} offset {start} rows fetch next {pageSize} rows only";
19 | }
20 |
21 | ///
22 | public string PrefixParameter(string paramName) => $"@{paramName}";
23 |
24 | ///
25 | public string QuoteIdentifier(string identifier) => $"[{identifier}]";
26 |
27 | ///
28 | public string LimitClause(int count) => $"order by 1 offset 0 rows fetch next {count} rows only";
29 |
30 | ///
31 | public string LikeExpression(string columnName, string parameterName) => $"{columnName} like {parameterName}";
32 | }
33 |
--------------------------------------------------------------------------------
/src/Dommel/SqliteSqlBuilder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Dommel;
4 |
5 | ///
6 | /// implementation for SQLite.
7 | ///
8 | public class SqliteSqlBuilder : ISqlBuilder
9 | {
10 | ///
11 | public virtual string BuildInsert(Type type, string tableName, string[] columnNames, string[] paramNames) =>
12 | $"insert into {tableName} ({string.Join(", ", columnNames)}) values ({string.Join(", ", paramNames)}); select last_insert_rowid() id";
13 |
14 | ///
15 | public virtual string BuildPaging(string? orderBy, int pageNumber, int pageSize)
16 | {
17 | var start = pageNumber >= 1 ? (pageNumber - 1) * pageSize : 0;
18 | return $" {orderBy} LIMIT {start}, {pageSize}";
19 | }
20 |
21 | ///
22 | public string PrefixParameter(string paramName) => $"@{paramName}";
23 |
24 | ///
25 | public string QuoteIdentifier(string identifier) => identifier;
26 |
27 | ///
28 | public string LimitClause(int count) => $"limit {count}";
29 |
30 | ///
31 | public string LikeExpression(string columnName, string parameterName) => $"{columnName} like {parameterName}";
32 | }
33 |
--------------------------------------------------------------------------------
/src/Dommel/Update.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Data;
3 | using System.Linq;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using Dapper;
7 |
8 | namespace Dommel;
9 |
10 | public static partial class DommelMapper
11 | {
12 | ///
13 | /// Updates the values of the specified entity in the database.
14 | /// The return value indicates whether the operation succeeded.
15 | ///
16 | /// The type of the entity.
17 | /// The connection to the database. This can either be open or closed.
18 | /// The entity in the database.
19 | /// Optional transaction for the command.
20 | /// A value indicating whether the update operation succeeded.
21 | public static bool Update(this IDbConnection connection, TEntity entity, IDbTransaction? transaction = null)
22 | {
23 | var sql = BuildUpdateQuery(GetSqlBuilder(connection), typeof(TEntity));
24 | LogQuery(sql);
25 | return connection.Execute(sql, entity, transaction) > 0;
26 | }
27 |
28 | ///
29 | /// Updates the values of the specified entity in the database.
30 | /// The return value indicates whether the operation succeeded.
31 | ///
32 | /// The type of the entity.
33 | /// The connection to the database. This can either be open or closed.
34 | /// The entity in the database.
35 | /// Optional transaction for the command.
36 | /// Optional cancellation token for the command.
37 | /// A value indicating whether the update operation succeeded.
38 | public static async Task UpdateAsync(this IDbConnection connection, TEntity entity, IDbTransaction? transaction = null, CancellationToken cancellationToken = default)
39 | {
40 | var sql = BuildUpdateQuery(GetSqlBuilder(connection), typeof(TEntity));
41 | LogQuery(sql);
42 | return await connection.ExecuteAsync(new CommandDefinition(sql, entity, transaction: transaction, cancellationToken: cancellationToken)) > 0;
43 | }
44 |
45 | internal static string BuildUpdateQuery(ISqlBuilder sqlBuilder, Type type)
46 | {
47 | var cacheKey = new QueryCacheKey(QueryCacheType.Update, sqlBuilder, type);
48 | if (!QueryCache.TryGetValue(cacheKey, out var sql))
49 | {
50 | var tableName = Resolvers.Table(type, sqlBuilder);
51 |
52 | // Use all non-key and non-generated properties for updates
53 | var keyProperties = Resolvers.KeyProperties(type);
54 | var typeProperties = Resolvers.Properties(type)
55 | .Where(x => !x.IsGenerated)
56 | .Select(x => x.Property)
57 | .Except(keyProperties.Where(p => p.IsGenerated).Select(p => p.Property));
58 |
59 | var columnNames = typeProperties.Select(p => $"{Resolvers.Column(p, sqlBuilder, false)} = {sqlBuilder.PrefixParameter(p.Name)}").ToArray();
60 | var whereClauses = keyProperties.Select(p => $"{Resolvers.Column(p.Property, sqlBuilder, false)} = {sqlBuilder.PrefixParameter(p.Property.Name)}");
61 | sql = $"update {tableName} set {string.Join(", ", columnNames)} where {string.Join(" and ", whereClauses)}";
62 |
63 | QueryCache.TryAdd(cacheKey, sql);
64 | }
65 |
66 | return sql;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/test/Dommel.IntegrationTests/AnyTests.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Xunit;
3 |
4 | namespace Dommel.IntegrationTests;
5 |
6 | [Collection("Database")]
7 | public class AnyTests
8 | {
9 | [Theory]
10 | [ClassData(typeof(DatabaseTestData))]
11 | public void Any(DatabaseDriver database)
12 | {
13 | using var con = database.GetConnection();
14 | Assert.True(con.Any());
15 | }
16 |
17 | [Theory]
18 | [ClassData(typeof(DatabaseTestData))]
19 | public async Task AnyAsync(DatabaseDriver database)
20 | {
21 | using var con = database.GetConnection();
22 | Assert.True(await con.AnyAsync());
23 | }
24 |
25 | [Theory]
26 | [ClassData(typeof(DatabaseTestData))]
27 | public void AnyWithPredicate(DatabaseDriver database)
28 | {
29 | using var con = database.GetConnection();
30 | Assert.True(con.Any(x => x.Name != null));
31 | }
32 |
33 | [Theory]
34 | [ClassData(typeof(DatabaseTestData))]
35 | public async Task AnyAsyncWithPredicate(DatabaseDriver database)
36 | {
37 | using var con = database.GetConnection();
38 | Assert.True(await con.AnyAsync(x => x.Name != null));
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/test/Dommel.IntegrationTests/CountTests.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Xunit;
3 |
4 | namespace Dommel.IntegrationTests;
5 |
6 | [Collection("Database")]
7 | public class CountTests
8 | {
9 | [Theory]
10 | [ClassData(typeof(DatabaseTestData))]
11 | public void Count(DatabaseDriver database)
12 | {
13 | using var con = database.GetConnection();
14 | Assert.True(con.Count() > 0);
15 | }
16 |
17 | [Theory]
18 | [ClassData(typeof(DatabaseTestData))]
19 | public async Task CountAsync(DatabaseDriver database)
20 | {
21 | using var con = database.GetConnection();
22 | Assert.True(await con.CountAsync() > 0);
23 | }
24 |
25 | [Theory]
26 | [ClassData(typeof(DatabaseTestData))]
27 | public void CountWithPredicate(DatabaseDriver database)
28 | {
29 | using var con = database.GetConnection();
30 | Assert.True(con.Count(x => x.Name != null) > 0);
31 | }
32 |
33 | [Theory]
34 | [ClassData(typeof(DatabaseTestData))]
35 | public async Task CountAsyncWithPredicate(DatabaseDriver database)
36 | {
37 | using var con = database.GetConnection();
38 | Assert.True(await con.CountAsync(x => x.Name != null) > 0);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/test/Dommel.IntegrationTests/Databases/DatabaseDriver.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Data.Common;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 | using Dapper;
7 |
8 | namespace Dommel.IntegrationTests;
9 |
10 | ///
11 | /// Provides a driver to interact with a specific database system.
12 | ///
13 | public abstract class DatabaseDriver
14 | {
15 | public abstract string TempDbDatabaseName { get; }
16 |
17 | public virtual string DefaultDatabaseName => "dommeltests";
18 |
19 | public abstract DbConnection GetConnection(string databaseName);
20 |
21 | public DbConnection GetConnection() => GetConnection(DefaultDatabaseName);
22 |
23 | public virtual async Task InitializeAsync()
24 | {
25 | await CreateDatabase();
26 | var created = await CreateTables();
27 |
28 | // Is the table created? If so, insert dummy data
29 | if (created)
30 | {
31 | using var connection = GetConnection();
32 | await connection.OpenAsync();
33 |
34 | var categoryId1 = Convert.ToInt32(await connection.InsertAsync(new Category { Name = "Food" }));
35 | var categoryId2 = Convert.ToInt32(await connection.InsertAsync(new Category { Name = "Food 2" }));
36 |
37 | var products = new List
38 | {
39 | new Product { CategoryId = categoryId1, Name = "Chai" },
40 | new Product { CategoryId = categoryId1, Name = "Chang" },
41 | new Product { CategoryId = categoryId1, Name = "Aniseed Syrup" },
42 | new Product { CategoryId = categoryId1, Name = "Chef Anton's Cajun Seasoning" },
43 | new Product { CategoryId = categoryId1, Name = "Chef Anton's Gumbo Mix" },
44 |
45 | new Product { CategoryId = categoryId2, Name = "Chai 2" },
46 | new Product { CategoryId = categoryId2, Name = "Chang 2" },
47 | new Product { CategoryId = categoryId2, Name = "Aniseed Syrup 2" },
48 | new Product { CategoryId = categoryId2, Name = "Chef Anton's Cajun Seasoning 2" },
49 | new Product { CategoryId = categoryId2, Name = "Chef Anton's Gumbo Mix 2" },
50 |
51 | new Product { Name = "Foo" }, // 11
52 | new Product { Name = "Bar" }, // 12
53 | new Product { Name = "Baz" }, // 13
54 | };
55 |
56 | await connection.InsertAllAsync(products);
57 |
58 | var productId = (await connection.FirstOrDefaultAsync(x => x.Name == "Chai"))!.ProductId;
59 | await connection.InsertAsync(new ProductOption { ProductId = productId });
60 |
61 | // Order 1
62 | var orderId = Convert.ToInt32(await connection.InsertAsync(new Order { Created = new DateTime(2011, 1, 1) }));
63 | var orderLines = new List
64 | {
65 | new OrderLine { OrderId = orderId, Line = "Line 1"},
66 | new OrderLine { OrderId = orderId, Line = "Line 2"},
67 | new OrderLine { OrderId = orderId, Line = "Line 3"},
68 | };
69 | await connection.InsertAllAsync(orderLines);
70 |
71 | // Order 2
72 | _ = await connection.InsertAsync(new Order { Created = new DateTime(2012, 2, 2) });
73 |
74 | // Foo's and Bar's for delete queries
75 | await connection.InsertAllAsync(Enumerable.Range(0, 5).Select(_ => new Foo()));
76 | await connection.InsertAllAsync(Enumerable.Range(0, 5).Select(_ => new Bar()));
77 |
78 | // Composite key entities
79 | await connection.InsertAsync(new ProductsCategories { ProductId = 1, CategoryId = 1 });
80 | await connection.InsertAsync(new ProductsCategories { ProductId = 1, CategoryId = 2 });
81 | await connection.InsertAsync(new ProductsCategories { ProductId = 3, CategoryId = 1 });
82 | }
83 | }
84 |
85 | protected abstract Task CreateDatabase();
86 |
87 | protected abstract Task CreateTables();
88 |
89 | protected virtual async Task DropTables()
90 | {
91 | using var con = GetConnection(DefaultDatabaseName);
92 | var sqlBuilder = DommelMapper.GetSqlBuilder(con);
93 | string Quote(string s) => sqlBuilder.QuoteIdentifier(s);
94 |
95 | await con.ExecuteAsync($@"
96 | DROP TABLE {Quote("Categories")};
97 | DROP TABLE {Quote("Products")};
98 | DROP TABLE {Quote("ProductsCategories")};
99 | DROP TABLE {Quote("ProductOptions")};
100 | DROP TABLE {Quote("Orders")};
101 | DROP TABLE {Quote("OrderLines")};
102 | DROP TABLE {Quote("Foos")};
103 | DROP TABLE {Quote("Bars")};
104 | DROP TABLE {Quote("Bazs")};");
105 | }
106 |
107 | public virtual async Task DisposeAsync() => await DropTables();
108 | }
109 |
--------------------------------------------------------------------------------
/test/Dommel.IntegrationTests/Databases/MySqlDatabaseDriver.cs:
--------------------------------------------------------------------------------
1 | using System.Data.Common;
2 | using System.Threading.Tasks;
3 | using Dapper;
4 | using MySqlConnector;
5 |
6 | namespace Dommel.IntegrationTests;
7 |
8 | public class MySqlDatabaseDriver : DatabaseDriver
9 | {
10 | public override DbConnection GetConnection(string databaseName)
11 | {
12 | var connectionString = $"Server=localhost;Database={databaseName};Uid=dommeltest;Pwd=test;";
13 | if (CI.IsAppVeyor)
14 | {
15 | connectionString = $"Server=localhost;Database={databaseName};Uid=root;Pwd=Password12!;";
16 | }
17 | else if (CI.IsTravis)
18 | {
19 | connectionString = $"Server=localhost;Database={databaseName};Uid=root;Pwd=;";
20 | }
21 |
22 | return new MySqlConnection(connectionString);
23 | }
24 |
25 | public override string TempDbDatabaseName => "mysql";
26 |
27 | protected override async Task CreateDatabase()
28 | {
29 | using var con = GetConnection(TempDbDatabaseName);
30 | await con.ExecuteAsync($"CREATE DATABASE IF NOT EXISTS {DefaultDatabaseName}");
31 | }
32 |
33 | protected override async Task CreateTables()
34 | {
35 | using var con = GetConnection(DefaultDatabaseName);
36 | var sql = @"
37 | SELECT * FROM information_schema.tables where table_name = 'Products' LIMIT 1;
38 | CREATE TABLE IF NOT EXISTS Categories (CategoryId INT AUTO_INCREMENT PRIMARY KEY, Name VARCHAR(255));
39 | CREATE TABLE IF NOT EXISTS ProductsCategories (ProductId INT, CategoryId INT, PRIMARY KEY (ProductId, CategoryId));
40 | CREATE TABLE IF NOT EXISTS Products (ProductId INT AUTO_INCREMENT PRIMARY KEY, CategoryId int, FullName VARCHAR(255), Slug VARCHAR(255));
41 | CREATE TABLE IF NOT EXISTS ProductOptions (Id INT AUTO_INCREMENT PRIMARY KEY, ProductId INT);
42 | CREATE TABLE IF NOT EXISTS Orders (Id INT AUTO_INCREMENT PRIMARY KEY, Created DATETIME NOT NULL);
43 | CREATE TABLE IF NOT EXISTS OrderLines (Id INT AUTO_INCREMENT PRIMARY KEY, OrderId int, Line VARCHAR(255));
44 | CREATE TABLE IF NOT EXISTS Foos (Id INT AUTO_INCREMENT PRIMARY KEY, Name VARCHAR(255));
45 | CREATE TABLE IF NOT EXISTS Bars (Id INT AUTO_INCREMENT PRIMARY KEY, Name VARCHAR(255));
46 | CREATE TABLE IF NOT EXISTS Bazs (BazId CHAR(36) PRIMARY KEY, Name VARCHAR(255));";
47 | var created = await con.ExecuteScalarAsync(sql);
48 |
49 | // No result means the tables were just created
50 | return created == null;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/test/Dommel.IntegrationTests/Databases/PostgresDatabaseDriver.cs:
--------------------------------------------------------------------------------
1 | using System.Data.Common;
2 | using System.Threading.Tasks;
3 | using Dapper;
4 | using Npgsql;
5 |
6 | namespace Dommel.IntegrationTests;
7 |
8 | public class PostgresDatabaseDriver : DatabaseDriver
9 | {
10 | public override DbConnection GetConnection(string databaseName)
11 | {
12 | var connectionString = $"Server=localhost;Port=5432;Database={databaseName};Uid=postgres;Pwd=postgres;";
13 | if (CI.IsAppVeyor)
14 | {
15 | connectionString = $"Server=localhost;Port=5432;Database={databaseName};Uid=postgres;Pwd=Password12!;";
16 | }
17 | else if (CI.IsTravis)
18 | {
19 | connectionString = $"Server=localhost;Port=5432;Database={databaseName};Uid=postgres;Pwd=;";
20 | }
21 |
22 | return new NpgsqlConnection(connectionString);
23 | }
24 |
25 | public override string TempDbDatabaseName => "postgres";
26 |
27 | protected override async Task CreateDatabase()
28 | {
29 | using var con = GetConnection(TempDbDatabaseName);
30 | try
31 | {
32 | // Always try to create the database as you'll run into
33 | // race conditions when tests run in parallel.
34 | await con.ExecuteAsync($"CREATE DATABASE {DefaultDatabaseName}");
35 | }
36 | catch (PostgresException pex) when (pex.SqlState == "42P04")
37 | {
38 | // Ignore errors that the database already exists
39 | }
40 | }
41 |
42 | protected override async Task CreateTables()
43 | {
44 | using var con = GetConnection(DefaultDatabaseName);
45 | var sql = @"
46 | SELECT * FROM information_schema.tables WHERE table_name = 'Products' LIMIT 1;
47 | CREATE TABLE IF NOT EXISTS ""Categories"" (""CategoryId"" SERIAL PRIMARY KEY, ""Name"" VARCHAR(255));
48 | CREATE TABLE IF NOT EXISTS ""Products"" (""ProductId"" SERIAL PRIMARY KEY, ""CategoryId"" INT, ""FullName"" VARCHAR(255), ""Slug"" VARCHAR(255));
49 | CREATE TABLE IF NOT EXISTS ""ProductOptions"" (""Id"" SERIAL PRIMARY KEY, ""ProductId"" INT);
50 | CREATE TABLE IF NOT EXISTS ""ProductsCategories"" (""ProductId"" INT, ""CategoryId"" INT, PRIMARY KEY (""ProductId"", ""CategoryId""));
51 | CREATE TABLE IF NOT EXISTS ""Orders"" (""Id"" SERIAL PRIMARY KEY, ""Created"" TIMESTAMP NOT NULL);
52 | CREATE TABLE IF NOT EXISTS ""OrderLines"" (""Id"" SERIAL PRIMARY KEY, ""OrderId"" INT, ""Line"" VARCHAR(255));
53 | CREATE TABLE IF NOT EXISTS ""Foos"" (""Id"" SERIAL PRIMARY KEY, ""Name"" VARCHAR(255));
54 | CREATE TABLE IF NOT EXISTS ""Bars"" (""Id"" SERIAL PRIMARY KEY, ""Name"" VARCHAR(255));
55 | CREATE TABLE IF NOT EXISTS ""Bazs"" (""BazId"" UUID primary key, ""Name"" VARCHAR(255));";
56 | var created = await con.ExecuteScalarAsync(sql);
57 |
58 | // No result means the tables were just created
59 | return created == null;
60 | }
61 | }
--------------------------------------------------------------------------------
/test/Dommel.IntegrationTests/Databases/SqlServerDatabaseDriver.cs:
--------------------------------------------------------------------------------
1 | using System.Data.Common;
2 | using System.Threading.Tasks;
3 | using Dapper;
4 | using Microsoft.Data.SqlClient;
5 |
6 | namespace Dommel.IntegrationTests;
7 |
8 | public class SqlServerDatabaseDriver : DatabaseDriver
9 | {
10 | public override DbConnection GetConnection(string databaseName)
11 | {
12 | var connectionString = CI.IsAppVeyor
13 | ? $"Server=(local)\\SQL2019;Database={databaseName};User ID=sa;Password=Password12!;Encrypt=False"
14 | : $"Server=(LocalDb)\\mssqllocaldb;Database={databaseName};User ID=dommel;Password=dommel;Encrypt=False";
15 |
16 | return new SqlConnection(connectionString);
17 | }
18 |
19 | public override string TempDbDatabaseName => "tempdb";
20 |
21 | protected override async Task CreateDatabase()
22 | {
23 | using var con = GetConnection(TempDbDatabaseName);
24 | await con.ExecuteAsync($"IF NOT EXISTS (SELECT * FROM sys.databases WHERE name = N'{DefaultDatabaseName}') BEGIN CREATE DATABASE {DefaultDatabaseName}; END;");
25 | }
26 |
27 | protected override async Task CreateTables()
28 | {
29 | using var con = GetConnection(DefaultDatabaseName);
30 | var sql = @"IF OBJECT_ID(N'dbo.Products', N'U') IS NULL
31 | BEGIN
32 | CREATE TABLE dbo.Categories (CategoryId INT IDENTITY(1,1) PRIMARY KEY, Name VARCHAR(255));
33 | CREATE TABLE dbo.Products (ProductId INT IDENTITY(1,1) PRIMARY KEY, CategoryId int, FullName VARCHAR(255), Slug VARCHAR(255));
34 | CREATE TABLE dbo.ProductOptions (Id INT IDENTITY(1,1) PRIMARY KEY, ProductId INT);
35 | CREATE TABLE dbo.ProductsCategories (ProductId INT, CategoryId INT, PRIMARY KEY (ProductId, CategoryId));
36 | CREATE TABLE dbo.Orders (Id INT IDENTITY(1,1) PRIMARY KEY, Created DATETIME NOT NULL);
37 | CREATE TABLE dbo.OrderLines (Id INT IDENTITY(1,1) PRIMARY KEY, OrderId int, Line VARCHAR(255));
38 | CREATE TABLE dbo.Foos (Id INT IDENTITY(1,1) PRIMARY KEY, Name VARCHAR(255));
39 | CREATE TABLE dbo.Bars (Id INT IDENTITY(1,1) PRIMARY KEY, Name VARCHAR(255));
40 | CREATE TABLE dbo.Bazs (BazId UNIQUEIDENTIFIER PRIMARY KEY, Name VARCHAR(255));
41 | SELECT 1;
42 | END";
43 | var created = await con.ExecuteScalarAsync(sql);
44 |
45 | // A result means the tables were just created
46 | return created != null;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/test/Dommel.IntegrationTests/DeleteTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Xunit;
6 |
7 | namespace Dommel.IntegrationTests;
8 |
9 | [Collection("Database")]
10 | public class DeleteTests
11 | {
12 | [Theory]
13 | [ClassData(typeof(DatabaseTestData))]
14 | public void Delete(DatabaseDriver database)
15 | {
16 | using var con = database.GetConnection();
17 | var id = Convert.ToInt32(con.Insert(new Product { Name = "blah" }));
18 | var product = con.Get(id);
19 | Assert.NotNull(product);
20 | Assert.Equal("blah", product!.Name);
21 | Assert.Equal(id, product.ProductId);
22 |
23 | con.Delete(product);
24 | Assert.Null(con.Get(id));
25 | }
26 |
27 | [Theory]
28 | [ClassData(typeof(DatabaseTestData))]
29 | public async Task DeleteAsync(DatabaseDriver database)
30 | {
31 | using var con = database.GetConnection();
32 | var id = Convert.ToInt32(await con.InsertAsync(new Product { Name = "blah" }));
33 | var product = await con.GetAsync(id);
34 | Assert.NotNull(product);
35 | Assert.Equal("blah", product!.Name);
36 | Assert.Equal(id, product.ProductId);
37 |
38 | await con.DeleteAsync(product);
39 | Assert.Null(await con.GetAsync(id));
40 | }
41 |
42 | [Theory]
43 | [ClassData(typeof(DatabaseTestData))]
44 | public void DeleteAll(DatabaseDriver database)
45 | {
46 | using var con = database.GetConnection();
47 | Assert.True(con.DeleteAll() > 0);
48 | Assert.Empty(con.GetAll());
49 | }
50 |
51 | [Theory]
52 | [ClassData(typeof(DatabaseTestData))]
53 | public async Task DeleteAllAsync(DatabaseDriver database)
54 | {
55 | using var con = database.GetConnection();
56 | Assert.True(await con.DeleteAllAsync() > 0);
57 | Assert.Empty(await con.GetAllAsync());
58 | }
59 |
60 | [Theory]
61 | [ClassData(typeof(DatabaseTestData))]
62 | public void DeleteMultiple(DatabaseDriver database)
63 | {
64 | using var con = database.GetConnection();
65 | var ps = new List
66 | {
67 | new Product { Name = "blah"},
68 | new Product { Name = "blah"},
69 | new Product { Name = "blah"},
70 | };
71 |
72 | con.InsertAll(ps);
73 |
74 | Assert.Equal(3, con.Select(p => p.Name == "blah").Count());
75 |
76 | con.DeleteMultiple(p => p.Name == "blah");
77 | }
78 |
79 | [Theory]
80 | [ClassData(typeof(DatabaseTestData))]
81 | public async Task DeleteMultipleAsync(DatabaseDriver database)
82 | {
83 | using var con = database.GetConnection();
84 | var ps = new List
85 | {
86 | new Product { Name = "blah"},
87 | new Product { Name = "blah"},
88 | new Product { Name = "blah"},
89 | };
90 |
91 | await con.InsertAllAsync(ps);
92 |
93 | Assert.Equal(3, (await con.SelectAsync(p => p.Name == "blah")).Count());
94 |
95 | await con.DeleteMultipleAsync(p => p.Name == "blah");
96 | }
97 |
98 | [Theory]
99 | [ClassData(typeof(DatabaseTestData))]
100 | public void DeleteMultipleLike(DatabaseDriver database)
101 | {
102 | using var con = database.GetConnection();
103 | var ps = new List
104 | {
105 | new Product { Name = "blah"},
106 | new Product { Name = "blah"},
107 | new Product { Name = "blah"},
108 | };
109 |
110 | con.InsertAll(ps);
111 |
112 | Assert.Equal(3, con.Select(p => p.Name == "blah").Count());
113 |
114 | con.DeleteMultiple(p => p.Name!.Contains("bla"));
115 | }
116 |
117 | [Theory]
118 | [ClassData(typeof(DatabaseTestData))]
119 | public async Task DeleteMultipleAsyncLike(DatabaseDriver database)
120 | {
121 | using var con = database.GetConnection();
122 | var ps = new List
123 | {
124 | new Product { Name = "blah"},
125 | new Product { Name = "blah"},
126 | new Product { Name = "blah"},
127 | };
128 |
129 | await con.InsertAllAsync(ps);
130 |
131 | Assert.Equal(3, (await con.SelectAsync(p => p.Name == "blah")).Count());
132 |
133 | await con.DeleteMultipleAsync(p => p.Name!.Contains("bla"));
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/test/Dommel.IntegrationTests/Dommel.IntegrationTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net9.0
4 | false
5 |
6 |
7 |
8 |
9 | runtime; build; native; contentfiles; analyzers
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | all
18 | runtime; build; native; contentfiles; analyzers; buildtransitive
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/test/Dommel.IntegrationTests/FirstOrDefaultTests.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Xunit;
3 |
4 | namespace Dommel.IntegrationTests;
5 |
6 | [Collection("Database")]
7 | public class FirstOrDefaultTests
8 | {
9 | [Theory]
10 | [ClassData(typeof(DatabaseTestData))]
11 | public void FirstOrDefault_Equals(DatabaseDriver database)
12 | {
13 | using var con = database.GetConnection();
14 | var product = con.FirstOrDefault(p => p.CategoryId == 1);
15 | Assert.NotNull(product);
16 | }
17 | [Theory]
18 | [ClassData(typeof(DatabaseTestData))]
19 | public async Task FirstOrDefaultAsync_Equals(DatabaseDriver database)
20 | {
21 | using var con = database.GetConnection();
22 | var product = await con.FirstOrDefaultAsync(p => p.CategoryId == 1);
23 | Assert.NotNull(product);
24 | }
25 |
26 | [Theory]
27 | [ClassData(typeof(DatabaseTestData))]
28 | public void FirstOrDefault_ContainsConstant(DatabaseDriver database)
29 | {
30 | using var con = database.GetConnection();
31 | var product = con.FirstOrDefault(p => p.Name!.Contains("Anton"));
32 | Assert.NotNull(product);
33 | }
34 |
35 | [Theory]
36 | [ClassData(typeof(DatabaseTestData))]
37 | public async Task FirstOrDefaultAsync_ContainsConstant(DatabaseDriver database)
38 | {
39 | using var con = database.GetConnection();
40 | var product = await con.FirstOrDefaultAsync(p => p.Name!.Contains("Anton"));
41 | Assert.NotNull(product);
42 | }
43 |
44 | [Theory]
45 | [ClassData(typeof(DatabaseTestData))]
46 | public void FirstOrDefault_ContainsVariable(DatabaseDriver database)
47 | {
48 | var productName = "Anton";
49 | using var con = database.GetConnection();
50 | var product = con.FirstOrDefault(p => p.Name!.Contains(productName));
51 | Assert.NotNull(product);
52 | }
53 |
54 | [Theory]
55 | [ClassData(typeof(DatabaseTestData))]
56 | public async Task FirstOrDefaultAsync_ContainsVariable(DatabaseDriver database)
57 | {
58 | var productName = "Anton";
59 | using var con = database.GetConnection();
60 | var product = await con.FirstOrDefaultAsync(p => p.Name!.Contains(productName));
61 | Assert.NotNull(product);
62 | }
63 |
64 | [Theory]
65 | [ClassData(typeof(DatabaseTestData))]
66 | public void FirstOrDefault_StartsWith(DatabaseDriver database)
67 | {
68 | var productName = "Cha";
69 | using var con = database.GetConnection();
70 | var product = con.FirstOrDefault(p => p.Name!.StartsWith(productName));
71 | Assert.NotNull(product);
72 | }
73 |
74 | [Theory]
75 | [ClassData(typeof(DatabaseTestData))]
76 | public async Task FirstOrDefaultAsync_StartsWith(DatabaseDriver database)
77 | {
78 | var productName = "Cha";
79 | using var con = database.GetConnection();
80 | var product = await con.FirstOrDefaultAsync(p => p.Name!.StartsWith(productName));
81 | Assert.NotNull(product);
82 | }
83 |
84 | [Theory]
85 | [ClassData(typeof(DatabaseTestData))]
86 | public void FirstOrDefault_EndsWith(DatabaseDriver database)
87 | {
88 | var productName = "2";
89 | using var con = database.GetConnection();
90 | var product = con.FirstOrDefault(p => p.Name!.EndsWith(productName));
91 | Assert.NotNull(product);
92 | }
93 |
94 | [Theory]
95 | [ClassData(typeof(DatabaseTestData))]
96 | public async Task FirstOrDefaultAsync_EndsWith(DatabaseDriver database)
97 | {
98 | var productName = "2";
99 | using var con = database.GetConnection();
100 | var product = await con.FirstOrDefaultAsync(p => p.Name!.EndsWith(productName));
101 | Assert.NotNull(product);
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/test/Dommel.IntegrationTests/FromTests.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Threading.Tasks;
3 | using Xunit;
4 |
5 | namespace Dommel.IntegrationTests;
6 |
7 | [Collection("Database")]
8 | public class FromTests
9 | {
10 | [Theory]
11 | [ClassData(typeof(DatabaseTestData))]
12 | public void SelectAllSync(DatabaseDriver database)
13 | {
14 | using var con = database.GetConnection();
15 | var products = con.From(sql => sql.Select());
16 | Assert.NotEmpty(products);
17 | }
18 |
19 | [Theory]
20 | [ClassData(typeof(DatabaseTestData))]
21 | public void SelectProjectSync(DatabaseDriver database)
22 | {
23 | using var con = database.GetConnection();
24 | var products = con.From(sql =>
25 | sql.Select(p => new { p.ProductId, p.Name }));
26 | Assert.NotEmpty(products);
27 | Assert.All(products, p => Assert.Equal(0, p.CategoryId));
28 | }
29 |
30 | [Theory]
31 | [ClassData(typeof(DatabaseTestData))]
32 | public async Task SelectAll(DatabaseDriver database)
33 | {
34 | using var con = database.GetConnection();
35 | var products = await con.FromAsync(sql => sql.Select());
36 | Assert.NotEmpty(products);
37 | }
38 |
39 | [Theory]
40 | [ClassData(typeof(DatabaseTestData))]
41 | public async Task SelectProject(DatabaseDriver database)
42 | {
43 | using var con = database.GetConnection();
44 | var products = await con.FromAsync(sql =>
45 | sql.Select(p => new { p.ProductId, p.Name }));
46 | Assert.NotEmpty(products);
47 | Assert.All(products, p => Assert.Equal(0, p.CategoryId));
48 | }
49 |
50 | [Theory]
51 | [ClassData(typeof(DatabaseTestData))]
52 | public async Task Select_Where(DatabaseDriver database)
53 | {
54 | using var con = database.GetConnection();
55 | var products = await con.FromAsync(
56 | sql => sql.Select(p => new { p.Name, p.CategoryId })
57 | .Where(p => p.Name!.StartsWith("Chai")));
58 |
59 | Assert.NotEmpty(products);
60 | Assert.All(products, p => Assert.StartsWith("Chai", p.Name));
61 | }
62 |
63 | [Theory]
64 | [ClassData(typeof(DatabaseTestData))]
65 | public async Task OrderBy(DatabaseDriver database)
66 | {
67 | using var con = database.GetConnection();
68 | var products = await con.FromAsync(
69 | sql => sql.OrderBy(p => p.Name).Select());
70 |
71 | Assert.NotEmpty(products);
72 | }
73 |
74 | [Theory]
75 | [ClassData(typeof(DatabaseTestData))]
76 | public async Task OrderByPropertyInfo(DatabaseDriver database)
77 | {
78 | using var con = database.GetConnection();
79 | var products = await con.FromAsync(
80 | sql => sql.OrderBy(typeof(Product).GetProperty("Name")!).Select());
81 |
82 | Assert.NotEmpty(products);
83 | }
84 |
85 | [Theory]
86 | [ClassData(typeof(DatabaseTestData))]
87 | public async Task KitchenSink(DatabaseDriver database)
88 | {
89 | using var con = database.GetConnection();
90 | var products = await con.FromAsync(sql =>
91 | sql.Select(p => new { p.Name, p.CategoryId })
92 | .Where(p => p.Name!.StartsWith("Chai") && p.CategoryId == 1)
93 | .OrWhere(p => p.Name != null)
94 | .AndWhere(p => p.CategoryId != 0)
95 | .OrderBy(p => p.CategoryId)
96 | .OrderByDescending(p => p.Name)
97 | .Page(1, 5));
98 |
99 | Assert.Equal(5, products.Count());
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/test/Dommel.IntegrationTests/GetAllTests.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Xunit;
3 |
4 | namespace Dommel.IntegrationTests;
5 |
6 | [Collection("Database")]
7 | public class GetAllTests
8 | {
9 | [Theory]
10 | [ClassData(typeof(DatabaseTestData))]
11 | public void GetAll(DatabaseDriver database)
12 | {
13 | using var con = database.GetConnection();
14 | var products = con.GetAll();
15 | Assert.NotEmpty(products);
16 | Assert.All(products, p => Assert.NotEmpty(p.Name!));
17 | }
18 |
19 | [Theory]
20 | [ClassData(typeof(DatabaseTestData))]
21 | public async Task GetAllAsync(DatabaseDriver database)
22 | {
23 | using var con = database.GetConnection();
24 | var products = await con.GetAllAsync();
25 | Assert.NotEmpty(products);
26 | Assert.All(products, p => Assert.NotEmpty(p.Name!));
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/test/Dommel.IntegrationTests/GetTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel.DataAnnotations;
3 | using System.ComponentModel.DataAnnotations.Schema;
4 | using System.Threading.Tasks;
5 | using Xunit;
6 |
7 | namespace Dommel.IntegrationTests;
8 |
9 | [Collection("Database")]
10 | public class GetTests
11 | {
12 | [Theory]
13 | [ClassData(typeof(DatabaseTestData))]
14 | public void Get(DatabaseDriver database)
15 | {
16 | using var con = database.GetConnection();
17 | var product = con.Get(1);
18 | Assert.NotNull(product);
19 | Assert.NotEmpty(product!.Name!);
20 | }
21 |
22 | [Theory]
23 | [ClassData(typeof(DatabaseTestData))]
24 | public async Task GetAsync(DatabaseDriver database)
25 | {
26 | using var con = database.GetConnection();
27 | var product = await con.GetAsync(1);
28 | Assert.NotNull(product);
29 | Assert.NotEmpty(product!.Name!);
30 | }
31 |
32 | [Theory]
33 | [ClassData(typeof(DatabaseTestData))]
34 | public void Get_ParamsOverload(DatabaseDriver database)
35 | {
36 | using var con = database.GetConnection();
37 | var product = con.Get(new object[] { 1 });
38 | Assert.NotNull(product);
39 | Assert.NotEmpty(product!.Name!);
40 | }
41 |
42 | [Theory]
43 | [ClassData(typeof(DatabaseTestData))]
44 | public async Task GetAsync_ParamsOverload(DatabaseDriver database)
45 | {
46 | using var con = database.GetConnection();
47 | var product = await con.GetAsync(new object[] { 1 });
48 | Assert.NotNull(product);
49 | Assert.NotEmpty(product!.Name!);
50 | }
51 |
52 | [Theory]
53 | [ClassData(typeof(DatabaseTestData))]
54 | public void Get_ThrowsWhenCompositeKey(DatabaseDriver database)
55 | {
56 | using var con = database.GetConnection();
57 | var ex = Assert.Throws(() => con.Get(1));
58 | Assert.Equal("Entity ProductsCategories contains more than one key property.Use the Get overload which supports passing multiple IDs.", ex.Message);
59 | }
60 |
61 | [Theory]
62 | [ClassData(typeof(DatabaseTestData))]
63 | public async Task GetAsync_ThrowsWhenCompositeKey(DatabaseDriver database)
64 | {
65 | using var con = database.GetConnection();
66 | var ex = await Assert.ThrowsAsync(() => con.GetAsync(1));
67 | Assert.Equal("Entity ProductsCategories contains more than one key property.Use the Get overload which supports passing multiple IDs.", ex.Message);
68 | }
69 |
70 | [Theory]
71 | [ClassData(typeof(DatabaseTestData))]
72 | public void Get_CompositeKey(DatabaseDriver database)
73 | {
74 | using var con = database.GetConnection();
75 | var product = con.Get(1, 1);
76 | Assert.NotNull(product);
77 | Assert.Equal(1, product!.ProductId);
78 | Assert.Equal(1, product.CategoryId);
79 | }
80 |
81 | [Theory]
82 | [ClassData(typeof(DatabaseTestData))]
83 | public async Task GetAsync_CompositeKey(DatabaseDriver database)
84 | {
85 | using var con = database.GetConnection();
86 | var product = await con.GetAsync(1, 1);
87 | Assert.NotNull(product);
88 | Assert.Equal(1, product!.ProductId);
89 | Assert.Equal(1, product.CategoryId);
90 | }
91 |
92 | [Theory]
93 | [ClassData(typeof(DatabaseTestData))]
94 | public void Get_ThrowsWhenCompositeKeyArgumentsDontMatch(DatabaseDriver database)
95 | {
96 | DommelMapper.QueryCache.Clear();
97 | using var con = database.GetConnection();
98 | var ex = Assert.Throws(() => con.Get(1, 2, 3));
99 | Assert.Equal("Number of key columns (2) of type ProductsCategories does not match with the number of specified IDs (3).", ex.Message);
100 | }
101 |
102 | [Theory]
103 | [ClassData(typeof(DatabaseTestData))]
104 | public async Task GetAsync_ThrowsWhenCompositeKeyArgumentsDontMatch(DatabaseDriver database)
105 | {
106 | DommelMapper.QueryCache.Clear();
107 | using var con = database.GetConnection();
108 | var ex = await Assert.ThrowsAsync(() => con.GetAsync(1, 2, 3));
109 | Assert.Equal("Number of key columns (2) of type ProductsCategories does not match with the number of specified IDs (3).", ex.Message);
110 | }
111 | }
112 |
113 | public class ProductsCategories
114 | {
115 | [Key]
116 | [DatabaseGenerated(DatabaseGeneratedOption.None)]
117 | public int ProductId { get; set; }
118 |
119 | [Key]
120 | [DatabaseGenerated(DatabaseGeneratedOption.None)]
121 | public int CategoryId { get; set; }
122 | }
--------------------------------------------------------------------------------
/test/Dommel.IntegrationTests/Infrastructure/CI.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Dommel.IntegrationTests;
4 |
5 | public static class CI
6 | {
7 | public static bool IsAppVeyor => EnvBool("APPVEYOR");
8 |
9 | public static bool IsTravis => EnvBool("TRAVIS");
10 |
11 | private static bool EnvBool(string env) => bool.TryParse(Environment.GetEnvironmentVariable(env), out var b) && b;
12 | }
13 |
--------------------------------------------------------------------------------
/test/Dommel.IntegrationTests/Infrastructure/DatabaseCollection.cs:
--------------------------------------------------------------------------------
1 | using Xunit;
2 |
3 | namespace Dommel.IntegrationTests;
4 |
5 | // Apply the text fixture to all tests in the "Database" collection
6 | [CollectionDefinition("Database")]
7 | public class DatabaseCollection : ICollectionFixture
8 | {
9 | }
10 |
--------------------------------------------------------------------------------
/test/Dommel.IntegrationTests/Infrastructure/DatabaseFixture.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Threading.Tasks;
4 | using Xunit;
5 |
6 | namespace Dommel.IntegrationTests;
7 |
8 | public class DatabaseFixture : DatabaseFixtureBase
9 | {
10 | protected override TheoryData Drivers => new DatabaseTestData();
11 | }
12 |
13 | public abstract class DatabaseFixtureBase : IAsyncLifetime
14 | {
15 | private readonly DatabaseDriver[] _databases;
16 |
17 | public DatabaseFixtureBase()
18 | {
19 | // Extract the database drivers from the test data
20 | _databases = Drivers
21 | .Cast()
22 | .ToArray();
23 |
24 | if (_databases.Length == 0)
25 | {
26 | throw new InvalidOperationException($"No databases defined in {nameof(DatabaseTestData)} theory data.");
27 | }
28 | }
29 |
30 | protected abstract TheoryData Drivers { get; }
31 |
32 | public async Task InitializeAsync()
33 | {
34 | foreach (var database in _databases)
35 | {
36 | await database.InitializeAsync();
37 | }
38 | }
39 |
40 | public async Task DisposeAsync()
41 | {
42 | foreach (var database in _databases)
43 | {
44 | await database.DisposeAsync();
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/test/Dommel.IntegrationTests/Infrastructure/DatabaseTestData.cs:
--------------------------------------------------------------------------------
1 | using Xunit;
2 |
3 | namespace Dommel.IntegrationTests;
4 |
5 | public class DatabaseTestData : TheoryData
6 | {
7 | public DatabaseTestData()
8 | {
9 | // Defines the database providers to use for each test method.
10 | // These providers are used to initialize the databases in the
11 | // DatabaseFixture as well.
12 | if (!CI.IsTravis)
13 | {
14 | Add(new SqlServerDatabaseDriver());
15 | }
16 |
17 | Add(new MySqlDatabaseDriver());
18 | Add(new PostgresDatabaseDriver());
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/test/Dommel.IntegrationTests/InsertNonGeneratedColumnTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using Xunit;
4 |
5 | namespace Dommel.IntegrationTests;
6 |
7 | [Collection("Database")]
8 | public class InsertNonGeneratedColumnTests
9 | {
10 | [Theory]
11 | [ClassData(typeof(DatabaseTestData))]
12 | public async Task InsertAsync(DatabaseDriver database)
13 | {
14 | using var con = database.GetConnection();
15 | // Arrange
16 | var generatedId = Guid.NewGuid();
17 |
18 | // Act
19 | _ = await con.InsertAsync(new Baz { BazId = generatedId });
20 |
21 | // Assert
22 | var product = await con.GetAsync(generatedId);
23 | Assert.NotNull(product);
24 | Assert.Equal(generatedId, product!.BazId);
25 | Assert.Equal("Baz", product.Name);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/test/Dommel.IntegrationTests/InsertOutputParameterTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using Dapper;
4 | using Microsoft.Data.SqlClient;
5 | using Xunit;
6 |
7 | namespace Dommel.IntegrationTests;
8 |
9 | [Collection("Database")]
10 | public class InsertOutputParameterTests
11 | {
12 | public class Qux
13 | {
14 | public Guid Id { get; set; } = Guid.NewGuid();
15 |
16 | public string? Name { get; set; }
17 | }
18 |
19 | public class GuidSqlServerSqlBuilder : SqlServerSqlBuilder
20 | {
21 | public override string BuildInsert(Type type, string tableName, string[] columnNames, string[] paramNames) =>
22 | $"set nocount on insert into {tableName} ({string.Join(", ", columnNames)}) output inserted.Id values ({string.Join(", ", paramNames)})";
23 | }
24 |
25 | [Fact]
26 | public async Task InsertGuidPrimaryKey()
27 | {
28 | if (CI.IsTravis)
29 | {
30 | // Don't run SQL Server test on Linux
31 | return;
32 | }
33 |
34 | using var con = new SqlServerDatabaseDriver().GetConnection();
35 | await con.ExecuteAsync("CREATE TABLE dbo.Quxs (Id UNIQUEIDENTIFIER NOT NULL DEFAULT NEWID(), Name VARCHAR(255));");
36 | try
37 | {
38 | object identity;
39 | try
40 | {
41 | DommelMapper.AddSqlBuilder(typeof(SqlConnection), new GuidSqlServerSqlBuilder());
42 | identity = await con.InsertAsync(new Qux { Name = "blah" });
43 | }
44 | finally
45 | {
46 | DommelMapper.AddSqlBuilder(typeof(SqlConnection), new SqlServerSqlBuilder());
47 | }
48 |
49 | Assert.NotNull(identity);
50 | var id = Assert.IsType(identity);
51 | var baz = await con.GetAsync(id);
52 | Assert.NotNull(baz);
53 | Assert.Equal("blah", baz!.Name);
54 | Assert.Equal(id, baz.Id);
55 | }
56 | finally
57 | {
58 | await con.ExecuteAsync("DROP TABLE dbo.Quxs");
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/test/Dommel.IntegrationTests/InsertTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Xunit;
6 |
7 | namespace Dommel.IntegrationTests;
8 |
9 | [Collection("Database")]
10 | public class InsertTests
11 | {
12 | [Theory]
13 | [ClassData(typeof(DatabaseTestData))]
14 | public void Insert(DatabaseDriver database)
15 | {
16 | // Arrange
17 | using var con = database.GetConnection();
18 | var productToInsert = new Product { Name = "Foo Product" };
19 | productToInsert.SetSlug("foo-product");
20 |
21 | // Act
22 | var id = Convert.ToInt32(con.Insert(productToInsert));
23 |
24 | // Assert
25 | var product = con.Get(id);
26 | Assert.NotNull(product);
27 | Assert.Equal(id, product!.ProductId);
28 | Assert.Equal("Foo Product", product.Name);
29 | Assert.Equal("foo-product", product.Slug);
30 | }
31 |
32 | [Theory]
33 | [ClassData(typeof(DatabaseTestData))]
34 | public async Task InsertAsync(DatabaseDriver database)
35 | {
36 | // Arrange
37 | using var con = database.GetConnection();
38 | var productToInsert = new Product { Name = "Foo Product" };
39 | productToInsert.SetSlug("foo-product");
40 |
41 | // Act
42 | var id = Convert.ToInt32(await con.InsertAsync(productToInsert));
43 |
44 | // Assert
45 | var product = await con.GetAsync(id);
46 | Assert.NotNull(product);
47 | Assert.Equal(id, product!.ProductId);
48 | Assert.Equal("Foo Product", product.Name);
49 | Assert.Equal("foo-product", product.Slug);
50 | }
51 |
52 | [Theory]
53 | [ClassData(typeof(DatabaseTestData))]
54 | public void InsertAll(DatabaseDriver database)
55 | {
56 | using var con = database.GetConnection();
57 | var ps = new List
58 | {
59 | new Foo { Name = "blah" },
60 | new Foo { Name = "blah" },
61 | new Foo { Name = "blah" },
62 | };
63 |
64 | con.InsertAll(ps);
65 |
66 | var blahs = con.Select(p => p.Name == "blah");
67 | Assert.Equal(3, blahs.Count());
68 | }
69 |
70 | [Theory]
71 | [ClassData(typeof(DatabaseTestData))]
72 | public async Task InsertAllAsync(DatabaseDriver database)
73 | {
74 | using var con = database.GetConnection();
75 | var ps = new List
76 | {
77 | new Bar { Name = "blah" },
78 | new Bar { Name = "blah" },
79 | new Bar { Name = "blah" },
80 | };
81 |
82 | await con.InsertAllAsync(ps);
83 |
84 | var blahs = await con.SelectAsync(p => p.Name == "blah");
85 | Assert.Equal(3, blahs.Count());
86 | }
87 |
88 | [Theory]
89 | [ClassData(typeof(DatabaseTestData))]
90 | public void InsertAllEmtyList(DatabaseDriver database)
91 | {
92 | using var con = database.GetConnection();
93 | var ps = new List();
94 | con.InsertAll(ps);
95 | }
96 |
97 | [Theory]
98 | [ClassData(typeof(DatabaseTestData))]
99 | public async Task InsertAllAsyncEmtyList(DatabaseDriver database)
100 | {
101 | using var con = database.GetConnection();
102 | var ps = new List();
103 | await con.InsertAllAsync(ps);
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/test/Dommel.IntegrationTests/Models.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel.DataAnnotations;
4 | using System.ComponentModel.DataAnnotations.Schema;
5 |
6 | namespace Dommel.IntegrationTests;
7 |
8 | public abstract class FullNamedEntity
9 | {
10 | [Column("FullName")]
11 | public string? Name { get; set; }
12 | }
13 |
14 | public abstract class NamedEntity
15 | {
16 | public string? Name { get; set; }
17 | }
18 |
19 | public class Product : FullNamedEntity
20 | {
21 | [Key]
22 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
23 | public int ProductId { get; set; }
24 |
25 | public string? Slug { get; private set; }
26 |
27 | public void SetSlug(string slug) => Slug = slug;
28 |
29 | // The foreign key to Categories table
30 | public int CategoryId { get; set; }
31 |
32 | // The navigation property
33 | public Category? Category { get; set; }
34 |
35 | // One Product has many Options
36 | public ICollection Options { get; set; } = new List();
37 | }
38 |
39 | public class Category : NamedEntity, IEquatable
40 | {
41 | [Key]
42 | public int CategoryId { get; set; }
43 |
44 | public bool Equals(Category? other) => CategoryId == other?.CategoryId;
45 | }
46 |
47 | public class ProductOption : IEquatable
48 | {
49 | public int Id { get; set; }
50 |
51 | // One ProductOption has one Product (no navigation)
52 | // Represents the foreign key to the product table
53 | public int ProductId { get; set; }
54 |
55 | public bool Equals(ProductOption? other) => Id == other?.Id;
56 | }
57 |
58 | public class Order
59 | {
60 | public int Id { get; set; }
61 |
62 | public DateTime Created { get; set; } = DateTime.UtcNow;
63 |
64 | [ForeignKey(nameof(OrderLine.OrderId))]
65 | public ICollection? OrderLines { get; set; }
66 | }
67 |
68 | public class OrderLine
69 | {
70 | public int Id { get; set; }
71 |
72 | public int OrderId { get; set; }
73 |
74 | public string? Line { get; set; }
75 | }
76 |
77 | public class Foo : NamedEntity
78 | {
79 | public Foo()
80 | {
81 | Name = nameof(Foo);
82 | }
83 |
84 | public int Id { get; set; }
85 | }
86 |
87 | public class Bar : NamedEntity
88 | {
89 | public Bar()
90 | {
91 | Name = nameof(Bar);
92 | }
93 |
94 | public int Id { get; set; }
95 | }
96 |
97 | public class Baz
98 | {
99 | [Key]
100 | [DatabaseGenerated(DatabaseGeneratedOption.None)]
101 | public Guid BazId { get; set; }
102 |
103 | public string? Name { get; set; } = nameof(Baz);
104 | }
--------------------------------------------------------------------------------
/test/Dommel.IntegrationTests/MultiMapTests.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Xunit;
3 |
4 | namespace Dommel.IntegrationTests;
5 |
6 | [Collection("Database")]
7 | public class MultiMapTests
8 | {
9 | [Theory]
10 | [ClassData(typeof(DatabaseTestData))]
11 | public void Get(DatabaseDriver database)
12 | {
13 | using var con = database.GetConnection();
14 | var product = con.Get(1, (p, c) =>
15 | {
16 | p.Category = c;
17 | return p;
18 | });
19 |
20 | Assert.NotNull(product);
21 | Assert.NotEmpty(product!.Name!);
22 | Assert.NotNull(product.Category);
23 | Assert.NotNull(product.Category?.Name);
24 | }
25 |
26 | [Theory]
27 | [ClassData(typeof(DatabaseTestData))]
28 | public async Task GetAsync(DatabaseDriver database)
29 | {
30 | using var con = database.GetConnection();
31 | var product = await con.GetAsync(1, (p, c) =>
32 | {
33 | p.Category = c;
34 | return p;
35 | });
36 |
37 | Assert.NotNull(product);
38 | Assert.NotEmpty(product!.Name!);
39 | Assert.NotNull(product.Category);
40 | Assert.NotNull(product.Category?.Name);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/test/Dommel.IntegrationTests/PagingTests.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Threading.Tasks;
3 | using Xunit;
4 |
5 | namespace Dommel.IntegrationTests;
6 |
7 | [Collection("Database")]
8 | public class PagingTests
9 | {
10 | [Theory]
11 | [ClassData(typeof(DatabaseTestData))]
12 | public void GetPaged_FetchesFirstPage(DatabaseDriver database)
13 | {
14 | using var con = database.GetConnection();
15 | var paged = con.GetPaged(1, 5);
16 | Assert.Equal(5, paged.Count());
17 | Assert.Collection(paged,
18 | p => Assert.Equal("Chai", p.Name),
19 | p => Assert.Equal("Chang", p.Name),
20 | p => Assert.Equal("Aniseed Syrup", p.Name),
21 | p => Assert.Equal("Chef Anton's Cajun Seasoning", p.Name),
22 | p => Assert.Equal("Chef Anton's Gumbo Mix", p.Name));
23 | }
24 |
25 | [Theory]
26 | [ClassData(typeof(DatabaseTestData))]
27 | public async Task GetPagedAsync_FetchesFirstPage(DatabaseDriver database)
28 | {
29 | using var con = database.GetConnection();
30 | var paged = await con.GetPagedAsync(1, 5);
31 | Assert.Equal(5, paged.Count());
32 | Assert.Collection(paged,
33 | p => Assert.Equal("Chai", p.Name),
34 | p => Assert.Equal("Chang", p.Name),
35 | p => Assert.Equal("Aniseed Syrup", p.Name),
36 | p => Assert.Equal("Chef Anton's Cajun Seasoning", p.Name),
37 | p => Assert.Equal("Chef Anton's Gumbo Mix", p.Name));
38 | }
39 |
40 | [Theory]
41 | [ClassData(typeof(DatabaseTestData))]
42 | public void GetPaged_FetchesSecondPage(DatabaseDriver database)
43 | {
44 | using var con = database.GetConnection();
45 | var paged = con.GetPaged(2, 5);
46 | Assert.Equal(5, paged.Count());
47 | }
48 |
49 | [Theory]
50 | [ClassData(typeof(DatabaseTestData))]
51 | public async Task GetPagedAsync_FetchesSecondPage(DatabaseDriver database)
52 | {
53 | using var con = database.GetConnection();
54 | var paged = await con.GetPagedAsync(2, 5);
55 | Assert.Equal(5, paged.Count());
56 | }
57 |
58 | [Theory]
59 | [ClassData(typeof(DatabaseTestData))]
60 | public void GetPaged_FetchesThirdPartialPage(DatabaseDriver database)
61 | {
62 | using var con = database.GetConnection();
63 | var paged = con.GetPaged(3, 5);
64 | Assert.True(paged.Count() >= 3, "Should contain at least 3 items");
65 | }
66 |
67 | [Theory]
68 | [ClassData(typeof(DatabaseTestData))]
69 | public async Task GetPagedAsync_FetchesThirdPartialPage(DatabaseDriver database)
70 | {
71 | using var con = database.GetConnection();
72 | var paged = await con.GetPagedAsync(3, 5);
73 | Assert.True(paged.Count() >= 3, "Should contain at least 3 items");
74 | }
75 |
76 | [Theory]
77 | [ClassData(typeof(DatabaseTestData))]
78 | public void SelectPaged_FetchesFirstPage(DatabaseDriver database)
79 | {
80 | using var con = database.GetConnection();
81 | var paged = con.SelectPaged(p => p.Name == "Chai", 1, 5);
82 | Assert.Single(paged);
83 | }
84 |
85 | [Theory]
86 | [ClassData(typeof(DatabaseTestData))]
87 | public async Task SelectPagedAsync_FetchesFirstPage(DatabaseDriver database)
88 | {
89 | using var con = database.GetConnection();
90 | var paged = await con.SelectPagedAsync(p => p.Name == "Chai", 1, 5);
91 | Assert.Single(paged);
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/test/Dommel.IntegrationTests/ProjectTests.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 | using System.ComponentModel.DataAnnotations.Schema;
3 | using System.Threading.Tasks;
4 | using Xunit;
5 |
6 | namespace Dommel.IntegrationTests;
7 |
8 | [Collection("Database")]
9 | public class ProjectTests
10 | {
11 | [Theory]
12 | [ClassData(typeof(DatabaseTestData))]
13 | public void Project(DatabaseDriver database)
14 | {
15 | using var con = database.GetConnection();
16 | var p = con.Project(1);
17 | Assert.NotNull(p);
18 | Assert.NotEqual(0, p!.ProductId);
19 | Assert.NotNull(p.Name);
20 | }
21 |
22 | [Theory]
23 | [ClassData(typeof(DatabaseTestData))]
24 | public async Task ProjectAsync(DatabaseDriver database)
25 | {
26 | using var con = database.GetConnection();
27 | var p = await con.ProjectAsync(1);
28 | Assert.NotNull(p);
29 | Assert.NotEqual(0, p!.ProductId);
30 | Assert.NotNull(p.Name);
31 | }
32 |
33 | [Theory]
34 | [ClassData(typeof(DatabaseTestData))]
35 | public void ProjectAll(DatabaseDriver database)
36 | {
37 | using var con = database.GetConnection();
38 | var ps = con.ProjectAll();
39 | Assert.NotEmpty(ps);
40 | Assert.All(ps, p =>
41 | {
42 | Assert.NotEqual(0, p.ProductId);
43 | Assert.NotNull(p.Name);
44 | });
45 | }
46 |
47 | [Theory]
48 | [ClassData(typeof(DatabaseTestData))]
49 | public async Task ProjectAllAsync(DatabaseDriver database)
50 | {
51 | using var con = database.GetConnection();
52 | var ps = await con.ProjectAllAsync();
53 | Assert.NotEmpty(ps);
54 | Assert.All(ps, p =>
55 | {
56 | Assert.NotEqual(0, p.ProductId);
57 | Assert.NotNull(p.Name);
58 | });
59 | }
60 | [Theory]
61 | [ClassData(typeof(DatabaseTestData))]
62 | public void ProjectPagedAll(DatabaseDriver database)
63 | {
64 | using var con = database.GetConnection();
65 | var ps = con.ProjectPaged(pageNumber: 1, pageSize: 5);
66 | Assert.NotEmpty(ps);
67 | Assert.All(ps, p =>
68 | {
69 | Assert.NotEqual(0, p.ProductId);
70 | Assert.NotNull(p.Name);
71 | });
72 | }
73 |
74 | [Theory]
75 | [ClassData(typeof(DatabaseTestData))]
76 | public async Task ProjectPagedAsync(DatabaseDriver database)
77 | {
78 | using var con = database.GetConnection();
79 | var ps = await con.ProjectPagedAsync(pageNumber: 1, pageSize: 5);
80 | Assert.NotEmpty(ps);
81 | Assert.All(ps, p =>
82 | {
83 | Assert.NotEqual(0, p.ProductId);
84 | Assert.NotNull(p.Name);
85 | });
86 | }
87 |
88 | // Subset of the default Product entity
89 | [Table("Products")]
90 | public class ProductSmall
91 | {
92 | [Key]
93 | public int ProductId { get; set; }
94 |
95 | [Column("FullName")]
96 | public string? Name { get; set; }
97 | }
98 | }
--------------------------------------------------------------------------------
/test/Dommel.IntegrationTests/ResolversTests.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Data.SqlClient;
2 | using System.ComponentModel.DataAnnotations.Schema;
3 | using Xunit;
4 |
5 | namespace Dommel.Tests;
6 |
7 | public class ResolversTests
8 | {
9 | private readonly SqlConnection _sqlConnection = new();
10 |
11 | [Fact]
12 | public void Table_WithSchema()
13 | {
14 | Assert.Equal("[dbo].[Qux]", Resolvers.Table(typeof(FooQux), _sqlConnection));
15 | Assert.Equal("[foo].[dbo].[Qux]", Resolvers.Table(typeof(FooDboQux), _sqlConnection));
16 | }
17 |
18 | [Fact]
19 | public void Table_NoCacheConflictNestedClass()
20 | {
21 | Assert.Equal("[BarA]", Resolvers.Table(typeof(Foo.Bar), _sqlConnection));
22 | Assert.Equal("[BarB]", Resolvers.Table(typeof(Baz.Bar), _sqlConnection));
23 | }
24 |
25 | public class Foo
26 | {
27 | [Table("BarA")]
28 | public class Bar
29 | {
30 | [Column("BazA")]
31 | public string? Baz { get; set; }
32 | }
33 |
34 | [Table("BarA")]
35 | public class BarChild
36 | {
37 | public int BarId { get; set; }
38 | }
39 | }
40 |
41 | public class Baz
42 | {
43 | [Table("BarB")]
44 | public class Bar
45 | {
46 | [Column("BazB")]
47 | public string? Baz { get; set; }
48 | }
49 |
50 | [Table("BarA")]
51 | public class BarChild
52 | {
53 | public int BarId { get; set; }
54 | }
55 | }
56 |
57 | [Table("Qux", Schema = "foo.dbo")]
58 | public class FooDboQux
59 | {
60 | public int Id { get; set; }
61 | }
62 |
63 | [Table("Qux", Schema = "dbo")]
64 | public class FooQux
65 | {
66 | public int Id { get; set; }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/test/Dommel.IntegrationTests/SelectAutoMultiMapTests.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Threading.Tasks;
3 | using Xunit;
4 |
5 | namespace Dommel.IntegrationTests;
6 |
7 | [Collection("Database")]
8 | public class SelectAutoMultiMapTests
9 | {
10 | [Theory]
11 | [ClassData(typeof(DatabaseTestData))]
12 | public void SelectMultiMap(DatabaseDriver database)
13 | {
14 | using var con = database.GetConnection();
15 | var products = con.Select(x => x.Name != null && x.CategoryId == 1);
16 | Assert.All(products, x => Assert.NotNull(x.Category));
17 | var first = products.First();
18 | Assert.NotEmpty(first.Options);
19 | }
20 |
21 | [Theory]
22 | [ClassData(typeof(DatabaseTestData))]
23 | public void FirstOrDefaultMultiMap(DatabaseDriver database)
24 | {
25 | using var con = database.GetConnection();
26 | var product = con.FirstOrDefault(x => x.Name != null && x.CategoryId == 1);
27 | Assert.NotNull(product);
28 | Assert.NotNull(product!.Category);
29 | Assert.NotEmpty(product.Options);
30 | }
31 |
32 | [Theory]
33 | [ClassData(typeof(DatabaseTestData))]
34 | public async Task SelectAsyncMultiMap(DatabaseDriver database)
35 | {
36 | using var con = database.GetConnection();
37 | var products = await con.SelectAsync(x => x.Name != null && x.CategoryId == 1);
38 | Assert.All(products, x => Assert.NotNull(x.Category));
39 | var first = products.First();
40 | Assert.NotEmpty(first.Options);
41 | }
42 |
43 | [Theory]
44 | [ClassData(typeof(DatabaseTestData))]
45 | public async Task FirstOrDefaultAsyncMultiMap(DatabaseDriver database)
46 | {
47 | using var con = database.GetConnection();
48 | var product = await con.FirstOrDefaultAsync(x => x.Name != null && x.CategoryId == 1);
49 | Assert.NotNull(product);
50 | Assert.NotNull(product!.Category);
51 | Assert.NotEmpty(product.Options);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/test/Dommel.IntegrationTests/SelectMultiMapTests.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Threading.Tasks;
3 | using Xunit;
4 |
5 | namespace Dommel.IntegrationTests;
6 |
7 | [Collection("Database")]
8 | public class SelectMultiMapTests
9 | {
10 | [Theory]
11 | [ClassData(typeof(DatabaseTestData))]
12 | public void SelectMultiMap(DatabaseDriver database)
13 | {
14 | using var con = database.GetConnection();
15 | var products = con.Select(
16 | p => p.CategoryId == 1 && p.Name != null,
17 | (p, c, po) =>
18 | {
19 | p.Category = c;
20 | if (!p.Options.Contains(po))
21 | {
22 | p.Options.Add(po);
23 | }
24 | return p;
25 | });
26 | Assert.Equal(5, products.Count());
27 | Assert.All(products, x => Assert.NotNull(x.Category));
28 | }
29 |
30 | [Theory]
31 | [ClassData(typeof(DatabaseTestData))]
32 | public void FirstOrDefaultMultiMap(DatabaseDriver database)
33 | {
34 | using var con = database.GetConnection();
35 | var product = con.FirstOrDefault(
36 | p => p.CategoryId == 1 && p.Name != null,
37 | (p, c, po) =>
38 | {
39 | p.Category = c;
40 | if (!p.Options.Contains(po))
41 | {
42 | p.Options.Add(po);
43 | }
44 | return p;
45 | });
46 | Assert.NotNull(product);
47 | Assert.NotNull(product!.Category);
48 | Assert.NotEmpty(product.Options);
49 | }
50 |
51 | [Theory]
52 | [ClassData(typeof(DatabaseTestData))]
53 | public async Task SelectAsyncMultiMap(DatabaseDriver database)
54 | {
55 | using var con = database.GetConnection();
56 | var products = await con.SelectAsync(
57 | p => p.CategoryId == 1 && p.Name != null,
58 | (p, c, po) =>
59 | {
60 | p.Category = c;
61 | if (!p.Options.Contains(po))
62 | {
63 | p.Options.Add(po);
64 | }
65 | return p;
66 | });
67 | Assert.Equal(5, products.Count());
68 | Assert.All(products, x => Assert.NotNull(x.Category));
69 | }
70 |
71 | [Theory]
72 | [ClassData(typeof(DatabaseTestData))]
73 | public async Task FirstOrDefaultAsyncMultiMap(DatabaseDriver database)
74 | {
75 | using var con = database.GetConnection();
76 | var product = await con.FirstOrDefaultAsync(
77 | p => p.CategoryId == 1 && p.Name != null,
78 | (p, c, po) =>
79 | {
80 | p.Category = c;
81 | if (!p.Options.Contains(po))
82 | {
83 | p.Options.Add(po);
84 | }
85 | return p;
86 | });
87 | Assert.NotNull(product);
88 | Assert.NotNull(product!.Category);
89 | Assert.NotEmpty(product.Options);
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/test/Dommel.IntegrationTests/TestSample.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Xunit;
3 |
4 | namespace Dommel.IntegrationTests;
5 |
6 | [Collection("Database")]
7 | public class SampleTests
8 | {
9 | [Theory]
10 | [ClassData(typeof(DatabaseTestData))]
11 | public void Sample(DatabaseDriver database)
12 | {
13 | using var con = database.GetConnection();
14 | _ = con.GetAll();
15 | }
16 |
17 | [Theory]
18 | [ClassData(typeof(DatabaseTestData))]
19 | public async Task SampleAsync(DatabaseDriver database)
20 | {
21 | using var con = database.GetConnection();
22 | _ = await con.GetAllAsync();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/test/Dommel.IntegrationTests/UpdateTests.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Xunit;
3 |
4 | namespace Dommel.IntegrationTests;
5 |
6 | [Collection("Database")]
7 | public class UpdateTests
8 | {
9 | [Theory]
10 | [ClassData(typeof(DatabaseTestData))]
11 | public void Update(DatabaseDriver database)
12 | {
13 | using var con = database.GetConnection();
14 | var product = con.Get(1);
15 | Assert.NotNull(product);
16 | product!.Name = "Test";
17 | product.SetSlug("test");
18 | con.Update(product);
19 |
20 | var newProduct = con.Get(1);
21 | Assert.Equal("Test", newProduct!.Name);
22 | Assert.Equal("test", newProduct.Slug);
23 | }
24 |
25 | [Theory]
26 | [ClassData(typeof(DatabaseTestData))]
27 | public async Task UpdateAsync(DatabaseDriver database)
28 | {
29 | using var con = database.GetConnection();
30 | var product = await con.GetAsync(1);
31 | Assert.NotNull(product);
32 | product!.Name = "Test";
33 | product.SetSlug("test");
34 | await con.UpdateAsync(product);
35 |
36 | var newProduct = await con.GetAsync(1);
37 | Assert.Equal("Test", newProduct!.Name);
38 | Assert.Equal("test", newProduct.Slug);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/test/Dommel.IntegrationTests/coverage.cmd:
--------------------------------------------------------------------------------
1 | dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:Include="[Dommel]*"
2 | reportgenerator "-reports:coverage.opencover.xml" "-targetdir:coveragereport"
3 | start coveragereport\index.htm
4 |
--------------------------------------------------------------------------------
/test/Dommel.Json.IntegrationTests/Databases/JsonMySqlDatabaseDriver.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Dapper;
3 | using Dommel.IntegrationTests;
4 |
5 | namespace Dommel.Json.IntegrationTests;
6 |
7 | public class JsonMySqlDatabaseDriver : MySqlDatabaseDriver
8 | {
9 | public override string DefaultDatabaseName => "dommeljsontests";
10 |
11 | protected override async Task CreateTables()
12 | {
13 | using (var con = GetConnection(DefaultDatabaseName))
14 | {
15 | var sql = @"
16 | CREATE TABLE IF NOT EXISTS `Leads` (
17 | `Id` INT AUTO_INCREMENT PRIMARY KEY,
18 | `DateCreated` DATETIME,
19 | `Email` VARCHAR(255),
20 | `Data` LONGTEXT,
21 | `Metadata` LONGTEXT
22 | );";
23 | await con.ExecuteScalarAsync(sql);
24 | }
25 |
26 | return await base.CreateTables();
27 | }
28 |
29 | protected override async Task DropTables()
30 | {
31 | using (var con = GetConnection(DefaultDatabaseName))
32 | {
33 | await con.ExecuteScalarAsync("DROP TABLE `Leads`");
34 | }
35 | await base.DropTables();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/test/Dommel.Json.IntegrationTests/Databases/JsonPostgresDatabaseDriver.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Dapper;
3 | using Dommel.IntegrationTests;
4 |
5 | namespace Dommel.Json.IntegrationTests;
6 |
7 | public class JsonPostgresDatabaseDriver : PostgresDatabaseDriver
8 | {
9 | public override string DefaultDatabaseName => "dommeljsontests";
10 |
11 | protected override async Task CreateTables()
12 | {
13 | using (var con = GetConnection(DefaultDatabaseName))
14 | {
15 | var sql = @"
16 | create table if not exists ""Leads"" (
17 | ""Id"" serial primary key,
18 | ""DateCreated"" timestamp,
19 | ""Email"" varchar(255),
20 | ""Data"" json,
21 | ""Metadata"" json
22 | );";
23 | await con.ExecuteScalarAsync(sql);
24 | }
25 |
26 | return await base.CreateTables();
27 | }
28 |
29 | protected override async Task DropTables()
30 | {
31 | using (var con = GetConnection(DefaultDatabaseName))
32 | {
33 | await con.ExecuteScalarAsync(@"drop table ""Leads""");
34 | }
35 | await base.DropTables();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/test/Dommel.Json.IntegrationTests/Databases/JsonSqlServerDatabaseDriver.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Dapper;
3 | using Dommel.IntegrationTests;
4 |
5 | namespace Dommel.Json.IntegrationTests;
6 |
7 | public class JsonSqlServerDatabaseDriver : SqlServerDatabaseDriver
8 | {
9 | public override string DefaultDatabaseName => "dommeljsontests";
10 |
11 | protected override async Task CreateTables()
12 | {
13 | using (var con = GetConnection(DefaultDatabaseName))
14 | {
15 | var sql = @"
16 | CREATE TABLE Leads (
17 | Id INT IDENTITY(1,1) PRIMARY KEY,
18 | DateCreated DATETIME,
19 | Email VARCHAR(255),
20 | Data NVARCHAR(MAX),
21 | Metadata NVARCHAR(MAX)
22 | );";
23 | await con.ExecuteScalarAsync(sql);
24 | }
25 |
26 | return await base.CreateTables();
27 | }
28 |
29 | protected override async Task DropTables()
30 | {
31 | using (var con = GetConnection(DefaultDatabaseName))
32 | {
33 | await con.ExecuteScalarAsync("DROP TABLE Leads");
34 | }
35 | await base.DropTables();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/test/Dommel.Json.IntegrationTests/DeleteTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Data.Common;
3 | using System.Threading.Tasks;
4 | using Dommel.IntegrationTests;
5 | using Xunit;
6 |
7 | namespace Dommel.Json.IntegrationTests;
8 |
9 | [Collection("JSON Database")]
10 | public class DeleteTests
11 | {
12 | private static object InsertLead(DbConnection con)
13 | {
14 | return con.Insert(new Lead
15 | {
16 | Email = "foo@example.com",
17 | Data = new LeadData
18 | {
19 | FirstName = "Foo",
20 | LastName = "Bar",
21 | Birthdate = new DateTime(1985, 7, 1),
22 | Email = "foo@example.com",
23 | }
24 | });
25 | }
26 |
27 | private static async Task