Skip to content

Commit

Permalink
Merge pull request #90 from henkmollema/integration-tests
Browse files Browse the repository at this point in the history
Integration tests

- Add integration tests for SQL Server, MySQL and PostgreSQL.
- Bumped Dommel package to from .NET 4.5.1 to 4.5.2.
- Add non-async `Get` with automatic multi-map.
- Resolved several issues with parallel / multi-threaded query execution or use of multiple database systems:
  - Add `QuoteIdentifier` to `ISqlBuilder` to provide quotes around an identifier such as a column or table name. This deprecates `StartEscapeCharacter` and `EndEscapeCharacter`, which are marked as `[Obsolete]`.
  - Add a centralized cache for queries and take the database connection type into account when creating a cache key to create unique cache items per database system.
  -  As  a result the connection object is passed to several objects such as `SqlExpression<T>` or the table and column name resolvers to provide database-specific behavior.
  • Loading branch information
henkmollema authored Nov 15, 2018
2 parents 1e35ec2 + 4c5769e commit abebc24
Show file tree
Hide file tree
Showing 47 changed files with 1,436 additions and 230 deletions.
8 changes: 8 additions & 0 deletions .appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ environment:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
CI: true
APPVEYOR: true
services:
- mssql2016
- mysql
- postgresql101
branches:
only:
- master
artifacts:
- path: .\src\Dommel\artifacts\**\*.nupkg
name: NuGet
Expand Down
13 changes: 8 additions & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ dist: trusty
env:
global:
- DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
- DOTNET_CLI_TELEMETRY_OPTOUT: 1
os:
- linux
- osx
osx_image: xcode8.2
- DOTNET_CLI_TELEMETRY_OPTOUT: true
- TRAVIS: true
services:
- mysql
- postgresql
branches:
only:
- master
before_script:
- chmod +x ./build.sh
script:
Expand Down
12 changes: 12 additions & 0 deletions Dommel.sln
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dommel.Tests", "test\Dommel
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dommel.IntegrationTests", "test\Dommel.IntegrationTests\Dommel.IntegrationTests.csproj", "{305C1E12-1F6D-4510-BD42-0C3F1BD838A1}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B850D48C-10D3-4EF7-AE61-873068211FD2}"
ProjectSection(SolutionItems) = preProject
.appveyor.yml = .appveyor.yml
.gitignore = .gitignore
.travis.yml = .travis.yml
build.cmd = build.cmd
build.ps1 = build.ps1
build.sh = build.sh
NuGet.Config = NuGet.Config
README.md = README.md
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down
4 changes: 3 additions & 1 deletion build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ function Exec

if(Test-Path .\src\Dommel\artifacts) { Remove-Item .\src\Dommel\artifacts -Force -Recurse }

exec { & dotnet --info }
exec { & dotnet restore }

$branch = @{ $true = $env:APPVEYOR_REPO_BRANCH; $false = $(git symbolic-ref --short -q HEAD) }[$env:APPVEYOR_REPO_BRANCH -ne $NULL];
Expand All @@ -29,6 +28,9 @@ echo "build: Executing tests"
Push-Location -Path .\test\Dommel.Tests
exec { & dotnet test -c Release --no-build }
Pop-Location
Push-Location -Path .\test\Dommel.IntegrationTests
exec { & dotnet test -c Release --no-build }
Pop-Location

if ($env:APPVEYOR_BUILD_NUMBER) {
$versionSuffix = "{0:00000}" -f [convert]::ToInt32("0" + $env:APPVEYOR_BUILD_NUMBER, 10)
Expand Down
1 change: 1 addition & 0 deletions build.sh
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
dotnet build
dotnet test test/Dommel.Tests
dotnet test test/Dommel.IntegrationTests
2 changes: 1 addition & 1 deletion src/Dommel/Dommel.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<AssemblyTitle>Dommel</AssemblyTitle>
<VersionPrefix>2.0.0-beta3</VersionPrefix>
<Authors>Henk Mollema</Authors>
<TargetFrameworks>net451;netstandard1.3;netstandard2.0</TargetFrameworks>
<TargetFrameworks>net452;netstandard1.3;netstandard2.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' != 'Windows_NT' ">netstandard1.3;netstandard2.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
Expand Down
129 changes: 129 additions & 0 deletions src/Dommel/DommelMapper.AutoMultiMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,25 @@ namespace Dommel
{
public static partial class DommelMapper
{
/// <summary>
/// Retrieves the automatically mapped entity of type <typeparamref name="TReturn"/> with the specified <paramref name="id"/>
/// joined with the types specified as type parameters.
/// </summary>
/// <typeparam name="T1">The first type parameter. This is the source entity.</typeparam>
/// <typeparam name="T2">The second type parameter.</typeparam>
/// <typeparam name="TReturn">The return type parameter.</typeparam>
/// <param name="connection">The connection to the database. This can either be open or closed.</param>
/// <param name="id">The id of the entity in the database.</param>
/// <param name="transaction">Optional transaction for the command.</param>
/// <returns>The entity with the corresponding id joined with the specified types.</returns>
public static TReturn Get<T1, T2, TReturn>(this IDbConnection connection, object id, IDbTransaction transaction = null) where T1 : TReturn
=> MultiMap<T1, T2, DontMap, DontMap, DontMap, DontMap, DontMap, TReturn>(
connection,
CreateMapDelegate<T1, T2, DontMap, DontMap, DontMap, DontMap, DontMap, TReturn>(),
id,
transaction)
.FirstOrDefault();

/// <summary>
/// Retrieves the automatically mapped entity of type <typeparamref name="TReturn"/> with the specified <paramref name="id"/>
/// joined with the types specified as type parameters.
Expand All @@ -28,6 +47,26 @@ public static async Task<TReturn> GetAsync<T1, T2, TReturn>(this IDbConnection c
transaction))
.FirstOrDefault();

/// <summary>
/// Retrieves the automatically mapped entity of type <typeparamref name="TReturn"/> with the specified <paramref name="id"/>
/// joined with the types specified as type parameters.
/// </summary>
/// <typeparam name="T1">The first type parameter. This is the source entity.</typeparam>
/// <typeparam name="T2">The second type parameter.</typeparam>
/// <typeparam name="T3">The third type parameter.</typeparam>
/// <typeparam name="TReturn">The return type parameter.</typeparam>
/// <param name="connection">The connection to the database. This can either be open or closed.</param>
/// <param name="id">The id of the entity in the database.</param>
/// <param name="transaction">Optional transaction for the command.</param>
/// <returns>The entity with the corresponding id joined with the specified types.</returns>
public static TReturn Get<T1, T2, T3, TReturn>(this IDbConnection connection, object id, IDbTransaction transaction = null) where T1 : TReturn
=> MultiMap<T1, T2, T3, DontMap, DontMap, DontMap, DontMap, TReturn>(
connection,
CreateMapDelegate<T1, T2, T3, DontMap, DontMap, DontMap, DontMap, TReturn>(),
id,
transaction)
.FirstOrDefault();

/// <summary>
/// Retrieves the automatically mapped entity of type <typeparamref name="TReturn"/> with the specified <paramref name="id"/>
/// joined with the types specified as type parameters.
Expand All @@ -48,6 +87,27 @@ public static async Task<TReturn> GetAsync<T1, T2, T3, TReturn>(this IDbConnecti
transaction))
.FirstOrDefault();

/// <summary>
/// Retrieves the automatically mapped entity of type <typeparamref name="TReturn"/> with the specified <paramref name="id"/>
/// joined with the types specified as type parameters.
/// </summary>
/// <typeparam name="T1">The first type parameter. This is the source entity.</typeparam>
/// <typeparam name="T2">The second type parameter.</typeparam>
/// <typeparam name="T3">The third type parameter.</typeparam>
/// <typeparam name="T4">The fourth type parameter.</typeparam>
/// <typeparam name="TReturn">The return type parameter.</typeparam>
/// <param name="connection">The connection to the database. This can either be open or closed.</param>
/// <param name="id">The id of the entity in the database.</param>
/// <param name="transaction">Optional transaction for the command.</param>
/// <returns>The entity with the corresponding id joined with the specified types.</returns>
public static TReturn Get<T1, T2, T3, T4, TReturn>(this IDbConnection connection, object id, IDbTransaction transaction = null) where T1 : TReturn
=> MultiMap<T1, T2, T3, T4, DontMap, DontMap, DontMap, TReturn>(
connection,
CreateMapDelegate<T1, T2, T3, T4, DontMap, DontMap, DontMap, TReturn>(),
id,
transaction)
.FirstOrDefault();

/// <summary>
/// Retrieves the automatically mapped entity of type <typeparamref name="TReturn"/> with the specified <paramref name="id"/>
/// joined with the types specified as type parameters.
Expand All @@ -69,6 +129,28 @@ public static async Task<TReturn> GetAsync<T1, T2, T3, T4, TReturn>(this IDbConn
transaction))
.FirstOrDefault();

/// <summary>
/// Retrieves the automatically mapped entity of type <typeparamref name="TReturn"/> with the specified <paramref name="id"/>
/// joined with the types specified as type parameters.
/// </summary>
/// <typeparam name="T1">The first type parameter. This is the source entity.</typeparam>
/// <typeparam name="T2">The second type parameter.</typeparam>
/// <typeparam name="T3">The third type parameter.</typeparam>
/// <typeparam name="T4">The fourth type parameter.</typeparam>
/// <typeparam name="T5">The fifth type parameter.</typeparam>
/// <typeparam name="TReturn">The return type parameter.</typeparam>
/// <param name="connection">The connection to the database. This can either be open or closed.</param>
/// <param name="id">The id of the entity in the database.</param>
/// <param name="transaction">Optional transaction for the command.</param>
/// <returns>The entity with the corresponding id joined with the specified types.</returns>
public static TReturn Get<T1, T2, T3, T4, T5, TReturn>(this IDbConnection connection, object id, IDbTransaction transaction = null) where T1 : TReturn
=> MultiMap<T1, T2, T3, T4, T5, DontMap, DontMap, TReturn>(
connection,
CreateMapDelegate<T1, T2, T3, T4, T5, DontMap, DontMap, TReturn>(),
id,
transaction)
.FirstOrDefault();

/// <summary>
/// Retrieves the automatically mapped entity of type <typeparamref name="TReturn"/> with the specified <paramref name="id"/>
/// joined with the types specified as type parameters.
Expand All @@ -91,6 +173,29 @@ public static async Task<TReturn> GetAsync<T1, T2, T3, T4, T5, TReturn>(this IDb
transaction))
.FirstOrDefault();

/// <summary>
/// Retrieves the automatically mapped entity of type <typeparamref name="TReturn"/> with the specified <paramref name="id"/>
/// joined with the types specified as type parameters.
/// </summary>
/// <typeparam name="T1">The first type parameter. This is the source entity.</typeparam>
/// <typeparam name="T2">The second type parameter.</typeparam>
/// <typeparam name="T3">The third type parameter.</typeparam>
/// <typeparam name="T4">The fourth type parameter.</typeparam>
/// <typeparam name="T5">The fifth type parameter.</typeparam>
/// <typeparam name="T6">The sixth type parameter.</typeparam>
/// <typeparam name="TReturn">The return type parameter.</typeparam>
/// <param name="connection">The connection to the database. This can either be open or closed.</param>
/// <param name="id">The id of the entity in the database.</param>
/// <param name="transaction">Optional transaction for the command.</param>
/// <returns>The entity with the corresponding id joined with the specified types.</returns>
public static TReturn Get<T1, T2, T3, T4, T5, T6, TReturn>(this IDbConnection connection, object id, IDbTransaction transaction = null) where T1 : TReturn
=> MultiMap<T1, T2, T3, T4, T5, T6, DontMap, TReturn>(
connection,
CreateMapDelegate<T1, T2, T3, T4, T5, T6, DontMap, TReturn>(),
id,
transaction)
.FirstOrDefault();

/// <summary>
/// Retrieves the automatically mapped entity of type <typeparamref name="TReturn"/> with the specified <paramref name="id"/>
/// joined with the types specified as type parameters.
Expand All @@ -114,6 +219,30 @@ public static async Task<TReturn> GetAsync<T1, T2, T3, T4, T5, T6, TReturn>(this
transaction))
.FirstOrDefault();

/// <summary>
/// Retrieves the automatically mapped entity of type <typeparamref name="TReturn"/> with the specified <paramref name="id"/>
/// joined with the types specified as type parameters.
/// </summary>
/// <typeparam name="T1">The first type parameter. This is the source entity.</typeparam>
/// <typeparam name="T2">The second type parameter.</typeparam>
/// <typeparam name="T3">The third type parameter.</typeparam>
/// <typeparam name="T4">The fourth type parameter.</typeparam>
/// <typeparam name="T5">The fifth type parameter.</typeparam>
/// <typeparam name="T6">The sixth type parameter.</typeparam>
/// <typeparam name="T7">The seventh type parameter.</typeparam>
/// <typeparam name="TReturn">The return type parameter.</typeparam>
/// <param name="connection">The connection to the database. This can either be open or closed.</param>
/// <param name="id">The id of the entity in the database.</param>
/// <param name="transaction">Optional transaction for the command.</param>
/// <returns>The entity with the corresponding id joined with the specified types.</returns>
public static TReturn Get<T1, T2, T3, T4, T5, T6, T7, TReturn>(this IDbConnection connection, object id, IDbTransaction transaction = null) where T1 : TReturn
=> MultiMap<T1, T2, T3, T4, T5, T6, T7, TReturn>(
connection,
CreateMapDelegate<T1, T2, T3, T4, T5, T6, T7, TReturn>(),
id,
transaction)
.FirstOrDefault();

/// <summary>
/// Retrieves the automatically mapped entity of type <typeparamref name="TReturn"/> with the specified <paramref name="id"/>
/// joined with the types specified as type parameters.
Expand Down
54 changes: 54 additions & 0 deletions src/Dommel/DommelMapper.Cache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;
using System.Collections.Concurrent;
using System.Data;
using System.Reflection;

namespace Dommel
{
public static partial class DommelMapper
{
internal enum QueryCacheType
{
Get,
GetByMultipleIds,
GetAll,
Count,
Insert,
Update,
Delete,
DeleteAll,
}

#pragma warning disable IDE1006 // Naming Styles
internal static ConcurrentDictionary<QueryCacheKey, string> QueryCache = new ConcurrentDictionary<QueryCacheKey, string>();
internal static ConcurrentDictionary<QueryCacheKey, string> ResolverCache = new ConcurrentDictionary<QueryCacheKey, string>();
#pragma warning restore IDE1006 // Naming Styles

internal struct QueryCacheKey : IEquatable<QueryCacheKey>
{
public QueryCacheKey(QueryCacheType cacheType, IDbConnection connection, MemberInfo memberInfo)
{
ConnectionType = connection.GetType();
CacheType = cacheType;
MemberInfo = memberInfo;
}

#if NETSTANDARD1_3
public QueryCacheKey(QueryCacheType cacheType, IDbConnection connection, Type type)
{
ConnectionType = connection.GetType();
CacheType = cacheType;
MemberInfo = type.GetTypeInfo();
}
#endif

public QueryCacheType CacheType { get; }

public Type ConnectionType { get; }

public MemberInfo MemberInfo { get; }

public bool Equals(QueryCacheKey other) => CacheType == other.CacheType && ConnectionType == other.ConnectionType && MemberInfo == other.MemberInfo;
}
}
}
18 changes: 8 additions & 10 deletions src/Dommel/DommelMapper.Count.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Collections.Concurrent;
using System.Data;
using System.Linq.Expressions;
using System.Threading.Tasks;
Expand All @@ -9,8 +8,6 @@ namespace Dommel
{
public static partial class DommelMapper
{
private static readonly ConcurrentDictionary<Type, string> _countQueryCache = new ConcurrentDictionary<Type, string>();

/// <summary>
/// Returns the number of entities matching the specified predicate.
/// </summary>
Expand All @@ -21,7 +18,7 @@ public static partial class DommelMapper
/// <returns>The number of entities matching the specified predicate.</returns>
public static long Count<TEntity>(this IDbConnection connection, Expression<Func<TEntity, bool>> predicate, IDbTransaction transaction = null)
{
var sql = BuildCountSql(predicate, out var parameters);
var sql = BuildCountSql(connection, predicate, out var parameters);
LogQuery<TEntity>(sql);
return connection.ExecuteScalar<long>(sql, parameters, transaction);
}
Expand All @@ -36,22 +33,23 @@ public static long Count<TEntity>(this IDbConnection connection, Expression<Func
/// <returns>The number of entities matching the specified predicate.</returns>
public static Task<long> CountAsync<TEntity>(this IDbConnection connection, Expression<Func<TEntity, bool>> predicate, IDbTransaction transaction = null)
{
var sql = BuildCountSql(predicate, out var parameters);
var sql = BuildCountSql(connection, predicate, out var parameters);
LogQuery<TEntity>(sql);
return connection.ExecuteScalarAsync<long>(sql, parameters, transaction);
}

private static string BuildCountSql<TEntity>(Expression<Func<TEntity, bool>> predicate, out DynamicParameters parameters)
private static string BuildCountSql<TEntity>(IDbConnection connection, Expression<Func<TEntity, bool>> predicate, out DynamicParameters parameters)
{
var type = typeof(TEntity);
if (!_countQueryCache.TryGetValue(type, out var sql))
var cacheKey = new QueryCacheKey(QueryCacheType.Count, connection, type);
if (!QueryCache.TryGetValue(cacheKey, out var sql))
{
var tableName = Resolvers.Table(type);
var tableName = Resolvers.Table(type, connection);
sql = $"select count(*) from {tableName}";
_countQueryCache.TryAdd(type, sql);
QueryCache.TryAdd(cacheKey, sql);
}

sql += new SqlExpression<TEntity>()
sql += new SqlExpression<TEntity>(GetSqlBuilder(connection))
.Where(predicate)
.ToSql(out parameters);
return sql;
Expand Down
1 change: 1 addition & 0 deletions src/Dommel/DommelMapper.DapperTypeMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public static partial class DommelMapper

static DommelMapper()
{
// Type mapper for [Column] attribute
SqlMapper.TypeMapProvider = type => CreateMap(type);

SqlMapper.ITypeMap CreateMap(Type t) => new CustomPropertyTypeMap(t,
Expand Down
Loading

0 comments on commit abebc24

Please sign in to comment.