From 42fb1254671a845cfba2db97ddd9b3c143f95a50 Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Sat, 26 Sep 2020 14:25:08 +1000 Subject: [PATCH 01/42] Initial v5 supporting only netstandard 2.0 with proper async --- NPoco.sln | 14 +- build.ps1 | 11 +- src/NPoco.JsonNet/NPoco.JsonNet.csproj | 36 +-- .../SqlServer2008DatabaseType.cs | 0 .../SqlServer2012DatabaseType.cs | 2 +- .../DatabaseTypes/SqlServerCEDatabaseType.cs | 6 +- .../DatabaseTypes/SqlServerDatabaseType.cs | 30 +-- src/NPoco.SqlServer/NPoco.SqlServer.csproj | 24 ++ .../SqlBulkCopyHelper.cs | 10 +- src/NPoco.SqlServer/SqlServerDatabase.cs | 20 ++ src/NPoco/AsyncDatabase.cs | 55 ++-- src/NPoco/AsyncEnumeratorAdapter.cs | 212 ++++++++++++++++ src/NPoco/Database.cs | 239 +++++++++--------- src/NPoco/DatabaseType.cs | 34 ++- .../DatabaseTypes/FirebirdDatabaseType.cs | 5 +- src/NPoco/DatabaseTypes/OracleDatabaseType.cs | 8 +- .../DatabaseTypes/PostgreSQLDatabaseType.cs | 5 +- src/NPoco/DatabaseTypes/SQLiteDatabaseType.cs | 5 +- src/NPoco/FluentMappings/ConventionScanner.cs | 2 - .../FluentMappingConfiguration.cs | 4 - .../FluentMappings/IConventionScanner.cs | 2 - src/NPoco/IAsyncDatabase.cs | 8 +- src/NPoco/IDatabase.cs | 3 +- src/NPoco/IDatabaseHelpers.cs | 4 - src/NPoco/IDatabaseQuery.cs | 4 +- src/NPoco/Linq/DeleteQueryProvider.cs | 12 +- src/NPoco/Linq/SimpleQueryProvider.cs | 41 +-- src/NPoco/Linq/UpdateQueryProvider.cs | 10 - src/NPoco/MapperCollection.cs | 2 - src/NPoco/NPoco.csproj | 79 +----- src/NPoco/PagingHelper.cs | 13 + src/NPoco/PocoDataFactory.cs | 4 - src/NPoco/PocoExpando.cs | 10 +- src/NPoco/Properties/AssemblyInfo.cs | 3 +- src/NPoco/ReflectionUtils.cs | 15 -- src/NPoco/RowMappers/DictionaryMapper.cs | 2 - src/NPoco/RowMappers/DynamicMember.cs | 55 ---- src/NPoco/Singleton.cs | 23 ++ src/NPoco/Sql.cs | 19 -- src/NPoco/Tuple.cs | 36 --- src/NPoco/TypeHelpers.cs | 7 - .../NPoco.Tests/Common/BaseDBDecoratedTest.cs | 7 +- test/NPoco.Tests/Common/BaseDBFluentTest.cs | 9 +- test/NPoco.Tests/Common/FirebirdDatabase.cs | 4 +- test/NPoco.Tests/Common/SQLLocalDatabase.cs | 8 +- test/NPoco.Tests/Common/TestDescriptor.cs | 4 - test/NPoco.Tests/ConstructorTests.cs | 7 +- test/NPoco.Tests/DatabaseFactoryTests.cs | 2 +- .../DecoratedTests/CRUDTests/UpdateTests.cs | 4 +- .../QueryTests/FetchAndQueryDecoratedTests.cs | 2 +- .../TransactionDecoratedTests.cs | 2 +- .../QueryTests/QueryProviderTests.cs | 9 +- test/NPoco.Tests/FormatCommandTest.cs | 6 +- test/NPoco.Tests/NPoco.Tests.csproj | 99 +++----- test/NPoco.Tests/NewMapper/FakeReader.cs | 2 - test/NPoco.Tests/SnapshotterTests.cs | 5 +- 56 files changed, 591 insertions(+), 653 deletions(-) rename src/{NPoco => NPoco.SqlServer}/DatabaseTypes/SqlServer2008DatabaseType.cs (100%) rename src/{NPoco => NPoco.SqlServer}/DatabaseTypes/SqlServer2012DatabaseType.cs (94%) rename src/{NPoco => NPoco.SqlServer}/DatabaseTypes/SqlServerCEDatabaseType.cs (83%) rename src/{NPoco => NPoco.SqlServer}/DatabaseTypes/SqlServerDatabaseType.cs (66%) create mode 100644 src/NPoco.SqlServer/NPoco.SqlServer.csproj rename src/{NPoco => NPoco.SqlServer}/SqlBulkCopyHelper.cs (95%) create mode 100644 src/NPoco.SqlServer/SqlServerDatabase.cs create mode 100644 src/NPoco/AsyncEnumeratorAdapter.cs delete mode 100644 src/NPoco/RowMappers/DynamicMember.cs delete mode 100644 src/NPoco/Tuple.cs diff --git a/NPoco.sln b/NPoco.sln index 13e39bd5..1a462279 100644 --- a/NPoco.sln +++ b/NPoco.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26228.4 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30509.190 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C7A94BA4-5689-4651-872D-7D7711D2DEDF}" EndProject @@ -18,6 +18,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NPoco.Tests", "test\NPoco.T EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NPoco.JsonNet", "src\NPoco.JsonNet\NPoco.JsonNet.csproj", "{8DCCBC0A-36D4-4F3C-9B16-39B23AAFD726}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPoco.SqlServer", "src\NPoco.SqlServer\NPoco.SqlServer.csproj", "{15554441-8CF4-4B7B-A6D3-914463BFFF42}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -36,6 +38,10 @@ Global {8DCCBC0A-36D4-4F3C-9B16-39B23AAFD726}.Debug|Any CPU.Build.0 = Debug|Any CPU {8DCCBC0A-36D4-4F3C-9B16-39B23AAFD726}.Release|Any CPU.ActiveCfg = Release|Any CPU {8DCCBC0A-36D4-4F3C-9B16-39B23AAFD726}.Release|Any CPU.Build.0 = Release|Any CPU + {15554441-8CF4-4B7B-A6D3-914463BFFF42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {15554441-8CF4-4B7B-A6D3-914463BFFF42}.Debug|Any CPU.Build.0 = Debug|Any CPU + {15554441-8CF4-4B7B-A6D3-914463BFFF42}.Release|Any CPU.ActiveCfg = Release|Any CPU + {15554441-8CF4-4B7B-A6D3-914463BFFF42}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -44,5 +50,9 @@ Global {56481BBA-35A1-4061-9FE8-0ED9E1B6A0A3} = {C7A94BA4-5689-4651-872D-7D7711D2DEDF} {B39C2641-0655-47CC-A1A3-5E1F84714FB5} = {8EDBB46A-95F8-442E-A432-19A50ED0683B} {8DCCBC0A-36D4-4F3C-9B16-39B23AAFD726} = {C7A94BA4-5689-4651-872D-7D7711D2DEDF} + {15554441-8CF4-4B7B-A6D3-914463BFFF42} = {C7A94BA4-5689-4651-872D-7D7711D2DEDF} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {75EBE34D-2D61-4748-A383-DB920088B393} EndGlobalSection EndGlobal diff --git a/build.ps1 b/build.ps1 index b04fe9dd..9db47e6e 100644 --- a/build.ps1 +++ b/build.ps1 @@ -9,6 +9,7 @@ Properties { $build_artifacts_dir = "$build_dir\build" $solution_dir = "$build_dir\src\NPoco" $jsonnet = "$build_dir\src\NPoco.JsonNet" + $sqlserver = "$build_dir\src\NPoco.SqlServer" } FormatTaskName (("-"*25) + "[{0}]" + ("-"*25)) @@ -19,10 +20,14 @@ Task Build -depends Clean { Write-Host "Creating BuildArtifacts" -ForegroundColor Green Exec { dotnet restore } Set-Location "$solution_dir" - #$env:DNX_BUILD_VERSION="alpha02" - Exec { dotnet pack --configuration release --output $build_artifacts_dir } + if ($env:BUILD_SUFFIX -ne "") { + $suffix = "/p:VersionSuffix=""$env:BUILD_SUFFIX""" + } + Exec { dotnet pack --configuration release --output $build_artifacts_dir $suffix } Set-Location "$jsonnet" - Exec { dotnet pack --configuration release --output $build_artifacts_dir } + Exec { dotnet pack --configuration release --output $build_artifacts_dir $suffix } + Set-Location "$sqlserver" + Exec { dotnet pack --configuration release --output $build_artifacts_dir $suffix } } Task Clean { diff --git a/src/NPoco.JsonNet/NPoco.JsonNet.csproj b/src/NPoco.JsonNet/NPoco.JsonNet.csproj index 955ca7ef..b8b4edf7 100644 --- a/src/NPoco.JsonNet/NPoco.JsonNet.csproj +++ b/src/NPoco.JsonNet/NPoco.JsonNet.csproj @@ -2,20 +2,19 @@ Provides an implementation to use Json.NET as the serializer for serialized columns - 4.0.0 - net35;net45;net40;netstandard1.3;netstandard2.0 + 5.0.0 + netstandard2.0 NPoco.JsonNet NPoco.JsonNet orm;sql;micro-orm;database;mvc https://github.com/schotime/NPoco - http://www.apache.org/licenses/LICENSE-2.0 - $(PackageTargetFallback);dnxcore50 false false false false false false + 8.0 @@ -26,33 +25,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v3.5\Profile\Client - - diff --git a/src/NPoco/DatabaseTypes/SqlServer2008DatabaseType.cs b/src/NPoco.SqlServer/DatabaseTypes/SqlServer2008DatabaseType.cs similarity index 100% rename from src/NPoco/DatabaseTypes/SqlServer2008DatabaseType.cs rename to src/NPoco.SqlServer/DatabaseTypes/SqlServer2008DatabaseType.cs diff --git a/src/NPoco/DatabaseTypes/SqlServer2012DatabaseType.cs b/src/NPoco.SqlServer/DatabaseTypes/SqlServer2012DatabaseType.cs similarity index 94% rename from src/NPoco/DatabaseTypes/SqlServer2012DatabaseType.cs rename to src/NPoco.SqlServer/DatabaseTypes/SqlServer2012DatabaseType.cs index 47c441c4..7a866be7 100644 --- a/src/NPoco/DatabaseTypes/SqlServer2012DatabaseType.cs +++ b/src/NPoco.SqlServer/DatabaseTypes/SqlServer2012DatabaseType.cs @@ -1,5 +1,5 @@ using System; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using System.Linq; namespace NPoco.DatabaseTypes diff --git a/src/NPoco/DatabaseTypes/SqlServerCEDatabaseType.cs b/src/NPoco.SqlServer/DatabaseTypes/SqlServerCEDatabaseType.cs similarity index 83% rename from src/NPoco/DatabaseTypes/SqlServerCEDatabaseType.cs rename to src/NPoco.SqlServer/DatabaseTypes/SqlServerCEDatabaseType.cs index 822824a4..62b5416d 100644 --- a/src/NPoco/DatabaseTypes/SqlServerCEDatabaseType.cs +++ b/src/NPoco.SqlServer/DatabaseTypes/SqlServerCEDatabaseType.cs @@ -1,6 +1,7 @@ using System.Data; using System.Data.Common; using System.Linq; +using System.Threading.Tasks; namespace NPoco.DatabaseTypes { @@ -19,14 +20,11 @@ public override object ExecuteInsert(Database db, DbCommand cmd, string prima return db.ExecuteScalar("SELECT @@@IDENTITY AS NewID;"); } - -#if !NET35 && !NET40 - public override async System.Threading.Tasks.Task ExecuteInsertAsync(Database db, DbCommand cmd, string primaryKeyName, bool useOutputClause, T poco, object[] args) + public override async Task ExecuteInsertAsync(Database db, DbCommand cmd, string primaryKeyName, bool useOutputClause, T poco, object[] args) { await db.ExecuteNonQueryHelperAsync(cmd); return await db.ExecuteScalarAsync("SELECT @@@IDENTITY AS NewID;"); } -#endif public override IsolationLevel GetDefaultTransactionIsolationLevel() { diff --git a/src/NPoco/DatabaseTypes/SqlServerDatabaseType.cs b/src/NPoco.SqlServer/DatabaseTypes/SqlServerDatabaseType.cs similarity index 66% rename from src/NPoco/DatabaseTypes/SqlServerDatabaseType.cs rename to src/NPoco.SqlServer/DatabaseTypes/SqlServerDatabaseType.cs index e8191822..401b6559 100644 --- a/src/NPoco/DatabaseTypes/SqlServerDatabaseType.cs +++ b/src/NPoco.SqlServer/DatabaseTypes/SqlServerDatabaseType.cs @@ -1,10 +1,8 @@ +using Microsoft.Data.SqlClient; using System; -using System.Collections.Generic; using System.Data; using System.Data.Common; -using System.Data.SqlClient; -using System.Linq; -using System.Text.RegularExpressions; +using System.Threading.Tasks; namespace NPoco.DatabaseTypes { @@ -12,8 +10,6 @@ public class SqlServerDatabaseType : DatabaseType { public bool UseOutputClause { get; set; } - private static readonly Regex OrderByAlias = new Regex(@"[\""\[\]\w]+\.([\[\]\""\w]+)", RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.IgnoreCase); - public override bool UseColumnAliases() { return true; @@ -21,14 +17,9 @@ public override bool UseColumnAliases() public override string BuildPageQuery(long skip, long take, PagingHelper.SQLParts parts, ref object[] args) { - parts.sqlOrderBy = string.IsNullOrEmpty(parts.sqlOrderBy) ? null : OrderByAlias.Replace(parts.sqlOrderBy, "$1"); - var sqlPage = string.Format("SELECT {4} FROM (SELECT poco_base.*, ROW_NUMBER() OVER ({0}) poco_rn \nFROM ( \n{1}) poco_base ) poco_paged \nWHERE poco_rn > @{2} AND poco_rn <= @{3} \nORDER BY poco_rn", - parts.sqlOrderBy ?? "ORDER BY (SELECT NULL /*poco_dual*/)", parts.sqlUnordered, args.Length, args.Length + 1, parts.sqlColumns); - args = args.Concat(new object[] { skip, skip + take }).ToArray(); - - return sqlPage; + return PagingHelper.BuildPaging(skip, take, parts, ref args); } - + private void AdjustSqlInsertCommandText(DbCommand cmd, bool useOutputClause) { if (!UseOutputClause && !useOutputClause) @@ -57,26 +48,17 @@ public override object ExecuteInsert(Database db, DbCommand cmd, string prima return db.ExecuteScalarHelper(cmd); } -#if !NET35 && !NET40 - public override System.Threading.Tasks.Task ExecuteInsertAsync(Database db, DbCommand cmd, string primaryKeyName, bool useOutputClause, T poco, object[] args) + public override Task ExecuteInsertAsync(Database db, DbCommand cmd, string primaryKeyName, bool useOutputClause, T poco, object[] args) { AdjustSqlInsertCommandText(cmd, useOutputClause); return db.ExecuteScalarHelperAsync(cmd); } -#endif public override string GetExistsSql() { return "IF EXISTS (SELECT 1 FROM {0} WHERE {1}) SELECT 1 ELSE SELECT 0"; } -#if !DNXCORE50 - public override void InsertBulk(IDatabase db, IEnumerable pocos) - { - SqlBulkCopyHelper.BulkInsert(db, pocos); - } -#endif - public override IsolationLevel GetDefaultTransactionIsolationLevel() { return IsolationLevel.ReadCommitted; @@ -92,7 +74,7 @@ public override IsolationLevel GetDefaultTransactionIsolationLevel() public override string GetProviderName() { - return "System.Data.SqlClient"; + return "Microsoft.Data.SqlClient"; } public override object ProcessDefaultMappings(PocoColumn pocoColumn, object value) diff --git a/src/NPoco.SqlServer/NPoco.SqlServer.csproj b/src/NPoco.SqlServer/NPoco.SqlServer.csproj new file mode 100644 index 00000000..768024e5 --- /dev/null +++ b/src/NPoco.SqlServer/NPoco.SqlServer.csproj @@ -0,0 +1,24 @@ + + + + An extremely easy to use Micro-ORM supporting Sql Server. + 5.0.0 + netstandard2.0 + NPoco.SqlServer + NPoco.SqlServer + Adam Schröder + orm;sql;micro-orm;database;mvc + https://github.com/schotime/NPoco + netstandard2.0 + 8.0 + + + + + + + + + + + diff --git a/src/NPoco/SqlBulkCopyHelper.cs b/src/NPoco.SqlServer/SqlBulkCopyHelper.cs similarity index 95% rename from src/NPoco/SqlBulkCopyHelper.cs rename to src/NPoco.SqlServer/SqlBulkCopyHelper.cs index 0a0480ca..657a8c43 100644 --- a/src/NPoco/SqlBulkCopyHelper.cs +++ b/src/NPoco.SqlServer/SqlBulkCopyHelper.cs @@ -2,12 +2,11 @@ using System.Collections.Generic; using System.Data; using System.Data.Common; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using System.Linq; namespace NPoco { -#if !DNXCORE50 public class SqlBulkCopyHelper { public static Func SqlConnectionResolver = dbConn => (SqlConnection)dbConn; @@ -26,7 +25,7 @@ public static void BulkInsert(IDatabase db, IEnumerable list, SqlBulkCopyO bulkCopy.WriteToServer(table); } } -#if NET45 || NETSTANDARD20 + public static async System.Threading.Tasks.Task BulkInsertAsync(IDatabase db, IEnumerable list, SqlBulkCopyOptions sqlBulkCopyOptions) { using (var bulkCopy = new SqlBulkCopy(SqlConnectionResolver(db.Connection), sqlBulkCopyOptions, SqlTransactionResolver(db.Transaction))) @@ -35,7 +34,7 @@ public static async System.Threading.Tasks.Task BulkInsertAsync(IDatabase db, await bulkCopy.WriteToServerAsync(table).ConfigureAwait(false); } } -#endif + private static DataTable BuildBulkInsertDataTable(IDatabase db, IEnumerable list, SqlBulkCopy bulkCopy, SqlBulkCopyOptions sqlBulkCopyOptions) { @@ -97,5 +96,4 @@ private static DataTable BuildBulkInsertDataTable(IDatabase db, IEnumerable UpdateAsync(object poco, object primaryKeyValue, IEnumerable UpdateAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns) { return UpdateImp (tableName, primaryKeyName, poco, primaryKeyValue, columns, - async (sql, args, next) => next(await ExecuteAsync(sql, args).ConfigureAwait(false)), TaskAsyncHelper.FromResult(0)); + async (sql, args, next) => next(await ExecuteAsync(sql, args)), TaskAsyncHelper.FromResult(0)); } public async Task UpdateBatchAsync(IEnumerable> pocos, BatchOptions options = null) @@ -256,7 +255,7 @@ public Task> PageAsync(long page, long itemsPerPage, string sql, para return PageImp>>(page, itemsPerPage, sql, args, async (paged, thesql) => { - paged.Items = (await QueryAsync(thesql).ConfigureAwait(false)).ToList(); + paged.Items = await (await QueryAsync(thesql)).ToListAsync(); return paged; }); } @@ -290,12 +289,12 @@ public Task> FetchAsync() public async Task> FetchAsync(string sql, params object[] args) { - return (await QueryAsync(sql, args).ConfigureAwait(false)).ToList(); + return await (await QueryAsync(sql, args)).ToListAsync(); } public async Task> FetchAsync(Sql sql) { - return (await QueryAsync(sql).ConfigureAwait(false)).ToList(); + return await (await QueryAsync(sql)).ToListAsync(); } public IAsyncQueryProviderWithIncludes QueryAsync() @@ -303,17 +302,17 @@ public IAsyncQueryProviderWithIncludes QueryAsync() return new AsyncQueryProvider(this); } - public Task> QueryAsync(string sql, params object[] args) + public Task> QueryAsync(string sql, params object[] args) { return QueryAsync(new Sql(sql, args)); } - public Task> QueryAsync(Sql sql) + public Task> QueryAsync(Sql sql) { return QueryAsync(default(T), null, null, sql); } - internal async Task> QueryAsync(T instance, Expression> listExpression, Func idFunc, Sql Sql) + internal async Task> QueryAsync(T instance, Expression> listExpression, Func idFunc, Sql Sql) { var sql = Sql.SQL; var args = Sql.Arguments; @@ -327,7 +326,7 @@ internal async Task> QueryAsync(T instance, Expression> QueryAsync(T instance, Expression(typeof(T), instance, r, cmd); + return (listExpression != null ? ReadOneToManyAsync(instance, r, cmd, listExpression, idFunc) : ReadAsync(typeof(T), instance, r, cmd)); } catch { @@ -347,54 +346,54 @@ internal async Task> QueryAsync(T instance, Expression SingleAsync(string sql, params object[] args) { - return (await QueryAsync(sql, args).ConfigureAwait(false)).Single(); + return await (await QueryAsync(sql, args)).SingleAsync(); } public async Task SingleAsync(Sql sql) { - return (await QueryAsync(sql).ConfigureAwait(false)).Single(); + return await (await QueryAsync(sql)).SingleAsync(); } public async Task SingleOrDefaultAsync(string sql, params object[] args) { - return (await QueryAsync(sql, args).ConfigureAwait(false)).SingleOrDefault(); + return await (await QueryAsync(sql, args)).SingleOrDefaultAsync(); } public async Task SingleOrDefaultAsync(Sql sql) { - return (await QueryAsync(sql).ConfigureAwait(false)).SingleOrDefault(); + return await (await QueryAsync(sql)).SingleOrDefaultAsync(); } public async Task SingleByIdAsync(object primaryKey) { var sql = GenerateSingleByIdSql(primaryKey); - return (await QueryAsync(sql).ConfigureAwait(false)).Single(); + return await (await QueryAsync(sql)).SingleAsync(); } public async Task SingleOrDefaultByIdAsync(object primaryKey) { var sql = GenerateSingleByIdSql(primaryKey); - return (await QueryAsync(sql).ConfigureAwait(false)).SingleOrDefault(); + return await (await QueryAsync(sql)).SingleOrDefaultAsync(); } public async Task FirstAsync(string sql, params object[] args) { - return (await QueryAsync(sql, args).ConfigureAwait(false)).First(); + return await (await QueryAsync(sql, args)).FirstAsync(); } public async Task FirstAsync(Sql sql) { - return (await QueryAsync(sql).ConfigureAwait(false)).First(); + return await (await QueryAsync(sql)).FirstAsync(); } public async Task FirstOrDefaultAsync(string sql, params object[] args) { - return (await QueryAsync(sql, args).ConfigureAwait(false)).FirstOrDefault(); + return await (await QueryAsync(sql, args)).FirstOrDefaultAsync(); } public async Task FirstOrDefaultAsync(Sql sql) { - return (await QueryAsync(sql).ConfigureAwait(false)).FirstOrDefault(); + return await (await QueryAsync(sql)).FirstOrDefaultAsync(); } public Task ExecuteAsync(string sql, params object[] args) @@ -412,7 +411,7 @@ public async Task ExecuteAsync(Sql Sql) OpenSharedConnectionInternal(); using (var cmd = CreateCommand(_sharedConnection, sql, args)) { - var result = await ExecuteNonQueryHelperAsync(cmd).ConfigureAwait(false); + var result = await ExecuteNonQueryHelperAsync(cmd); return result; } } @@ -442,10 +441,10 @@ public async Task ExecuteScalarAsync(Sql Sql) OpenSharedConnectionInternal(); using (var cmd = CreateCommand(_sharedConnection, sql, args)) { - object val = await ExecuteScalarHelperAsync(cmd).ConfigureAwait(false); + object val = await ExecuteScalarHelperAsync(cmd); if (val == null || val == DBNull.Value) - return await TaskAsyncHelper.FromResult(default(T)).ConfigureAwait(false); + return await TaskAsyncHelper.FromResult(default(T)); Type t = typeof(T); Type u = Nullable.GetUnderlyingType(t); @@ -467,7 +466,7 @@ public async Task ExecuteScalarAsync(Sql Sql) internal async Task ExecuteNonQueryHelperAsync(DbCommand cmd) { DoPreExecute(cmd); - var result = await _dbType.ExecuteNonQueryAsync(this, cmd).ConfigureAwait(false); + var result = await _dbType.ExecuteNonQueryAsync(this, cmd); OnExecutedCommandInternal(cmd); return result; } @@ -475,7 +474,7 @@ internal async Task ExecuteNonQueryHelperAsync(DbCommand cmd) internal async Task ExecuteScalarHelperAsync(DbCommand cmd) { DoPreExecute(cmd); - var result = await _dbType.ExecuteScalarAsync(this, cmd).ConfigureAwait(false); + var result = await _dbType.ExecuteScalarAsync(this, cmd); OnExecutedCommandInternal(cmd); return result; } @@ -483,7 +482,7 @@ internal async Task ExecuteScalarHelperAsync(DbCommand cmd) internal async Task ExecuteReaderHelperAsync(DbCommand cmd) { DoPreExecute(cmd); - var reader = await _dbType.ExecuteReaderAsync(this, cmd).ConfigureAwait(false); + var reader = await _dbType.ExecuteReaderAsync(this, cmd); OnExecutedCommandInternal(cmd); return reader; } @@ -499,5 +498,3 @@ public static Task FromResult(T value) } } } - -#endif diff --git a/src/NPoco/AsyncEnumeratorAdapter.cs b/src/NPoco/AsyncEnumeratorAdapter.cs new file mode 100644 index 00000000..5c43ebc4 --- /dev/null +++ b/src/NPoco/AsyncEnumeratorAdapter.cs @@ -0,0 +1,212 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using System.Threading.Tasks.Sources; + +namespace System.Collections.Generic +{ + /// + /// Provides extension methods that enable use of await foreach + /// with . + /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + public static class AsyncEnumeratorAdapter + { + /// + public readonly struct Dispose + { + private readonly IAsyncEnumerator m_enumerator; + + public Dispose(IAsyncEnumerator enumerator) + { + m_enumerator = enumerator; + } + + public IAsyncEnumerator GetAsyncEnumerator() => m_enumerator; + } + + /// + public readonly struct KeepAlive + { + private readonly IAsyncEnumerator m_enumerator; + + public KeepAlive(IAsyncEnumerator enumerator) + { + m_enumerator = enumerator; + } + + public KeepAlive GetAsyncEnumerator() => this; + + public T Current => m_enumerator.Current; + public ValueTask MoveNextAsync() => m_enumerator.MoveNextAsync(); + } + + /// + public readonly struct ConfiguredDispose + { + private readonly IAsyncEnumerator m_enumerator; + private readonly bool m_continueOnCapturedContext; + + public ConfiguredDispose(IAsyncEnumerator enumerator, bool continueOnCapturedContext) + { + m_enumerator = enumerator; + m_continueOnCapturedContext = continueOnCapturedContext; + } + + public ConfiguredDispose GetAsyncEnumerator() => this; + + public T Current => m_enumerator.Current; + public ConfiguredValueTaskAwaitable MoveNextAsync() => m_enumerator.MoveNextAsync().ConfigureAwait(m_continueOnCapturedContext); + public ConfiguredValueTaskAwaitable DisposeAsync() => m_enumerator.DisposeAsync().ConfigureAwait(m_continueOnCapturedContext); + } + + /// + public readonly struct ConfiguredKeepAlive + { + private readonly IAsyncEnumerator m_enumerator; + private readonly bool m_continueOnCapturedContext; + + public ConfiguredKeepAlive(IAsyncEnumerator enumerator, bool continueOnCapturedContext) + { + m_enumerator = enumerator; + m_continueOnCapturedContext = continueOnCapturedContext; + } + + public ConfiguredKeepAlive GetAsyncEnumerator() => this; + + public T Current => m_enumerator.Current; + public ConfiguredValueTaskAwaitable MoveNextAsync() => m_enumerator.MoveNextAsync().ConfigureAwait(m_continueOnCapturedContext); + } + + /// + /// Enables use of await foreach with . + /// The enumerator is disposed after enumeration completes. + /// + public static Dispose EnumerateAndDispose(this IAsyncEnumerator enumerator) => + new Dispose(enumerator); + + /// + /// Enables use of await foreach with . + /// The enumerator is NOT disposed after enumeration completes. + /// Callers are responsible for managing the enumerator's lifetime. + /// + public static KeepAlive EnumerateAndKeepAlive(this IAsyncEnumerator enumerator) => + new KeepAlive(enumerator); + + /// + /// Enables use of await foreach with . + /// The enumerator is disposed after enumeration completes. + /// + public static ConfiguredDispose ConfigureEnumerateAndDispose(this IAsyncEnumerator enumerator, bool continueOnCapturedContext) => + new ConfiguredDispose(enumerator, continueOnCapturedContext); + + /// + /// Enables use of await foreach with . + /// The enumerator is NOT disposed after enumeration completes. + /// Callers are responsible for managing the enumerator's lifetime. + /// + public static ConfiguredKeepAlive ConfigureEnumerateAndKeepAlive(this IAsyncEnumerator enumerator, bool continueOnCapturedContext) => + new ConfiguredKeepAlive(enumerator, continueOnCapturedContext); + } + + /// + /// Provides extension methods for IAsyncEnumXxx. + /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + public static class AsyncEnumeratorExtensions + { + sealed class WrappingAsyncEnumerable : IAsyncEnumerable + { + private readonly IEnumerable m_enumerable; + + public WrappingAsyncEnumerable(IEnumerable enumerable) + { + m_enumerable = enumerable; + } + + public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + return new WrappingAsyncEnumerator(m_enumerable.GetEnumerator(), cancellationToken); + } + } + + sealed class WrappingAsyncEnumerator : IAsyncEnumerator, IValueTaskSource + { + private readonly IEnumerator m_enumerator; + private readonly CancellationToken m_cancellationToken; + + public WrappingAsyncEnumerator(IEnumerator enumerator, CancellationToken cancellationToken = default) + { + m_enumerator = enumerator; + m_cancellationToken = cancellationToken; + } + + public T Current => m_enumerator.Current; + + public ValueTask DisposeAsync() + { + m_enumerator.Dispose(); + return new ValueTask(); + } + + public ValueTask MoveNextAsync() + { + return m_cancellationToken.IsCancellationRequested ? + new ValueTask(this, 0) : + new ValueTask(m_enumerator.MoveNext()); + } + + public bool GetResult(short token) + { + m_cancellationToken.ThrowIfCancellationRequested(); + throw new InvalidOperationException(); + } + + public ValueTaskSourceStatus GetStatus(short token) + { + return ValueTaskSourceStatus.Canceled; + } + + public void OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) + { + throw new InvalidOperationException(); + } + } + + /// + /// Wraps a synchronous into an . + /// + public static IAsyncEnumerable ToAsyncEnumerable(this IEnumerable enumerable) => new WrappingAsyncEnumerable(enumerable); + + /// + /// Wraps a synchronous into an . + /// + public static IAsyncEnumerator ToAsyncEnumerator(this IEnumerable enumerable, CancellationToken cancellationToken = default) => + new WrappingAsyncEnumerator(enumerable.GetEnumerator(), cancellationToken); + + /// + /// Wraps a synchronous into an . + /// + public static IAsyncEnumerator ToAsyncEnumerator(this IEnumerator enumerator, CancellationToken cancellationToken = default) => + new WrappingAsyncEnumerator(enumerator, cancellationToken); + } + + /// + /// Provides an empty . + /// + public static class AsyncEnumerator + { + sealed class EmptyAsyncEnumerator : IAsyncEnumerator + { + public T Current => throw new InvalidOperationException(); + public ValueTask MoveNextAsync() => new ValueTask(false); + public ValueTask DisposeAsync() => new ValueTask(); + } + + /// + /// Gets an empty . + /// + public static readonly IAsyncEnumerator Empty = new EmptyAsyncEnumerator(); + } +} \ No newline at end of file diff --git a/src/NPoco/Database.cs b/src/NPoco/Database.cs index 0be25245..b3c0c5d8 100644 --- a/src/NPoco/Database.cs +++ b/src/NPoco/Database.cs @@ -19,12 +19,10 @@ using System.Linq.Expressions; using System.Reflection; using System.Text; +using System.Threading.Tasks; using NPoco.Expressions; using NPoco.Extensions; using NPoco.Linq; -#if !DNXCORE50 -using System.Configuration; -#endif namespace NPoco { @@ -66,55 +64,6 @@ public Database(DbConnection connection, DatabaseType dbType, IsolationLevel? is //} } -#if !DNXCORE50 && !NETSTANDARD2_0 - public Database(string connectionString, string providerName) - : this(connectionString, providerName, DefaultEnableAutoSelect) - { } - - public Database(string connectionString, string providerName, IsolationLevel isolationLevel) - : this(connectionString, providerName, isolationLevel, DefaultEnableAutoSelect) - { } - - public Database(string connectionString, string providerName, bool enableAutoSelect) - : this(connectionString, providerName, null, enableAutoSelect) - { } - - public Database(string connectionString, string providerName, IsolationLevel? isolationLevel, bool enableAutoSelect) - { - EnableAutoSelect = enableAutoSelect; - KeepConnectionAlive = false; - - _connectionString = connectionString; - _factory = DbProviderFactories.GetFactory(providerName); - var dbTypeName = (_factory == null ? _sharedConnection.GetType() : _factory.GetType()).Name; - _dbType = DatabaseType.Resolve(dbTypeName, providerName); - _providerName = providerName; - _isolationLevel = isolationLevel.HasValue ? isolationLevel.Value : _dbType.GetDefaultTransactionIsolationLevel(); - _paramPrefix = _dbType.GetParameterPrefix(_connectionString); - } - - public Database(string connectionString, DatabaseType dbType) - : this(connectionString, dbType, null, DefaultEnableAutoSelect) - { } - - public Database(string connectionString, DatabaseType dbType, IsolationLevel? isolationLevel) - : this(connectionString, dbType, isolationLevel, DefaultEnableAutoSelect) - { } - - public Database(string connectionString, DatabaseType dbType, IsolationLevel? isolationLevel, bool enableAutoSelect) - { - EnableAutoSelect = enableAutoSelect; - KeepConnectionAlive = false; - - _connectionString = connectionString; - _dbType = dbType; - _providerName = _dbType.GetProviderName(); - _factory = DbProviderFactories.GetFactory(_dbType.GetProviderName()); - _isolationLevel = isolationLevel.HasValue ? isolationLevel.Value : _dbType.GetDefaultTransactionIsolationLevel(); - _paramPrefix = _dbType.GetParameterPrefix(_connectionString); - } -#endif - public Database(string connectionString, DatabaseType databaseType, DbProviderFactory provider) : this(connectionString, databaseType, provider, null, DefaultEnableAutoSelect) { } @@ -132,52 +81,6 @@ public Database(string connectionString, DatabaseType databaseType, DbProviderFa _paramPrefix = _dbType.GetParameterPrefix(_connectionString); } -#if !DNXCORE50 && !NETSTANDARD2_0 - public Database(string connectionStringName) - : this(connectionStringName, DefaultEnableAutoSelect) - { } - - public Database(string connectionStringName, IsolationLevel isolationLevel) - : this(connectionStringName, isolationLevel, DefaultEnableAutoSelect) - { } - - public Database(string connectionStringName, bool enableAutoSelect) - : this(connectionStringName, (IsolationLevel?) null, enableAutoSelect) - { } - - public Database(string connectionStringName, IsolationLevel? isolationLevel, bool enableAutoSelect) - { - EnableAutoSelect = enableAutoSelect; - KeepConnectionAlive = false; - - // Use first? - if (connectionStringName == "") connectionStringName = ConfigurationManager.ConnectionStrings[0].Name; - - // Work out connection string and provider name - var providerName = "System.Data.SqlClient"; - if (ConfigurationManager.ConnectionStrings[connectionStringName] != null) - { - if (!string.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[connectionStringName].ProviderName)) - { - providerName = ConfigurationManager.ConnectionStrings[connectionStringName].ProviderName; - } - } - else - { - throw new InvalidOperationException("Can't find a connection string with the name '" + connectionStringName + "'"); - } - - // Store factory and connection string - _connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString; - _providerName = providerName; - - _factory = DbProviderFactories.GetFactory(_providerName); - _dbType = DatabaseType.Resolve(_factory.GetType().Name, _providerName); - _isolationLevel = isolationLevel.HasValue ? isolationLevel.Value : _dbType.GetDefaultTransactionIsolationLevel(); - _paramPrefix = _dbType.GetParameterPrefix(_connectionString); - } -#endif - private readonly DatabaseType _dbType; public DatabaseType DatabaseType { get { return _dbType; } } public IsolationLevel IsolationLevel { get { return _isolationLevel; } } @@ -952,6 +855,111 @@ public IEnumerable Query(Sql Sql) return Query(default(T), Sql); } + private async IAsyncEnumerable ReadAsync(Type type, object instance, DbDataReader r, DbCommand cmd) + { + try + { + using (cmd) + { + using (r) + { + var pd = PocoDataFactory.ForType(type); + var factory = new MappingFactory(pd, r); + while (true) + { + T poco; + try + { + if (!await r.ReadAsync()) yield break; + poco = (T)factory.Map(r, instance); + } + catch (Exception x) + { + OnExceptionInternal(x); + throw; + } + + yield return poco; + } + } + } + } + finally + { + CloseSharedConnectionInternal(); + } + } + + private async IAsyncEnumerable ReadOneToManyAsync(T instance, DbDataReader r, DbCommand cmd, Expression> listExpression, Func idFunc) + { + Func listFunc = null; + PocoMember pocoMember = null; + PocoMember foreignMember = null; + + try + { + using (cmd) + { + using (r) + { + var pocoData = PocoDataFactory.ForType(typeof(T)); + if (listExpression != null) + { + idFunc = idFunc ?? (x => pocoData.GetPrimaryKeyValues(x)); + listFunc = listExpression.Compile(); + var key = PocoColumn.GenerateKey(MemberChainHelper.GetMembers(listExpression)); + pocoMember = pocoData.Members.FirstOrDefault(x => x.Name == key); + foreignMember = pocoMember != null ? pocoMember.PocoMemberChildren.FirstOrDefault(x => x.Name == pocoMember.ReferenceMemberName && x.ReferenceType == ReferenceType.Foreign) : null; + } + + var factory = new MappingFactory(pocoData, r); + object prevPoco = null; + + while (true) + { + T poco; + try + { + if (!await r.ReadAsync()) break; + poco = (T)factory.Map(r, instance); + } + catch (Exception x) + { + OnExceptionInternal(x); + throw; + } + + if (prevPoco != null) + { + if (listFunc != null + && pocoMember != null + && idFunc(poco).SequenceEqual(idFunc((T)prevPoco))) + { + OneToManyHelper.SetListValue(listFunc, pocoMember, prevPoco, poco); + continue; + } + + OneToManyHelper.SetForeignList(listFunc, foreignMember, prevPoco); + yield return (T)prevPoco; + } + + prevPoco = poco; + } + + if (prevPoco != null) + { + OneToManyHelper.SetForeignList(listFunc, foreignMember, prevPoco); + yield return (T)prevPoco; + } + } + } + } + finally + { + CloseSharedConnectionInternal(); + } + } + private IEnumerable Read(Type type, object instance, DbDataReader r, DbCommand cmd) { try @@ -1580,31 +1588,31 @@ public int UpdateBatch(IEnumerable> pocos, BatchOptions option var sql = new Sql(); foreach (var preparedUpdate in preparedUpdates) - { + { if (preparedUpdate.Sql != null) { sql.Append(preparedUpdate.Sql + options.StatementSeperator, preparedUpdate.Rawvalues.ToArray()); - } + } } using (var cmd = CreateCommand(_sharedConnection, sql.SQL, sql.Arguments)) - { + { result += ExecuteNonQueryHelper(cmd); - } + } } } catch (Exception x) - { + { OnExceptionInternal(x); throw; - } + } finally - { + { CloseSharedConnectionInternal(); - } + } return result; - } + } // Update a record with values from a poco. primary key value can be either supplied or read from the poco private TRet UpdateImp(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns, Func, TRet> executeFunc, TRet defaultId) @@ -1623,12 +1631,8 @@ private TRet UpdateImp(string tableName, string primaryKeyName, object poc var result = executeFunc(preparedStatement.Sql, preparedStatement.Rawvalues.ToArray(), (id) => { if (id == 0 && !string.IsNullOrEmpty(preparedStatement.VersionName) && VersionException == VersionExceptionHandling.Exception) - { -#if DNXCORE50 - throw new Exception(string.Format("A Concurrency update occurred in table '{0}' for primary key value(s) = '{1}' and version = '{2}'", tableName, string.Join(",", preparedStatement.PrimaryKeyValuePairs.Values.Select(x => x.ToString()).ToArray()), preparedStatement.VersionValue)); -#else + { throw new DBConcurrencyException(string.Format("A Concurrency update occurred in table '{0}' for primary key value(s) = '{1}' and version = '{2}'", tableName, string.Join(",", preparedStatement.PrimaryKeyValuePairs.Values.Select(x => x.ToString()).ToArray()), preparedStatement.VersionValue)); -#endif } // Set Version @@ -1806,12 +1810,11 @@ public int Delete(Sql sql) /// Checks if a poco represents a new record. public bool IsNew(T poco) { -#if !NET35 if (poco is System.Dynamic.ExpandoObject || poco is PocoExpando) { return true; } -#endif + var pd = PocoDataFactory.ForType(poco.GetType()); object pk; PocoColumn pc; @@ -2009,13 +2012,11 @@ internal DbDataReader ExecuteReaderHelper(DbCommand cmd) DbDataReader IDatabaseHelpers.ExecuteReaderHelper(DbCommand cmd) => ExecuteReaderHelper(cmd); -#if !NET35 && !NET40 - System.Threading.Tasks.Task IDatabaseHelpers.ExecuteNonQueryHelperAsync(DbCommand cmd) => ExecuteNonQueryHelperAsync(cmd); + Task IDatabaseHelpers.ExecuteNonQueryHelperAsync(DbCommand cmd) => ExecuteNonQueryHelperAsync(cmd); - System.Threading.Tasks.Task IDatabaseHelpers.ExecuteScalarHelperAsync(DbCommand cmd) => ExecuteScalarHelperAsync(cmd); + Task IDatabaseHelpers.ExecuteScalarHelperAsync(DbCommand cmd) => ExecuteScalarHelperAsync(cmd); - System.Threading.Tasks.Task IDatabaseHelpers.ExecuteReaderHelperAsync(DbCommand cmd) => ExecuteReaderHelperAsync(cmd); -#endif + Task IDatabaseHelpers.ExecuteReaderHelperAsync(DbCommand cmd) => ExecuteReaderHelperAsync(cmd); public static bool IsEnum(MemberInfoData memberInfo) { diff --git a/src/NPoco/DatabaseType.cs b/src/NPoco/DatabaseType.cs index dfd05a20..4530d5d6 100644 --- a/src/NPoco/DatabaseType.cs +++ b/src/NPoco/DatabaseType.cs @@ -7,24 +7,25 @@ using NPoco.DatabaseTypes; using NPoco.Expressions; using System.Reflection; +using System.Threading.Tasks; namespace NPoco { /// /// Base class for DatabaseType handlers - provides default/common handling for different database engines /// - public abstract class DatabaseType + public abstract partial class DatabaseType { // Helper Properties - public static DatabaseType SqlServer2012 { get { return Singleton.Instance; } } - public static DatabaseType SqlServer2008 { get { return Singleton.Instance; } } - public static DatabaseType SqlServer2005 { get { return Singleton.Instance; } } + public static DatabaseType SqlServer2012 { get { return DynamicDatabaseType.MakeSqlServerType("SqlServerDatabaseType"); } } + public static DatabaseType SqlServer2008 { get { return DynamicDatabaseType.MakeSqlServerType("SqlServer2008DatabaseType"); } } + public static DatabaseType SqlServer2005 { get { return DynamicDatabaseType.MakeSqlServerType("SqlServerDatabaseType"); } } public static DatabaseType PostgreSQL { get { return Singleton.Instance; } } public static DatabaseType Oracle { get { return Singleton.Instance; } } public static DatabaseType OracleManaged { get { return Singleton.Instance; } } public static DatabaseType MySQL { get { return Singleton.Instance; } } public static DatabaseType SQLite { get { return Singleton.Instance; } } - public static DatabaseType SQLCe { get { return Singleton.Instance; } } + public static DatabaseType SQLCe { get { return DynamicDatabaseType.MakeSqlServerType("SqlServerCEDatabaseType"); } } public static DatabaseType Firebird { get { return Singleton.Instance; } } readonly Dictionary typeMap; @@ -223,13 +224,12 @@ public virtual object ExecuteInsert(Database db, DbCommand cmd, string primar cmd.CommandText += ";\nSELECT @@IDENTITY AS NewID;"; return db.ExecuteScalarHelper(cmd); } -#if !NET35 && !NET40 - public virtual async System.Threading.Tasks.Task ExecuteInsertAsync(Database db, DbCommand cmd, string primaryKeyName, bool useOutputClause, T poco, object[] args) + + public virtual async Task ExecuteInsertAsync(Database db, DbCommand cmd, string primaryKeyName, bool useOutputClause, T poco, object[] args) { cmd.CommandText += ";\nSELECT @@IDENTITY AS NewID;"; return await db.ExecuteScalarHelperAsync(cmd); } -#endif public virtual void InsertBulk(IDatabase db, IEnumerable pocos) { @@ -251,7 +251,7 @@ public static DatabaseType Resolve(string typeName, string providerName) if (typeName.StartsWith("MySql")) return Singleton.Instance; if (typeName.StartsWith("SqlCe")) - return Singleton.Instance; + return DynamicDatabaseType.MakeSqlServerType("SqlServerCEDatabaseType"); if (typeName.StartsWith("Npgsql") || typeName.StartsWith("PgSql")) return Singleton.Instance; if (typeName.StartsWith("OracleManaged")) @@ -261,7 +261,7 @@ public static DatabaseType Resolve(string typeName, string providerName) if (typeName.StartsWith("SQLite")) return Singleton.Instance; if (typeName.StartsWith("SqlConnection")) - return Singleton.Instance; + return DynamicDatabaseType.MakeSqlServerType("SqlServerDatabaseType"); if (typeName.StartsWith("Fb") || typeName.StartsWith("Firebird")) return Singleton.Instance; @@ -271,7 +271,7 @@ public static DatabaseType Resolve(string typeName, string providerName) if (providerName.IndexOf("MySql", StringComparison.OrdinalIgnoreCase) >= 0) return Singleton.Instance; if (providerName.IndexOf("SqlServerCe", StringComparison.OrdinalIgnoreCase) >= 0) - return Singleton.Instance; + return DynamicDatabaseType.MakeSqlServerType("SqlServerCEDatabaseType"); if (providerName.IndexOf("pgsql", StringComparison.OrdinalIgnoreCase) >= 0) return Singleton.Instance; if (providerName.IndexOf("Oracle.DataAccess", StringComparison.OrdinalIgnoreCase) >= 0) @@ -285,7 +285,7 @@ public static DatabaseType Resolve(string typeName, string providerName) } // Assume SQL Server - return Singleton.Instance; + return DynamicDatabaseType.MakeSqlServerType("SqlServerDatabaseType"); } public virtual string GetDefaultInsertSql(string tableName, string primaryKeyName, bool useOutputClause, string[] names, string[] parameters) @@ -334,25 +334,23 @@ public virtual SqlExpression ExpressionVisitor(IDatabase db, PocoData poco public virtual string GetProviderName() { - return "System.Data.SqlClient"; + return "Microsoft.Data.SqlClient"; } -#if !NET35 && !NET40 - public virtual System.Threading.Tasks.Task ExecuteNonQueryAsync(Database database, DbCommand cmd) + public virtual Task ExecuteNonQueryAsync(Database database, DbCommand cmd) { return cmd.ExecuteNonQueryAsync(); } - public virtual System.Threading.Tasks.Task ExecuteScalarAsync(Database database, DbCommand cmd) + public virtual Task ExecuteScalarAsync(Database database, DbCommand cmd) { return cmd.ExecuteScalarAsync(); } - public virtual System.Threading.Tasks.Task ExecuteReaderAsync(Database database, DbCommand cmd) + public virtual Task ExecuteReaderAsync(Database database, DbCommand cmd) { return cmd.ExecuteReaderAsync(); } -#endif public virtual object ProcessDefaultMappings(PocoColumn pocoColumn, object value) { diff --git a/src/NPoco/DatabaseTypes/FirebirdDatabaseType.cs b/src/NPoco/DatabaseTypes/FirebirdDatabaseType.cs index 4c0ecb17..2b1e7741 100644 --- a/src/NPoco/DatabaseTypes/FirebirdDatabaseType.cs +++ b/src/NPoco/DatabaseTypes/FirebirdDatabaseType.cs @@ -4,6 +4,7 @@ using System.Data.Common; using System.Text; using NPoco.Expressions; +using System.Threading.Tasks; namespace NPoco.DatabaseTypes { @@ -75,8 +76,7 @@ public override object ExecuteInsert(Database db, DbCommand cmd, string prima return -1; } -#if !NET35 && !NET40 - public override async System.Threading.Tasks.Task ExecuteInsertAsync(Database db, DbCommand cmd, string primaryKeyName, bool useOutputClause, T poco, object[] args) + public override async Task ExecuteInsertAsync(Database db, DbCommand cmd, string primaryKeyName, bool useOutputClause, T poco, object[] args) { if (primaryKeyName != null) { @@ -88,7 +88,6 @@ public override async System.Threading.Tasks.Task ExecuteInsertAsync( await db.ExecuteNonQueryHelperAsync(cmd); return TaskAsyncHelper.FromResult(-1); } -#endif public override SqlExpression ExpressionVisitor(IDatabase db, PocoData pocoData, bool prefixTableName) { diff --git a/src/NPoco/DatabaseTypes/OracleDatabaseType.cs b/src/NPoco/DatabaseTypes/OracleDatabaseType.cs index 2092a15c..4f75f1b3 100644 --- a/src/NPoco/DatabaseTypes/OracleDatabaseType.cs +++ b/src/NPoco/DatabaseTypes/OracleDatabaseType.cs @@ -2,6 +2,7 @@ using System.Data; using System.Data.Common; using System.Reflection; +using System.Threading.Tasks; namespace NPoco.DatabaseTypes { @@ -24,7 +25,7 @@ public override string BuildPageQuery(long skip, long take, PagingHelper.SQLPart throw new Exception("Query must alias '*' when performing a paged query.\neg. select t.* from table t order by t.id"); // Same deal as SQL Server - return Singleton.Instance.BuildPageQuery(skip, take, parts, ref args); + return PagingHelper.BuildPaging(skip, take, parts, ref args); } public override string EscapeSqlIdentifier(string str) @@ -65,8 +66,7 @@ public override object ExecuteInsert(Database db, DbCommand cmd, string prima return -1; } -#if !NET35 && !NET40 - public override async System.Threading.Tasks.Task ExecuteInsertAsync(Database db, DbCommand cmd, string primaryKeyName, bool useOutputClause, T poco, object[] args) + public override async Task ExecuteInsertAsync(Database db, DbCommand cmd, string primaryKeyName, bool useOutputClause, T poco, object[] args) { if (primaryKeyName != null) { @@ -78,8 +78,6 @@ public override async System.Threading.Tasks.Task ExecuteInsertAsync( await db.ExecuteNonQueryHelperAsync(cmd); return TaskAsyncHelper.FromResult(-1); } -#endif - public override string GetProviderName() { diff --git a/src/NPoco/DatabaseTypes/PostgreSQLDatabaseType.cs b/src/NPoco/DatabaseTypes/PostgreSQLDatabaseType.cs index 99cb1a8d..71d5960a 100644 --- a/src/NPoco/DatabaseTypes/PostgreSQLDatabaseType.cs +++ b/src/NPoco/DatabaseTypes/PostgreSQLDatabaseType.cs @@ -1,5 +1,6 @@ using System.Data; using System.Data.Common; +using System.Threading.Tasks; namespace NPoco.DatabaseTypes { @@ -35,8 +36,7 @@ public override object ExecuteInsert(Database db, DbCommand cmd, string prima return -1; } -#if !NET35 && !NET40 - public override async System.Threading.Tasks.Task ExecuteInsertAsync(Database db, DbCommand cmd, string primaryKeyName, bool useOutputClause, T poco, object[] args) + public override async Task ExecuteInsertAsync(Database db, DbCommand cmd, string primaryKeyName, bool useOutputClause, T poco, object[] args) { if (primaryKeyName != null) { @@ -47,7 +47,6 @@ public override async System.Threading.Tasks.Task ExecuteInsertAsync( await db.ExecuteNonQueryHelperAsync(cmd); return TaskAsyncHelper.FromResult(-1); } -#endif public override string GetParameterPrefix(string connectionString) { diff --git a/src/NPoco/DatabaseTypes/SQLiteDatabaseType.cs b/src/NPoco/DatabaseTypes/SQLiteDatabaseType.cs index 2e63cd2d..b36f9002 100644 --- a/src/NPoco/DatabaseTypes/SQLiteDatabaseType.cs +++ b/src/NPoco/DatabaseTypes/SQLiteDatabaseType.cs @@ -1,6 +1,7 @@ using System; using System.Data; using System.Data.Common; +using System.Threading.Tasks; namespace NPoco.DatabaseTypes { @@ -31,8 +32,7 @@ public override object ExecuteInsert(Database db, DbCommand cmd, string prima return -1; } -#if !NET35 && !NET40 - public override async System.Threading.Tasks.Task ExecuteInsertAsync(Database db, DbCommand cmd, string primaryKeyName, bool useOutputClause, T poco, object[] args) + public override async Task ExecuteInsertAsync(Database db, DbCommand cmd, string primaryKeyName, bool useOutputClause, T poco, object[] args) { if (primaryKeyName != null) { @@ -43,7 +43,6 @@ public override async System.Threading.Tasks.Task ExecuteInsertAsync( await db.ExecuteNonQueryHelperAsync(cmd); return TaskAsyncHelper.FromResult(-1); } -#endif public override string GetExistsSql() { diff --git a/src/NPoco/FluentMappings/ConventionScanner.cs b/src/NPoco/FluentMappings/ConventionScanner.cs index 506b124f..6b9ce6ee 100644 --- a/src/NPoco/FluentMappings/ConventionScanner.cs +++ b/src/NPoco/FluentMappings/ConventionScanner.cs @@ -28,12 +28,10 @@ public void Assembly(Assembly assembly) _scannerSettings.Assemblies.Add(assembly); } -#if !DNXCORE50 public void TheCallingAssembly() { _scannerSettings.TheCallingAssembly = true; } -#endif public void IncludeTypes(Func typeIncludes) { diff --git a/src/NPoco/FluentMappings/FluentMappingConfiguration.cs b/src/NPoco/FluentMappings/FluentMappingConfiguration.cs index e9bdc9dc..bff825f5 100644 --- a/src/NPoco/FluentMappings/FluentMappingConfiguration.cs +++ b/src/NPoco/FluentMappings/FluentMappingConfiguration.cs @@ -190,10 +190,8 @@ private static ConventionScannerSettings ProcessSettings(Action FindTypes(ConventionScannerSettings scannerSettings) { -#if !DNXCORE50 if (scannerSettings.TheCallingAssembly) scannerSettings.Assemblies.Add(FindTheCallingAssembly()); -#endif var types = scannerSettings.Assemblies .SelectMany(x => x.GetExportedTypes()) @@ -307,7 +305,6 @@ private static FluentConfig SetFactory(Mappings mappings, Action includeTypes); void TablesNamed(Func tableFunc); diff --git a/src/NPoco/IAsyncDatabase.cs b/src/NPoco/IAsyncDatabase.cs index 9908d46b..f0dd4e55 100644 --- a/src/NPoco/IAsyncDatabase.cs +++ b/src/NPoco/IAsyncDatabase.cs @@ -3,15 +3,12 @@ using System.Data; using System.Linq.Expressions; using NPoco.Linq; -#if !NET35 && !NET40 using System.Threading.Tasks; -#endif namespace NPoco { public interface IAsyncDatabase : IBaseDatabase { -#if !NET35 && !NET40 /// /// Fetch the only row of type T using the sql and parameters specified /// Get an object of type T by primary key value @@ -67,13 +64,13 @@ public interface IAsyncDatabase : IBaseDatabase /// Fetch objects of type T from the database using the sql and parameters specified. /// Caution: This query will only be executed once you start iterating the result /// - Task> QueryAsync(string sql, params object[] args); + Task> QueryAsync(string sql, params object[] args); /// /// Fetch objects of type T from the database using the sql and parameters specified. /// Caution: This query will only be executed once you start iterating the result /// - Task> QueryAsync(Sql sql); + Task> QueryAsync(Sql sql); /// /// Entry point for LINQ queries @@ -199,6 +196,5 @@ public interface IAsyncDatabase : IBaseDatabase /// Generate a delete statement using a Fluent syntax. Remember to call Execute. /// IAsyncDeleteQueryProvider DeleteManyAsync(); -#endif } } diff --git a/src/NPoco/IDatabase.cs b/src/NPoco/IDatabase.cs index 59e0cd85..d58fbf58 100644 --- a/src/NPoco/IDatabase.cs +++ b/src/NPoco/IDatabase.cs @@ -2,10 +2,11 @@ using System.Collections.Generic; using System.Linq.Expressions; using NPoco.Linq; +using System.Threading.Tasks; namespace NPoco { - public interface IDatabase : IDisposable, IDatabaseQuery, IDatabaseConfig + public interface IDatabase : IDatabaseQuery, IDatabaseConfig { /// /// Insert POCO into the table, primary key and autoincrement specified diff --git a/src/NPoco/IDatabaseHelpers.cs b/src/NPoco/IDatabaseHelpers.cs index 0c7de900..3e23d8f1 100644 --- a/src/NPoco/IDatabaseHelpers.cs +++ b/src/NPoco/IDatabaseHelpers.cs @@ -1,7 +1,5 @@ using System.Data.Common; -#if !NET35 using System.Threading.Tasks; -#endif namespace NPoco { @@ -10,10 +8,8 @@ public interface IDatabaseHelpers int ExecuteNonQueryHelper(DbCommand cmd); object ExecuteScalarHelper(DbCommand cmd); DbDataReader ExecuteReaderHelper(DbCommand cmd); -#if !NET35 && !NET40 Task ExecuteNonQueryHelperAsync(DbCommand cmd); Task ExecuteScalarHelperAsync(DbCommand cmd); Task ExecuteReaderHelperAsync(DbCommand cmd); -#endif } } \ No newline at end of file diff --git a/src/NPoco/IDatabaseQuery.cs b/src/NPoco/IDatabaseQuery.cs index 76694050..a1d7caa3 100644 --- a/src/NPoco/IDatabaseQuery.cs +++ b/src/NPoco/IDatabaseQuery.cs @@ -4,13 +4,11 @@ using System.Data; using System.Linq.Expressions; using NPoco.Linq; -#if !NET35 && !NET40 using System.Threading.Tasks; -#endif namespace NPoco { - public interface IDatabaseQuery : IAsyncDatabase + public interface IDatabaseQuery : IAsyncDatabase, IDisposable { /// /// Builds a paged query from a non-paged query diff --git a/src/NPoco/Linq/DeleteQueryProvider.cs b/src/NPoco/Linq/DeleteQueryProvider.cs index 1050b7c3..3dd530e9 100644 --- a/src/NPoco/Linq/DeleteQueryProvider.cs +++ b/src/NPoco/Linq/DeleteQueryProvider.cs @@ -1,27 +1,21 @@ using System; using System.Linq.Expressions; -#if !NET35 && !NET40 using System.Threading.Tasks; -#endif using NPoco.Expressions; namespace NPoco.Linq { public interface IAsyncDeleteQueryProvider { -#if !NET35 && !NET40 IAsyncDeleteQueryProvider Where(Expression> whereExpression); Task Execute(); -#endif } public interface IDeleteQueryProvider { IDeleteQueryProvider Where(Expression> whereExpression); int Execute(); -#if !NET35 && !NET40 Task ExecuteAsync(); -#endif } public class DeleteQueryProvider : AsyncDeleteQueryProvider, IDeleteQueryProvider @@ -41,12 +35,11 @@ public DeleteQueryProvider(IDatabase database) : base(database) } #pragma warning restore CS0109 -#if !NET35 && !NET40 public Task ExecuteAsync() { return base.Execute(); } -#endif + } public class AsyncDeleteQueryProvider : IAsyncDeleteQueryProvider @@ -65,11 +58,10 @@ public IAsyncDeleteQueryProvider Where(Expression> whereExpress _sqlExpression = _sqlExpression.Where(whereExpression); return this; } -#if !NET35 && !NET40 + public Task Execute() { return _database.ExecuteAsync(_sqlExpression.Context.ToDeleteStatement(), _sqlExpression.Context.Params); } -#endif } } \ No newline at end of file diff --git a/src/NPoco/Linq/SimpleQueryProvider.cs b/src/NPoco/Linq/SimpleQueryProvider.cs index 0f4c61bd..bb0a0c1c 100644 --- a/src/NPoco/Linq/SimpleQueryProvider.cs +++ b/src/NPoco/Linq/SimpleQueryProvider.cs @@ -3,19 +3,16 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -#if !NET35 && !NET40 using System.Threading.Tasks; -#endif using NPoco.Expressions; namespace NPoco.Linq { public interface IAsyncQueryResultProvider { -#if !NET35 && !NET40 Task> ToList(); Task ToArray(); - Task> ToEnumerable(); + Task> ToEnumerable(); Task FirstOrDefault(); Task FirstOrDefault(Expression> whereExpression); Task First(); @@ -32,7 +29,6 @@ public interface IAsyncQueryResultProvider Task> ProjectTo(Expression> projectionExpression); Task> Distinct(Expression> projectionExpression); Task> Distinct(); -#endif } public interface IQueryResultProvider @@ -52,18 +48,15 @@ public interface IQueryResultProvider List ToList(); T[] ToArray(); IEnumerable ToEnumerable(); -#if !NET35 List ToDynamicList(); IEnumerable ToDynamicEnumerable(); -#endif Page ToPage(int page, int pageSize); List ProjectTo(Expression> projectionExpression); List Distinct(Expression> projectionExpression); List Distinct(); -#if !NET35 && !NET40 Task> ToListAsync(); Task ToArrayAsync(); - Task> ToEnumerableAsync(); + Task> ToEnumerableAsync(); Task FirstOrDefaultAsync(); Task FirstOrDefaultAsync(Expression> whereExpression); Task FirstAsync(); @@ -80,7 +73,6 @@ public interface IQueryResultProvider Task> ProjectToAsync(Expression> projectionExpression); Task> DistinctAsync(Expression> projectionExpression); Task> DistinctAsync(); -#endif } public interface IQueryProvider : IQueryResultProvider @@ -203,18 +195,17 @@ private IAsyncQueryProviderWithIncludes QueryProviderWithIncludes(Expression return this; } -#if !NET35 && !NET40 public async Task> ToList() { - return (await ToEnumerable().ConfigureAwait(false)).ToList(); + return await (await ToEnumerable().ConfigureAwait(false)).ToListAsync(); } public async Task ToArray() { - return (await ToEnumerable().ConfigureAwait(false)).ToArray(); + return await (await ToEnumerable().ConfigureAwait(false)).ToArrayAsync(); } - public Task> ToEnumerable() + public Task> ToEnumerable() { return _database.QueryAsync(default(T), _listExpression, null, BuildSql()); } @@ -227,7 +218,7 @@ public Task FirstOrDefault() public async Task FirstOrDefault(Expression> whereExpression) { AddWhere(whereExpression); - return (await ToEnumerable().ConfigureAwait(false)).FirstOrDefault(); + return await (await ToEnumerable()).FirstOrDefaultAsync(); } public Task First() @@ -238,7 +229,7 @@ public Task First() public async Task First(Expression> whereExpression) { AddWhere(null); - return (await ToEnumerable().ConfigureAwait(false)).First(); + return await (await ToEnumerable()).FirstAsync(); } public Task SingleOrDefault() @@ -249,7 +240,7 @@ public Task SingleOrDefault() public async Task SingleOrDefault(Expression> whereExpression) { AddWhere(whereExpression); - return (await ToEnumerable().ConfigureAwait(false)).SingleOrDefault(); + return await (await ToEnumerable().ConfigureAwait(false)).SingleOrDefaultAsync(); } public Task Single() @@ -260,7 +251,7 @@ public Task Single() public async Task Single(Expression> whereExpression) { AddWhere(whereExpression); - return (await ToEnumerable().ConfigureAwait(false)).Single(); + return await (await ToEnumerable().ConfigureAwait(false)).SingleAsync(); } public Task Count() @@ -313,20 +304,19 @@ public async Task> ToPage(int page, int pageSize) public async Task> ProjectTo(Expression> projectionExpression) { var sql = _buildComplexSql.GetSqlForProjection(projectionExpression, false); - return (await _database.QueryAsync(sql).ConfigureAwait(false)).Select(projectionExpression.Compile()).ToList(); + return await (await _database.QueryAsync(sql).ConfigureAwait(false)).Select(projectionExpression.Compile()).ToListAsync(); } public async Task> Distinct() { - return (await _database.QueryAsync(new Sql(_sqlExpression.Context.ToSelectStatement(true, true), _sqlExpression.Context.Params))).ToList(); + return await (await _database.QueryAsync(new Sql(_sqlExpression.Context.ToSelectStatement(true, true), _sqlExpression.Context.Params))).ToListAsync(); } public async Task> Distinct(Expression> projectionExpression) { var sql = _buildComplexSql.GetSqlForProjection(projectionExpression, true); - return (await _database.QueryAsync(sql).ConfigureAwait(false)).Select(projectionExpression.Compile()).ToList(); + return await (await _database.QueryAsync(sql).ConfigureAwait(false)).Select(projectionExpression.Compile()).ToListAsync(); } -#endif public IAsyncQueryProvider Where(Expression> whereExpression) { @@ -444,7 +434,6 @@ public IAsyncQueryProvider From(QueryBuilder builder) return this; } -#if !NET35 public List ToDynamicList() { return ToDynamicEnumerable().ToList(); @@ -455,7 +444,7 @@ public IEnumerable ToDynamicEnumerable() var sql = BuildSql(); return _database.QueryImp(null, null, null, sql); } -#endif + IDatabase INeedDatabase.GetDatabase() { return _database; @@ -609,7 +598,6 @@ private IEnumerable ExecuteQuery(Sql sql) } #pragma warning restore CS0109 -#if !NET35 && !NET40 public Task> ToListAsync() { return base.ToList(); @@ -620,7 +608,7 @@ public Task ToArrayAsync() return base.ToArray(); } - public Task> ToEnumerableAsync() + public Task> ToEnumerableAsync() { return base.ToEnumerable(); } @@ -704,7 +692,6 @@ public Task> DistinctAsync() { return base.Distinct(); } -#endif public new IQueryProvider IncludeMany(Expression> expression, JoinType joinType = JoinType.Left) { diff --git a/src/NPoco/Linq/UpdateQueryProvider.cs b/src/NPoco/Linq/UpdateQueryProvider.cs index c0d26157..dd517480 100644 --- a/src/NPoco/Linq/UpdateQueryProvider.cs +++ b/src/NPoco/Linq/UpdateQueryProvider.cs @@ -1,20 +1,16 @@ using System; using System.Linq.Expressions; -#if !NET35 && !NET40 using System.Threading.Tasks; -#endif using NPoco.Expressions; namespace NPoco.Linq { public interface IAsyncUpdateQueryProvider { -#if !NET35 && !NET40 IAsyncUpdateQueryProvider Where(Expression> whereExpression); IAsyncUpdateQueryProvider ExcludeDefaults(); IAsyncUpdateQueryProvider OnlyFields(Expression> onlyFields); Task Execute(T obj); -#endif } public interface IUpdateQueryProvider @@ -23,9 +19,7 @@ public interface IUpdateQueryProvider IUpdateQueryProvider ExcludeDefaults(); IUpdateQueryProvider OnlyFields(Expression> onlyFields); int Execute(T obj); -#if !NET35 && !NET40 Task ExecuteAsync(T obj); -#endif } public class UpdateQueryProvider : AsyncUpdateQueryProvider, IUpdateQueryProvider @@ -57,12 +51,10 @@ public UpdateQueryProvider(IDatabase database) : base(database) } #pragma warning restore CS0109 -#if !NET35 && !NET40 public Task ExecuteAsync(T obj) { return base.Execute(obj); } -#endif } public class AsyncUpdateQueryProvider : IAsyncUpdateQueryProvider @@ -97,12 +89,10 @@ public IAsyncUpdateQueryProvider OnlyFields(Expression> onlyF return this; } -#if !NET35 && !NET40 public async Task Execute(T obj) { var updateStatement = _sqlExpression.Context.ToUpdateStatement(obj, _excludeDefaults, _onlyFields); return await _database.ExecuteAsync(updateStatement, _sqlExpression.Context.Params); } -#endif } } diff --git a/src/NPoco/MapperCollection.cs b/src/NPoco/MapperCollection.cs index 6f4e63cb..7dfa1357 100644 --- a/src/NPoco/MapperCollection.cs +++ b/src/NPoco/MapperCollection.cs @@ -12,9 +12,7 @@ public class MapperCollection : List public MapperCollection() { -#if !NET35 Factories.Add(typeof(object), x => new PocoExpando()); -#endif Factories.Add(typeof(IDictionary), x => new Dictionary(StringComparer.OrdinalIgnoreCase)); Factories.Add(typeof(Dictionary), x => new Dictionary(StringComparer.OrdinalIgnoreCase)); } diff --git a/src/NPoco/NPoco.csproj b/src/NPoco/NPoco.csproj index a78801cc..43e42a35 100644 --- a/src/NPoco/NPoco.csproj +++ b/src/NPoco/NPoco.csproj @@ -2,91 +2,32 @@ An extremely easy to use Micro-ORM supporting Sql Server, MySQL, PostgreSQL, Oracle, Sqlite, SqlCE. - net35;net45;net40;netstandard1.3;netstandard2.0 + netstandard2.0 NPoco NPoco - 4.0.2 + 5.0.0 Adam Schröder orm;sql;micro-orm;database;mvc https://github.com/schotime/NPoco - http://www.apache.org/licenses/LICENSE-2.0 + LICENSE.txt https://github.com/schotime/NPoco/wiki/Release-Notes - $(PackageTargetFallback);dnxcore50 false false false false false false + 8.0 - - - - - - - + + + + - - - - - - - + + - - - - - - - - - - - - - - - - $(DefineConstants);DNXCORE50 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v3.5\Profile\Client - - diff --git a/src/NPoco/PagingHelper.cs b/src/NPoco/PagingHelper.cs index 629c1286..17382048 100644 --- a/src/NPoco/PagingHelper.cs +++ b/src/NPoco/PagingHelper.cs @@ -1,5 +1,6 @@ using System; using System.Data; +using System.Linq; using System.Text.RegularExpressions; namespace NPoco @@ -50,5 +51,17 @@ public static bool SplitSQL(string sql, out SQLParts parts) return true; } + + private static readonly Regex OrderByAlias = new Regex(@"[\""\[\]\w]+\.([\[\]\""\w]+)", RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.IgnoreCase); + + public static string BuildPaging(long skip, long take, SQLParts parts, ref object[] args) + { + parts.sqlOrderBy = string.IsNullOrEmpty(parts.sqlOrderBy) ? null : OrderByAlias.Replace(parts.sqlOrderBy, "$1"); + var sqlPage = string.Format("SELECT {4} FROM (SELECT poco_base.*, ROW_NUMBER() OVER ({0}) poco_rn \nFROM ( \n{1}) poco_base ) poco_paged \nWHERE poco_rn > @{2} AND poco_rn <= @{3} \nORDER BY poco_rn", + parts.sqlOrderBy ?? "ORDER BY (SELECT NULL /*poco_dual*/)", parts.sqlUnordered, args.Length, args.Length + 1, parts.sqlColumns); + args = args.Concat(new object[] { skip, skip + take }).ToArray(); + + return sqlPage; + } } } diff --git a/src/NPoco/PocoDataFactory.cs b/src/NPoco/PocoDataFactory.cs index 48285c0c..31e917b5 100644 --- a/src/NPoco/PocoDataFactory.cs +++ b/src/NPoco/PocoDataFactory.cs @@ -95,7 +95,6 @@ private InitializedPocoDataBuilder BaseClassFalbackPocoDataBuilder(Type type) public static PocoData ForObjectStatic(object o, string primaryKeyName, bool autoIncrement, Func fallback) { var t = o.GetType(); -#if !NET35 if (t == typeof (System.Dynamic.ExpandoObject) || t == typeof (PocoExpando)) { var pd = new PocoData @@ -122,16 +121,13 @@ public static PocoData ForObjectStatic(object o, string primaryKeyName, bool aut return pd; } else -#endif return fallback(t); } public static void Guard(Type type) { -#if !NET35 if (type == typeof(System.Dynamic.ExpandoObject) || type == typeof(PocoExpando)) throw new InvalidOperationException("Can't use dynamic types with this method"); -#endif } } diff --git a/src/NPoco/PocoExpando.cs b/src/NPoco/PocoExpando.cs index 43a3cb3b..d78623b6 100644 --- a/src/NPoco/PocoExpando.cs +++ b/src/NPoco/PocoExpando.cs @@ -4,7 +4,6 @@ namespace NPoco { - #if !NET35 public class PocoExpando : System.Dynamic.DynamicObject, IDictionary, IDictionary { private readonly IDictionary Dictionary = @@ -17,7 +16,7 @@ public void Add(KeyValuePair item) public bool Contains(object key) { - return ((IDictionary) Dictionary).Contains(key); + return ((IDictionary)Dictionary).Contains(key); } public void Add(object key, object value) @@ -32,7 +31,7 @@ public void Clear() IDictionaryEnumerator IDictionary.GetEnumerator() { - return ((IDictionary) Dictionary).GetEnumerator(); + return ((IDictionary)Dictionary).GetEnumerator(); } public void Remove(object key) @@ -71,8 +70,8 @@ public int Count get { return this.Dictionary.Keys.Count; } } - public object SyncRoot => ((IDictionary) Dictionary).SyncRoot; - public bool IsSynchronized => ((IDictionary) Dictionary).IsSynchronized; + public object SyncRoot => ((IDictionary)Dictionary).SyncRoot; + public bool IsSynchronized => ((IDictionary)Dictionary).IsSynchronized; ICollection IDictionary.Values => ((IDictionary)Dictionary).Values; @@ -171,5 +170,4 @@ public ICollection Values get { return Dictionary.Values; } } } - #endif } \ No newline at end of file diff --git a/src/NPoco/Properties/AssemblyInfo.cs b/src/NPoco/Properties/AssemblyInfo.cs index 36eace82..3a237cca 100644 --- a/src/NPoco/Properties/AssemblyInfo.cs +++ b/src/NPoco/Properties/AssemblyInfo.cs @@ -22,4 +22,5 @@ // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("56481bba-35a1-4061-9fe8-0ed9e1b6a0a3")] -[assembly: InternalsVisibleTo("NPoco.Tests")] \ No newline at end of file +[assembly: InternalsVisibleTo("NPoco.Tests")] +[assembly: InternalsVisibleTo("NPoco.SqlServer")] \ No newline at end of file diff --git a/src/NPoco/ReflectionUtils.cs b/src/NPoco/ReflectionUtils.cs index 89b16655..78834b19 100644 --- a/src/NPoco/ReflectionUtils.cs +++ b/src/NPoco/ReflectionUtils.cs @@ -54,14 +54,9 @@ public static Type GetMemberInfoType(this MemberInfo member) public static bool IsDynamic(this MemberInfo member) { -#if !NET35 return member.GetCustomAttributes(typeof(DynamicAttribute), true).Any(); -#else - return false; -#endif } - public static bool IsField(this MemberInfo member) { return member is FieldInfo; @@ -150,23 +145,13 @@ public static bool IsOfGenericType(this Type instanceType, Type genericType) public static IEnumerable GetCustomAttributes(MemberInfo memberInfo) { -#if NET35 || NET40 - var attrs = Attribute.GetCustomAttributes(memberInfo); -#else var attrs = memberInfo.GetCustomAttributes(); -#endif - return attrs; } public static IEnumerable GetCustomAttributes(MemberInfo memberInfo, Type type) { -#if NET35 || NET40 - var attrs = Attribute.GetCustomAttributes(memberInfo, type); -#else var attrs = memberInfo.GetCustomAttributes(type); -#endif - return attrs; } } diff --git a/src/NPoco/RowMappers/DictionaryMapper.cs b/src/NPoco/RowMappers/DictionaryMapper.cs index 4f0fe395..cc2ac0d0 100644 --- a/src/NPoco/RowMappers/DictionaryMapper.cs +++ b/src/NPoco/RowMappers/DictionaryMapper.cs @@ -25,10 +25,8 @@ public override object Map(DbDataReader dataReader, RowMapperContext context) { IDictionary target = new Dictionary(StringComparer.OrdinalIgnoreCase); -#if !NET35 if (context.Type == typeof(object)) target = new PocoExpando(); -#endif for (int i = 0; i < _posNames.Length; i++) { diff --git a/src/NPoco/RowMappers/DynamicMember.cs b/src/NPoco/RowMappers/DynamicMember.cs deleted file mode 100644 index 258b1c93..00000000 --- a/src/NPoco/RowMappers/DynamicMember.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; - -namespace NPoco.RowMappers -{ -// public class DynamicMember : MemberInfo -// { -// public static DynamicMember Create(string name) -// { -// var member = new DynamicMember(); -// member.SetName(name); -// return member; -// } - -// private string _name; - -// public void SetName(string name) -// { -// _name = name; -// } - -// public override string Name { get { return _name; } } -// public override Type DeclaringType { get { return typeof(IDictionary); } } -// public Type DynamicType { get { return typeof(object); } } - -// public object GetValue(object target) -// { -// object val; -// ((IDictionary)target).TryGetValue(Name, out val); -// return val; -// } - -//#if !DNXCORE50 -// public override Type ReflectedType { get { return DynamicType; } } - -// public override bool IsDefined(Type attributeType, bool inherit) -// { -// throw new NotImplementedException(); -// } - -// public override MemberTypes MemberType { get { return MemberTypes.Custom; } } - -// public override object[] GetCustomAttributes(bool inherit) -// { -// throw new NotImplementedException(); -// } - -// public override object[] GetCustomAttributes(Type attributeType, bool inherit) -// { -// throw new NotImplementedException(); -// } -//#endif -// } -} \ No newline at end of file diff --git a/src/NPoco/Singleton.cs b/src/NPoco/Singleton.cs index 26bb21fc..850f5b4b 100644 --- a/src/NPoco/Singleton.cs +++ b/src/NPoco/Singleton.cs @@ -8,5 +8,28 @@ namespace NPoco static class Singleton where T : new() { public static T Instance = new T(); + + } + + class DynamicDatabaseType + { + public static Cache cache = Cache.CreateStaticCache(); + + public static DatabaseType MakeSqlServerType(string type) + { + try + { + return cache.Get(type, () => + { + var newType = Type.GetType($"NPoco.DatabaseTypes.{type}, NPoco.SqlServer"); + var gen = typeof(Singleton<>).MakeGenericType(newType); + return (DatabaseType)gen.GetField("Instance").GetValue(null); + }); + } + catch (Exception ex) + { + throw new Exception($"No database type found for the type string specified: '{type}'. Make sure the relevant assembly NPoco.SqlServer is referenced.", ex); + } + } } } diff --git a/src/NPoco/Sql.cs b/src/NPoco/Sql.cs index d5574af3..b4bb8e1c 100644 --- a/src/NPoco/Sql.cs +++ b/src/NPoco/Sql.cs @@ -1,9 +1,6 @@ using System; using System.Collections.Generic; using System.Text; -#if NET35 -using System.Linq; -#endif namespace NPoco { @@ -134,41 +131,25 @@ public Sql Where(string sql, params object[] args) public Sql OrderBy(params object[] columns) { -#if NET35 - Append("ORDER BY " + string.Join(",", columns.Select(x => x.ToString()).ToArray())); -#else Append("ORDER BY " + string.Join(", ", columns)); -#endif return this; } public Sql Select(params object[] columns) { -#if NET35 - Append("SELECT " + string.Join(", ", columns.Select(x => x.ToString()).ToArray())); -#else Append("SELECT " + string.Join(", ", columns)); -#endif return this; } public Sql From(params object[] tables) { -#if NET35 - Append("FROM " + string.Join(", ", tables.Select(x => x.ToString()).ToArray())); -#else Append("FROM " + string.Join(", ", tables)); -#endif return this; } public Sql GroupBy(params object[] columns) { -#if NET35 - Append("GROUP BY " + string.Join(", ", columns.Select(x => x.ToString()).ToArray())); -#else Append("GROUP BY " + string.Join(", ", columns)); -#endif return this; } diff --git a/src/NPoco/Tuple.cs b/src/NPoco/Tuple.cs deleted file mode 100644 index 1e0b3068..00000000 --- a/src/NPoco/Tuple.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace NPoco -{ - #if NET35 - public class Tuple - { - public T1 Item1 { get; set; } - public T2 Item2 { get; set; } - public Tuple(T1 item1, T2 item2) { Item1 = item1; Item2 = item2; } - public Tuple() { } - } - - public class Tuple - { - public T1 Item1 { get; set; } - public T2 Item2 { get; set; } - public T3 Item3 { get; set; } - public Tuple(T1 item1, T2 item2, T3 item3) { Item1 = item1; Item2 = item2; Item3 = item3; } - public Tuple() { } - } - - public class Tuple - { - public T1 Item1 { get; set; } - public T2 Item2 { get; set; } - public T3 Item3 { get; set; } - public T4 Item4 { get; set; } - public Tuple(T1 item1, T2 item2, T3 item3, T4 item4) { Item1 = item1; Item2 = item2; Item3 = item3; Item4 = item4; } - public Tuple() { } - } - #endif -} diff --git a/src/NPoco/TypeHelpers.cs b/src/NPoco/TypeHelpers.cs index c1946b1c..3cb3f21e 100644 --- a/src/NPoco/TypeHelpers.cs +++ b/src/NPoco/TypeHelpers.cs @@ -27,12 +27,5 @@ public static bool IsAClass(this Type type) { return type != typeof(Type) && !type.GetTypeInfo().IsValueType && (type.GetTypeInfo().IsClass || type.GetTypeInfo().IsInterface) && type != typeof (string) && type != typeof(object) && !type.IsArray; } - -#if NET40 || NET35 - public static Type GetTypeInfo(this Type type) - { - return type; - } -#endif } } diff --git a/test/NPoco.Tests/Common/BaseDBDecoratedTest.cs b/test/NPoco.Tests/Common/BaseDBDecoratedTest.cs index f0acc205..10e22b84 100644 --- a/test/NPoco.Tests/Common/BaseDBDecoratedTest.cs +++ b/test/NPoco.Tests/Common/BaseDBDecoratedTest.cs @@ -1,10 +1,8 @@ using System; using System.Collections.Generic; using System.Data; -using System.Data.SqlClient; -#if !DNXCORE50 +using Microsoft.Data.SqlClient; using FirebirdSql.Data.FirebirdClient; -#endif using NPoco; using NPoco.DatabaseTypes; using NPoco.Tests.NewMapper.Models; @@ -51,13 +49,10 @@ public void SetUp() case 7: // Postgres Assert.Fail("Database platform not supported for unit testing"); return; -#if !DNXCORE50 case 8: // Firebird TestDatabase = new FirebirdDatabase(); Database = new Database(TestDatabase.Connection, new FirebirdDatabaseType(), IsolationLevel.ReadUncommitted); break; -#endif - default: Assert.Fail("Unknown database platform specified"); return; diff --git a/test/NPoco.Tests/Common/BaseDBFluentTest.cs b/test/NPoco.Tests/Common/BaseDBFluentTest.cs index a566b3c7..a6b15168 100644 --- a/test/NPoco.Tests/Common/BaseDBFluentTest.cs +++ b/test/NPoco.Tests/Common/BaseDBFluentTest.cs @@ -1,11 +1,9 @@ using System; using System.Collections.Generic; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using System.Linq; using System.Reflection; -#if !DNXCORE50 using FirebirdSql.Data.FirebirdClient; -#endif using NPoco.DatabaseTypes; using NPoco.FluentMappings; using NPoco.Tests.FluentMappings; @@ -63,7 +61,7 @@ public void SetUp() case 3: // SQL Server var dataSource = configuration.GetSection("TestDbDataSource").Value; TestDatabase = new SQLLocalDatabase(dataSource); - Database = dbFactory.Build(new Database(TestDatabase.Connection, new SqlServer2008DatabaseType())); + Database = dbFactory.Build(new Database(TestDatabase.Connection, new NPoco.DatabaseTypes.SqlServer2008DatabaseType())); break; case 4: // SQL CE @@ -72,15 +70,12 @@ public void SetUp() case 7: // Postgres Assert.Fail("Database platform not supported for unit testing"); return; -#if !DNXCORE50 case 8: // Firebird TestDatabase = new FirebirdDatabase(); var db = new Database(TestDatabase.Connection, new FirebirdDatabaseType()); db.Mappers.Insert(0, new FirebirdDefaultMapper()); Database = dbFactory.Build(db); break; -#endif - default: Assert.Fail("Unknown database platform specified"); return; diff --git a/test/NPoco.Tests/Common/FirebirdDatabase.cs b/test/NPoco.Tests/Common/FirebirdDatabase.cs index 69ea1e97..8bc8c030 100644 --- a/test/NPoco.Tests/Common/FirebirdDatabase.cs +++ b/test/NPoco.Tests/Common/FirebirdDatabase.cs @@ -1,5 +1,4 @@ -#if !DNXCORE50 -using System; +using System; using System.Data; using System.IO; using System.Threading; @@ -259,4 +258,3 @@ public override void CleanupDataBase() } } } -#endif \ No newline at end of file diff --git a/test/NPoco.Tests/Common/SQLLocalDatabase.cs b/test/NPoco.Tests/Common/SQLLocalDatabase.cs index c43072c1..9ca882e5 100644 --- a/test/NPoco.Tests/Common/SQLLocalDatabase.cs +++ b/test/NPoco.Tests/Common/SQLLocalDatabase.cs @@ -1,6 +1,6 @@ using System; using System.Data; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using System.IO; using NPoco.DatabaseTypes; @@ -27,19 +27,17 @@ public SQLLocalDatabase(string dataSource) ConnectionStringBase = String.Format("Data Source={0};Integrated Security=True;", dataSource); ConnectionString = String.Format("{0}AttachDbFileName=\"{1}\";", ConnectionStringBase, FQDBFile); - ProviderName = "System.Data.SqlClient"; + ProviderName = "Microsoft.Data.SqlClient"; RecreateDataBase(); EnsureSharedConnectionConfigured(); // Console.WriteLine("Tables (Constructor): " + Environment.NewLine); -//#if !DNXCORE50 // var dt = ((SqlConnection)Connection).GetSchema("Tables"); // foreach (DataRow row in dt.Rows) // { // Console.WriteLine((string)row[2]); // } -//#endif } public override void EnsureSharedConnectionConfigured() @@ -222,13 +220,11 @@ select @Name cmd.ExecuteNonQuery(); // Console.WriteLine("Tables (CreateDB): " + Environment.NewLine); - //#if !DNXCORE50 // var dt = conn.GetSchema("Tables"); // foreach (DataRow row in dt.Rows) // { // Console.WriteLine(row[2]); // } - //#endif cmd.Dispose(); conn.Close(); diff --git a/test/NPoco.Tests/Common/TestDescriptor.cs b/test/NPoco.Tests/Common/TestDescriptor.cs index 9024c974..747490fe 100644 --- a/test/NPoco.Tests/Common/TestDescriptor.cs +++ b/test/NPoco.Tests/Common/TestDescriptor.cs @@ -8,11 +8,7 @@ public class TestDescriptor : Attribute, ITestAction { public void BeforeTest(ITest test) { -#if DNXCORE50 - Console.WriteLine("Executing {0}.{1}...", test.Method.MethodInfo.DeclaringType.Name, test.Name); -#else Console.Write("Executing..."); -#endif } public void AfterTest(ITest test) diff --git a/test/NPoco.Tests/ConstructorTests.cs b/test/NPoco.Tests/ConstructorTests.cs index ae0f4ac4..b9056d7b 100644 --- a/test/NPoco.Tests/ConstructorTests.cs +++ b/test/NPoco.Tests/ConstructorTests.cs @@ -1,6 +1,6 @@ using System; using System.Data; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using NPoco.DatabaseTypes; using NPoco.Tests.Common; using NUnit.Framework; @@ -38,12 +38,9 @@ public void SetUp() case 7: // Postgres Assert.Fail("Database platform not supported for unit testing"); return; -#if !DNXCORE50 case 8: // Firebird TestDatabase = new FirebirdDatabase(); break; -#endif - default: Assert.Fail("Unknown database platform specified: " + testDBType); return; @@ -221,7 +218,7 @@ public void ConstructorWithConnectionStringAndProviderName() Assert.IsNull(db.Connection); db.Dispose(); - Assert.IsNull(db.Connection); + Assert.IsNull(db.Connection); } [Test] diff --git a/test/NPoco.Tests/DatabaseFactoryTests.cs b/test/NPoco.Tests/DatabaseFactoryTests.cs index 280f8cba..309717d3 100644 --- a/test/NPoco.Tests/DatabaseFactoryTests.cs +++ b/test/NPoco.Tests/DatabaseFactoryTests.cs @@ -1,4 +1,4 @@ -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using NPoco; using NPoco.FluentMappings; using NUnit.Framework; diff --git a/test/NPoco.Tests/DecoratedTests/CRUDTests/UpdateTests.cs b/test/NPoco.Tests/DecoratedTests/CRUDTests/UpdateTests.cs index b50b6871..77ae9696 100644 --- a/test/NPoco.Tests/DecoratedTests/CRUDTests/UpdateTests.cs +++ b/test/NPoco.Tests/DecoratedTests/CRUDTests/UpdateTests.cs @@ -103,7 +103,7 @@ public void UpdatePrimaryKeyVersionConcurrencyException() Database.Update(poco1); poco2.Age = 200; - + Assert.Throws(() => Database.Update(poco2)); } @@ -168,7 +168,7 @@ public void UpdateBatchTest() foreach (var u in result) { Assert.AreEqual(30, u.Age); - } + } Assert.AreEqual(14, updated); } } diff --git a/test/NPoco.Tests/DecoratedTests/QueryTests/FetchAndQueryDecoratedTests.cs b/test/NPoco.Tests/DecoratedTests/QueryTests/FetchAndQueryDecoratedTests.cs index e5e341dd..7f5cc0d2 100644 --- a/test/NPoco.Tests/DecoratedTests/QueryTests/FetchAndQueryDecoratedTests.cs +++ b/test/NPoco.Tests/DecoratedTests/QueryTests/FetchAndQueryDecoratedTests.cs @@ -3,7 +3,7 @@ using NPoco.Tests.Common; using NUnit.Framework; using System.Data; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; namespace NPoco.Tests.DecoratedTests.QueryTests { diff --git a/test/NPoco.Tests/DecoratedTests/TransactionDecoratedTests.cs b/test/NPoco.Tests/DecoratedTests/TransactionDecoratedTests.cs index c02439a9..acb2b4ae 100644 --- a/test/NPoco.Tests/DecoratedTests/TransactionDecoratedTests.cs +++ b/test/NPoco.Tests/DecoratedTests/TransactionDecoratedTests.cs @@ -1,5 +1,5 @@ using System; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using NPoco; using NPoco.Tests.Common; using NUnit.Framework; diff --git a/test/NPoco.Tests/FluentTests/QueryTests/QueryProviderTests.cs b/test/NPoco.Tests/FluentTests/QueryTests/QueryProviderTests.cs index c11fe170..64c89e27 100644 --- a/test/NPoco.Tests/FluentTests/QueryTests/QueryProviderTests.cs +++ b/test/NPoco.Tests/FluentTests/QueryTests/QueryProviderTests.cs @@ -341,14 +341,7 @@ public void QueryWithIncludeAndNestedOrderBy() private static void SetCurrentCulture(CultureInfo culture) { -#if NET35 || NET40 || NET45 || NET451 || NET452 || NET462 || DNX451 || DNX452 - // In the .NET Framework 4.5.2 and earlier versions, the CurrentCulture property is read-only - Thread.CurrentThread.CurrentCulture = culture; -#else - // Starting with the .NET Framework 4.6, the CurrentCulture property is read-write - // and Core does not have Thread.CurrentThread? CultureInfo.CurrentCulture = culture; -#endif } [Test] @@ -610,7 +603,7 @@ public class Usersss //Module Module1 // Sub Main() -// Dim Db = New Database("asdf", "System.Data.SqlClient") +// Dim Db = New Database("asdf", "Microsoft.Data.SqlClient") // Dim exp = New DefaultSqlExpression (Of User)(Db) // Dim whered = exp.Where(Function(item) (item.Name = "Test")) // Console.WriteLine(whered.Context.ToSelectStatement()) diff --git a/test/NPoco.Tests/FormatCommandTest.cs b/test/NPoco.Tests/FormatCommandTest.cs index 6a9ea4ca..dc13c410 100644 --- a/test/NPoco.Tests/FormatCommandTest.cs +++ b/test/NPoco.Tests/FormatCommandTest.cs @@ -1,6 +1,6 @@ using System.Data; -using System.Data.SqlClient; -using NPoco; +using Microsoft.Data.SqlClient; +using NPoco.DatabaseTypes; using NUnit.Framework; namespace NPoco.Tests @@ -33,7 +33,7 @@ public void FormattingWithStringValue() public class MyDb : Database { public MyDb() - : base("test", DatabaseType.SqlServer2008, SqlClientFactory.Instance) + : base("test", new SqlServerDatabaseType(), SqlClientFactory.Instance) { } } diff --git a/test/NPoco.Tests/NPoco.Tests.csproj b/test/NPoco.Tests/NPoco.Tests.csproj index 628aa079..7a77c6dc 100644 --- a/test/NPoco.Tests/NPoco.Tests.csproj +++ b/test/NPoco.Tests/NPoco.Tests.csproj @@ -1,68 +1,39 @@  - - NPoco Tests - 3.4.0 - netcoreapp2.1;net471 - NPoco.Tests - Library - NPoco.Tests - $(PackageTargetFallback);dnxcore50 - 1.1.1 - false - false - false - false - false - false - - - - - - - - - PreserveNewest - - - - - - - - - - - - - - - - - - - - - - - - - - $(DefineConstants);DNXCORE50 - - - - - - - - - - - - - - + + NPoco Tests + 5.0.0 + netcoreapp3.1 + NPoco.Tests + Library + NPoco.Tests + false + false + false + false + false + false + + + + + PreserveNewest + + + + + + + + + + + + + + + + + diff --git a/test/NPoco.Tests/NewMapper/FakeReader.cs b/test/NPoco.Tests/NewMapper/FakeReader.cs index 621518c6..22a0bffd 100644 --- a/test/NPoco.Tests/NewMapper/FakeReader.cs +++ b/test/NPoco.Tests/NewMapper/FakeReader.cs @@ -7,7 +7,6 @@ namespace NPoco.Tests.NewMapper { public class FakeReader : DbDataReader { -#if !DNXCORE50 public override void Close() { throw new NotImplementedException(); @@ -17,7 +16,6 @@ public override DataTable GetSchemaTable() { throw new NotImplementedException(); } -#endif public override bool NextResult() { diff --git a/test/NPoco.Tests/SnapshotterTests.cs b/test/NPoco.Tests/SnapshotterTests.cs index 4bfc9e79..a31bf657 100644 --- a/test/NPoco.Tests/SnapshotterTests.cs +++ b/test/NPoco.Tests/SnapshotterTests.cs @@ -1,10 +1,11 @@ using System; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using System.Linq; using NPoco; using NPoco.FluentMappings; using NPoco.Tests.Common; using NUnit.Framework; +using NPoco.DatabaseTypes; namespace NPoco.Tests { @@ -19,7 +20,7 @@ public void Setup() var dbfactory = new DatabaseFactory(); dbfactory .Config() - .UsingDatabase(() => new Database("", DatabaseType.SqlServer2012, SqlClientFactory.Instance)) + .UsingDatabase(() => new Database("", new SqlServer2012DatabaseType(), SqlClientFactory.Instance)) .WithFluentConfig(FluentMappingConfiguration.Configure(new MyMappings())); _database = dbfactory.GetDatabase(); From 41c0cc6a488bb7e99d1940cbafca5ed280874776 Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Sat, 26 Sep 2020 23:46:32 +1000 Subject: [PATCH 02/42] Add transient error retry hooks for SQL Server --- src/NPoco.SqlServer/DefaultPollyPolicy.cs | 33 +++++ src/NPoco.SqlServer/IPollyPolicy.cs | 10 ++ src/NPoco.SqlServer/NPoco.SqlServer.csproj | 1 + src/NPoco.SqlServer/SqlServerDatabase.cs | 40 ++++-- .../SqlServerTransientExceptionDetector.cs | 132 ++++++++++++++++++ src/NPoco/AsyncDatabase.cs | 6 +- src/NPoco/Database.cs | 28 ++-- 7 files changed, 228 insertions(+), 22 deletions(-) create mode 100644 src/NPoco.SqlServer/DefaultPollyPolicy.cs create mode 100644 src/NPoco.SqlServer/IPollyPolicy.cs create mode 100644 src/NPoco.SqlServer/SqlServerTransientExceptionDetector.cs diff --git a/src/NPoco.SqlServer/DefaultPollyPolicy.cs b/src/NPoco.SqlServer/DefaultPollyPolicy.cs new file mode 100644 index 00000000..53fba5f7 --- /dev/null +++ b/src/NPoco.SqlServer/DefaultPollyPolicy.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using Microsoft.Data.SqlClient; +using NPoco.SqlServer; +using Polly; +using Polly.Retry; + +namespace NPoco.DatabaseTypes +{ + public class DefaultPollyPolicy : IPollyPolicy + { + public virtual RetryPolicy RetryPolicy { get; set; } = RetryPolicyImp; + + public virtual AsyncRetryPolicy AsyncRetryPolicy { get; set; } = RetryPolicyAsyncImp; + + public static IEnumerable RetryTimes { get; set; } = new[] + { + TimeSpan.FromSeconds(1), + TimeSpan.FromSeconds(2), + TimeSpan.FromSeconds(3) + }; + + private static readonly AsyncRetryPolicy RetryPolicyAsyncImp = Policy + .Handle(SqlServerTransientExceptionDetector.ShouldRetryOn) + .Or() + .WaitAndRetryAsync(RetryTimes); + + private static readonly RetryPolicy RetryPolicyImp = Policy + .Handle(SqlServerTransientExceptionDetector.ShouldRetryOn) + .Or() + .WaitAndRetry(RetryTimes); + } +} \ No newline at end of file diff --git a/src/NPoco.SqlServer/IPollyPolicy.cs b/src/NPoco.SqlServer/IPollyPolicy.cs new file mode 100644 index 00000000..56be850a --- /dev/null +++ b/src/NPoco.SqlServer/IPollyPolicy.cs @@ -0,0 +1,10 @@ +using Polly.Retry; + +namespace NPoco.SqlServer +{ + public interface IPollyPolicy + { + RetryPolicy RetryPolicy { get; set; } + AsyncRetryPolicy AsyncRetryPolicy { get; set; } + } +} \ No newline at end of file diff --git a/src/NPoco.SqlServer/NPoco.SqlServer.csproj b/src/NPoco.SqlServer/NPoco.SqlServer.csproj index 768024e5..ca9ae89d 100644 --- a/src/NPoco.SqlServer/NPoco.SqlServer.csproj +++ b/src/NPoco.SqlServer/NPoco.SqlServer.csproj @@ -15,6 +15,7 @@ + diff --git a/src/NPoco.SqlServer/SqlServerDatabase.cs b/src/NPoco.SqlServer/SqlServerDatabase.cs index e89cf94e..154cb9a4 100644 --- a/src/NPoco.SqlServer/SqlServerDatabase.cs +++ b/src/NPoco.SqlServer/SqlServerDatabase.cs @@ -1,20 +1,44 @@ -using System; -using System.Collections.Generic; -using System.Data.Common; -using System.Text; +using Microsoft.Data.SqlClient; +using NPoco.SqlServer; +using System; +using System.Threading.Tasks; +// ReSharper disable once CheckNamespace namespace NPoco.DatabaseTypes { public class SqlServerDatabase : Database { - public SqlServerDatabase(string connectionString) - : base(connectionString, new SqlServer2012DatabaseType(), Microsoft.Data.SqlClient.SqlClientFactory.Instance) + private readonly IPollyPolicy _pollyPolicy; + + public SqlServerDatabase(string connectionString, IPollyPolicy pollyPolicy = null) + : this(connectionString, new SqlServer2012DatabaseType(), pollyPolicy) { } - public SqlServerDatabase(string connectionString, SqlServerDatabaseType databaseType) - : base(connectionString, databaseType, Microsoft.Data.SqlClient.SqlClientFactory.Instance) + public SqlServerDatabase(string connectionString, SqlServerDatabaseType databaseType, IPollyPolicy pollyPolicy) + : base(connectionString, databaseType, SqlClientFactory.Instance) { + _pollyPolicy = pollyPolicy; + } + + protected override T ExecutionHook(Func action) + { + if (_pollyPolicy?.RetryPolicy != null) + { + return _pollyPolicy.RetryPolicy.Execute(action); + } + + return base.ExecutionHook(action); + } + + protected override async ValueTask ExecutionHookAsync(Func> action) + { + if (_pollyPolicy?.AsyncRetryPolicy != null) + { + return await _pollyPolicy.AsyncRetryPolicy.ExecuteAsync(action); + } + + return await base.ExecutionHookAsync(action); } } } diff --git a/src/NPoco.SqlServer/SqlServerTransientExceptionDetector.cs b/src/NPoco.SqlServer/SqlServerTransientExceptionDetector.cs new file mode 100644 index 00000000..70faf5b6 --- /dev/null +++ b/src/NPoco.SqlServer/SqlServerTransientExceptionDetector.cs @@ -0,0 +1,132 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.Data.SqlClient; + +namespace NPoco.SqlServer +{ + /// + /// Detects the exceptions caused by SQL Server transient failures. + /// + public static class SqlServerTransientExceptionDetector + { + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static bool ShouldRetryOn(Exception ex) + { + if (ex is SqlException sqlException) + { + foreach (SqlError err in sqlException.Errors) + { + switch (err.Number) + { + // SQL Error Code: 49920 + // Cannot process request. Too many operations in progress for subscription "%ld". + // The service is busy processing multiple requests for this subscription. + // Requests are currently blocked for resource optimization. Query sys.dm_operation_status for operation status. + // Wait until pending requests are complete or delete one of your pending requests and retry your request later. + case 49920: + // SQL Error Code: 49919 + // Cannot process create or update request. Too many create or update operations in progress for subscription "%ld". + // The service is busy processing multiple create or update requests for your subscription or server. + // Requests are currently blocked for resource optimization. Query sys.dm_operation_status for pending operations. + // Wait till pending create or update requests are complete or delete one of your pending requests and + // retry your request later. + case 49919: + // SQL Error Code: 49918 + // Cannot process request. Not enough resources to process request. + // The service is currently busy.Please retry the request later. + case 49918: + // SQL Error Code: 41839 + // Transaction exceeded the maximum number of commit dependencies. + case 41839: + // SQL Error Code: 41325 + // The current transaction failed to commit due to a serializable validation failure. + case 41325: + // SQL Error Code: 41305 + // The current transaction failed to commit due to a repeatable read validation failure. + case 41305: + // SQL Error Code: 41302 + // The current transaction attempted to update a record that has been updated since the transaction started. + case 41302: + // SQL Error Code: 41301 + // Dependency failure: a dependency was taken on another transaction that later failed to commit. + case 41301: + // SQL Error Code: 40613 + // Database XXXX on server YYYY is not currently available. Please retry the connection later. + // If the problem persists, contact customer support, and provide them the session tracing ID of ZZZZZ. + case 40613: + // SQL Error Code: 40501 + // The service is currently busy. Retry the request after 10 seconds. Code: (reason code to be decoded). + case 40501: + // SQL Error Code: 40197 + // The service has encountered an error processing your request. Please try again. + case 40197: + // SQL Error Code: 10936 + // Resource ID : %d. The request limit for the elastic pool is %d and has been reached. + // See 'http://go.microsoft.com/fwlink/?LinkId=267637' for assistance. + case 10936: + // SQL Error Code: 10929 + // Resource ID: %d. The %s minimum guarantee is %d, maximum limit is %d and the current usage for the database is %d. + // However, the server is currently too busy to support requests greater than %d for this database. + // For more information, see http://go.microsoft.com/fwlink/?LinkId=267637. Otherwise, please try again. + case 10929: + // SQL Error Code: 10928 + // Resource ID: %d. The %s limit for the database is %d and has been reached. For more information, + // see http://go.microsoft.com/fwlink/?LinkId=267637. + case 10928: + // SQL Error Code: 10060 + // A network-related or instance-specific error occurred while establishing a connection to SQL Server. + // The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server + // is configured to allow remote connections. (provider: TCP Provider, error: 0 - A connection attempt failed + // because the connected party did not properly respond after a period of time, or established connection failed + // because connected host has failed to respond.)"} + case 10060: + // SQL Error Code: 10054 + // A transport-level error has occurred when sending the request to the server. + // (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by the remote host.) + case 10054: + // SQL Error Code: 10053 + // A transport-level error has occurred when receiving results from the server. + // An established connection was aborted by the software in your host machine. + case 10053: + // SQL Error Code: 1205 + // Deadlock + case 1205: + // SQL Error Code: 233 + // The client was unable to establish a connection because of an error during connection initialization process before login. + // Possible causes include the following: the client tried to connect to an unsupported version of SQL Server; + // the server was too busy to accept new connections; or there was a resource limitation (insufficient memory or maximum + // allowed connections) on the server. (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by + // the remote host.) + case 233: + // SQL Error Code: 121 + // The semaphore timeout period has expired + case 121: + // SQL Error Code: 64 + // A connection was successfully established with the server, but then an error occurred during the login process. + // (provider: TCP Provider, error: 0 - The specified network name is no longer available.) + case 64: + // DBNETLIB Error Code: 20 + // The instance of SQL Server you attempted to connect to does not support encryption. + case 20: + return true; + // This exception can be thrown even if the operation completed successfully, so it's safer to let the application fail. + // DBNETLIB Error Code: -2 + // Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding. The statement has been terminated. + //case -2: + } + } + + return false; + } + + return ex is TimeoutException; + } + } +} \ No newline at end of file diff --git a/src/NPoco/AsyncDatabase.cs b/src/NPoco/AsyncDatabase.cs index 6aa1ba2b..1925334a 100644 --- a/src/NPoco/AsyncDatabase.cs +++ b/src/NPoco/AsyncDatabase.cs @@ -466,7 +466,7 @@ public async Task ExecuteScalarAsync(Sql Sql) internal async Task ExecuteNonQueryHelperAsync(DbCommand cmd) { DoPreExecute(cmd); - var result = await _dbType.ExecuteNonQueryAsync(this, cmd); + var result = await ExecutionHookAsync(() => _dbType.ExecuteNonQueryAsync(this, cmd)); OnExecutedCommandInternal(cmd); return result; } @@ -474,7 +474,7 @@ internal async Task ExecuteNonQueryHelperAsync(DbCommand cmd) internal async Task ExecuteScalarHelperAsync(DbCommand cmd) { DoPreExecute(cmd); - var result = await _dbType.ExecuteScalarAsync(this, cmd); + var result = await ExecutionHookAsync(() => _dbType.ExecuteScalarAsync(this, cmd)); OnExecutedCommandInternal(cmd); return result; } @@ -482,7 +482,7 @@ internal async Task ExecuteScalarHelperAsync(DbCommand cmd) internal async Task ExecuteReaderHelperAsync(DbCommand cmd) { DoPreExecute(cmd); - var reader = await _dbType.ExecuteReaderAsync(this, cmd); + var reader = await ExecutionHookAsync(() => _dbType.ExecuteReaderAsync(this, cmd)); OnExecutedCommandInternal(cmd); return reader; } diff --git a/src/NPoco/Database.cs b/src/NPoco/Database.cs index b3c0c5d8..4860c640 100644 --- a/src/NPoco/Database.cs +++ b/src/NPoco/Database.cs @@ -1,13 +1,9 @@ -/* NPoco 4.0 - A Tiny ORMish thing for your POCO's. - * Copyright 2011-2015. All Rights Reserved. +/* NPoco 5.0 - A Tiny ORMish thing for your POCO's. + * Copyright 2011-2020. All Rights Reserved. * * Apache License 2.0 - http://www.apache.org/licenses/LICENSE-2.0 * * Originally created by Brad Robinson (@toptensoftware) - * - * Special thanks to Rob Conery (@robconery) for original inspiration (ie:Massive) and for - * use of Subsonic's T4 templates, Rob Sullivan (@DataChomp) for hard core DBA advice - * and Adam Schroder (@schotime) for lots of suggestions, improvements and Oracle support */ using System; @@ -1985,7 +1981,7 @@ public IPocoDataFactory PocoDataFactory internal int ExecuteNonQueryHelper(DbCommand cmd) { DoPreExecute(cmd); - var result = cmd.ExecuteNonQuery(); + var result = ExecutionHook(() => cmd.ExecuteNonQuery()); OnExecutedCommandInternal(cmd); return result; } @@ -1993,17 +1989,27 @@ internal int ExecuteNonQueryHelper(DbCommand cmd) internal object ExecuteScalarHelper(DbCommand cmd) { DoPreExecute(cmd); - object r = cmd.ExecuteScalar(); + var result = ExecutionHook(() => cmd.ExecuteScalar()); OnExecutedCommandInternal(cmd); - return r; + return result; } internal DbDataReader ExecuteReaderHelper(DbCommand cmd) { DoPreExecute(cmd); - DbDataReader r = cmd.ExecuteReader(); + var result = ExecutionHook(() => cmd.ExecuteReader()); OnExecutedCommandInternal(cmd); - return r; + return result; + } + + protected virtual T ExecutionHook(Func action) + { + return action(); + } + + protected virtual async ValueTask ExecutionHookAsync(Func> action) + { + return await action(); } int IDatabaseHelpers.ExecuteNonQueryHelper(DbCommand cmd) => ExecuteNonQueryHelper(cmd); From b498f80728e79ad33fe2039cec05eed82603c616 Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Mon, 28 Sep 2020 10:29:28 +1000 Subject: [PATCH 03/42] Refactor IAsyncDatabase instances and implement BulkInsertAsync --- .../DatabaseTypes/SqlServerDatabaseType.cs | 12 ++ src/NPoco.SqlServer/DefaultPollyPolicy.cs | 3 +- src/NPoco.SqlServer/SqlBulkCopyHelper.cs | 14 +- src/NPoco.SqlServer/SqlServerDatabase.cs | 3 +- src/NPoco/AsyncDatabase.cs | 18 +++ src/NPoco/DatabaseType.cs | 8 + src/NPoco/IAsyncDatabase.cs | 140 +++++++++--------- src/NPoco/IBaseDatabase.cs | 3 +- src/NPoco/IDatabase.cs | 2 +- src/NPoco/IDatabaseQuery.cs | 2 +- 10 files changed, 129 insertions(+), 76 deletions(-) diff --git a/src/NPoco.SqlServer/DatabaseTypes/SqlServerDatabaseType.cs b/src/NPoco.SqlServer/DatabaseTypes/SqlServerDatabaseType.cs index 401b6559..241960ee 100644 --- a/src/NPoco.SqlServer/DatabaseTypes/SqlServerDatabaseType.cs +++ b/src/NPoco.SqlServer/DatabaseTypes/SqlServerDatabaseType.cs @@ -1,8 +1,10 @@ using Microsoft.Data.SqlClient; using System; +using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Threading.Tasks; +using NPoco.SqlServer; namespace NPoco.DatabaseTypes { @@ -72,6 +74,16 @@ public override IsolationLevel GetDefaultTransactionIsolationLevel() return base.LookupDbType(type, name); } + public override void InsertBulk(IDatabase db, IEnumerable pocos) + { + SqlBulkCopyHelper.BulkInsert(db, pocos); + } + + public override Task InsertBulkAsync(IDatabase db, IEnumerable pocos) + { + return SqlBulkCopyHelper.BulkInsertAsync(db, pocos); + } + public override string GetProviderName() { return "Microsoft.Data.SqlClient"; diff --git a/src/NPoco.SqlServer/DefaultPollyPolicy.cs b/src/NPoco.SqlServer/DefaultPollyPolicy.cs index 53fba5f7..50ee985d 100644 --- a/src/NPoco.SqlServer/DefaultPollyPolicy.cs +++ b/src/NPoco.SqlServer/DefaultPollyPolicy.cs @@ -1,11 +1,10 @@ using System; using System.Collections.Generic; using Microsoft.Data.SqlClient; -using NPoco.SqlServer; using Polly; using Polly.Retry; -namespace NPoco.DatabaseTypes +namespace NPoco.SqlServer { public class DefaultPollyPolicy : IPollyPolicy { diff --git a/src/NPoco.SqlServer/SqlBulkCopyHelper.cs b/src/NPoco.SqlServer/SqlBulkCopyHelper.cs index 657a8c43..b6120fd5 100644 --- a/src/NPoco.SqlServer/SqlBulkCopyHelper.cs +++ b/src/NPoco.SqlServer/SqlBulkCopyHelper.cs @@ -2,10 +2,11 @@ using System.Collections.Generic; using System.Data; using System.Data.Common; -using Microsoft.Data.SqlClient; using System.Linq; +using System.Threading.Tasks; +using Microsoft.Data.SqlClient; -namespace NPoco +namespace NPoco.SqlServer { public class SqlBulkCopyHelper { @@ -26,12 +27,17 @@ public static void BulkInsert(IDatabase db, IEnumerable list, SqlBulkCopyO } } - public static async System.Threading.Tasks.Task BulkInsertAsync(IDatabase db, IEnumerable list, SqlBulkCopyOptions sqlBulkCopyOptions) + public static Task BulkInsertAsync(IDatabase db, IEnumerable list) + { + return BulkInsertAsync(db, list, SqlBulkCopyOptions.Default); + } + + public static async Task BulkInsertAsync(IDatabase db, IEnumerable list, SqlBulkCopyOptions sqlBulkCopyOptions) { using (var bulkCopy = new SqlBulkCopy(SqlConnectionResolver(db.Connection), sqlBulkCopyOptions, SqlTransactionResolver(db.Transaction))) { var table = BuildBulkInsertDataTable(db, list, bulkCopy, sqlBulkCopyOptions); - await bulkCopy.WriteToServerAsync(table).ConfigureAwait(false); + await bulkCopy.WriteToServerAsync(table); } } diff --git a/src/NPoco.SqlServer/SqlServerDatabase.cs b/src/NPoco.SqlServer/SqlServerDatabase.cs index 154cb9a4..b0c55ad7 100644 --- a/src/NPoco.SqlServer/SqlServerDatabase.cs +++ b/src/NPoco.SqlServer/SqlServerDatabase.cs @@ -2,9 +2,10 @@ using NPoco.SqlServer; using System; using System.Threading.Tasks; +using NPoco.DatabaseTypes; // ReSharper disable once CheckNamespace -namespace NPoco.DatabaseTypes +namespace NPoco.SqlServer { public class SqlServerDatabase : Database { diff --git a/src/NPoco/AsyncDatabase.cs b/src/NPoco/AsyncDatabase.cs index 1925334a..a8528c4e 100644 --- a/src/NPoco/AsyncDatabase.cs +++ b/src/NPoco/AsyncDatabase.cs @@ -99,6 +99,24 @@ private async Task InsertAsyncImp(PocoData pocoData, string tableName } } + public async Task InsertBulkAsync(IEnumerable pocos) + { + try + { + OpenSharedConnectionInternal(); + await _dbType.InsertBulkAsync(this, pocos); + } + catch (Exception x) + { + OnExceptionInternal(x); + throw; + } + finally + { + CloseSharedConnectionInternal(); + } + } + public async Task InsertBatchAsync(IEnumerable pocos, BatchOptions options = null) { options = options ?? new BatchOptions(); diff --git a/src/NPoco/DatabaseType.cs b/src/NPoco/DatabaseType.cs index 4530d5d6..387cbf5b 100644 --- a/src/NPoco/DatabaseType.cs +++ b/src/NPoco/DatabaseType.cs @@ -239,6 +239,14 @@ public virtual void InsertBulk(IDatabase db, IEnumerable pocos) } } + public virtual async Task InsertBulkAsync(IDatabase db, IEnumerable pocos) + { + foreach (var poco in pocos) + { + await db.InsertAsync(poco); + } + } + /// /// Look at the type and provider name being used and instantiate a suitable DatabaseType instance. /// diff --git a/src/NPoco/IAsyncDatabase.cs b/src/NPoco/IAsyncDatabase.cs index f0dd4e55..3017d14b 100644 --- a/src/NPoco/IAsyncDatabase.cs +++ b/src/NPoco/IAsyncDatabase.cs @@ -7,7 +7,80 @@ namespace NPoco { - public interface IAsyncDatabase : IBaseDatabase + public interface IAsyncDatabase : IAsyncQueryDatabase + { + /// + /// Executes the provided sql and parameters and casts the result to T + /// + Task ExecuteScalarAsync(string sql, params object[] args); + + /// + /// Executes the provided sql and parameters and casts the result to T + /// + Task ExecuteScalarAsync(Sql sql); + + /// + /// Executes the provided sql and parameters + /// + Task ExecuteAsync(string sql, params object[] args); + + /// + /// Executes the provided sql and parameters + /// + Task ExecuteAsync(Sql sql); + + /// + /// Insert POCO into the table by convention or configuration + /// + Task InsertAsync(T poco); + + /// + /// Insert POCO's into database using SqlBulkCopy for SqlServer (other DB's currently fall back to looping each row) + /// + Task InsertBulkAsync(IEnumerable pocos); + + /// + /// Insert POCO's into database by concatenating sql using the provided batch options + /// + Task InsertBatchAsync(IEnumerable pocos, BatchOptions options = null); + + /// + /// Update POCO in the table by convention or configuration + /// + Task UpdateAsync(object poco); + + /// + /// Update POCO in the table by convention or configuration specifying which columns to update + /// + Task UpdateAsync(object poco, IEnumerable columns); + + /// + /// Update POCO in the table by convention or configuration specifying which columns to update + /// + Task UpdateAsync(T poco, Expression> fields); + + /// + /// Update POCO's into database by concatenating sql using the provided batch options + /// + Task UpdateBatchAsync(IEnumerable> pocos, BatchOptions options = null); + + /// + /// Delete POCO from table by convention or configuration + /// + Task DeleteAsync(object poco); + + /// + /// Generate an update statement using a Fluent syntax. Remember to call Execute. + /// + IAsyncUpdateQueryProvider UpdateManyAsync(); + + /// + /// Generate a delete statement using a Fluent syntax. Remember to call Execute. + /// + IAsyncDeleteQueryProvider DeleteManyAsync(); + } + + public interface IAsyncQueryDatabase : IBaseDatabase { /// /// Fetch the only row of type T using the sql and parameters specified @@ -131,70 +204,5 @@ public interface IAsyncDatabase : IBaseDatabase /// The sql provided will be converted so that only the results for the skip and take values specified will be returned. /// Task> SkipTakeAsync(long skip, long take, Sql sql); - - /// - /// Executes the provided sql and parameters and casts the result to T - /// - Task ExecuteScalarAsync(string sql, params object[] args); - - /// - /// Executes the provided sql and parameters and casts the result to T - /// - Task ExecuteScalarAsync(Sql sql); - - /// - /// Executes the provided sql and parameters - /// - Task ExecuteAsync(string sql, params object[] args); - - /// - /// Executes the provided sql and parameters - /// - Task ExecuteAsync(Sql sql); - - /// - /// Insert POCO into the table by convention or configuration - /// - Task InsertAsync(T poco); - - /// - /// Insert POCO's into database by concatenating sql using the provided batch options - /// - Task InsertBatchAsync(IEnumerable pocos, BatchOptions options = null); - - /// - /// Update POCO in the table by convention or configuration - /// - Task UpdateAsync(object poco); - - /// - /// Update POCO in the table by convention or configuration specifying which columns to update - /// - Task UpdateAsync(object poco, IEnumerable columns); - - /// - /// Update POCO in the table by convention or configuration specifying which columns to update - /// - Task UpdateAsync(T poco, Expression> fields); - - /// - /// Update POCO's into database by concatenating sql using the provided batch options - /// - Task UpdateBatchAsync(IEnumerable> pocos, BatchOptions options = null); - - /// - /// Delete POCO from table by convention or configuration - /// - Task DeleteAsync(object poco); - - /// - /// Generate an update statement using a Fluent syntax. Remember to call Execute. - /// - IAsyncUpdateQueryProvider UpdateManyAsync(); - - /// - /// Generate a delete statement using a Fluent syntax. Remember to call Execute. - /// - IAsyncDeleteQueryProvider DeleteManyAsync(); } } diff --git a/src/NPoco/IBaseDatabase.cs b/src/NPoco/IBaseDatabase.cs index bc700c3e..e4a350b0 100644 --- a/src/NPoco/IBaseDatabase.cs +++ b/src/NPoco/IBaseDatabase.cs @@ -1,10 +1,11 @@ +using System; using System.Collections.Generic; using System.Data; using System.Data.Common; namespace NPoco { - public interface IBaseDatabase + public interface IBaseDatabase : IDisposable { /// /// The underlying connection object diff --git a/src/NPoco/IDatabase.cs b/src/NPoco/IDatabase.cs index d58fbf58..6b552778 100644 --- a/src/NPoco/IDatabase.cs +++ b/src/NPoco/IDatabase.cs @@ -6,7 +6,7 @@ namespace NPoco { - public interface IDatabase : IDatabaseQuery, IDatabaseConfig + public interface IDatabase : IAsyncDatabase, IDatabaseQuery, IDatabaseConfig { /// /// Insert POCO into the table, primary key and autoincrement specified diff --git a/src/NPoco/IDatabaseQuery.cs b/src/NPoco/IDatabaseQuery.cs index a1d7caa3..a5d3ade2 100644 --- a/src/NPoco/IDatabaseQuery.cs +++ b/src/NPoco/IDatabaseQuery.cs @@ -8,7 +8,7 @@ namespace NPoco { - public interface IDatabaseQuery : IAsyncDatabase, IDisposable + public interface IDatabaseQuery : IAsyncQueryDatabase { /// /// Builds a paged query from a non-paged query From b085c26f364b344931c6549d75a11c1596816771 Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Mon, 28 Sep 2020 12:08:19 +1000 Subject: [PATCH 04/42] Return ValueTask for async queries --- src/NPoco.SqlServer/DefaultPollyPolicy.cs | 4 +- src/NPoco/AsyncDatabase.cs | 56 +++--- src/NPoco/Database.cs | 20 +-- .../DatabaseTypes/FirebirdDatabaseType.cs | 2 +- src/NPoco/DatabaseTypes/OracleDatabaseType.cs | 2 +- .../DatabaseTypes/PostgreSQLDatabaseType.cs | 2 +- src/NPoco/DatabaseTypes/SQLiteDatabaseType.cs | 2 +- src/NPoco/IAsyncDatabase.cs | 40 ++--- src/NPoco/Linq/SimpleQueryProvider.cs | 160 +++++++++--------- 9 files changed, 139 insertions(+), 149 deletions(-) diff --git a/src/NPoco.SqlServer/DefaultPollyPolicy.cs b/src/NPoco.SqlServer/DefaultPollyPolicy.cs index 50ee985d..24ea699c 100644 --- a/src/NPoco.SqlServer/DefaultPollyPolicy.cs +++ b/src/NPoco.SqlServer/DefaultPollyPolicy.cs @@ -15,8 +15,8 @@ public class DefaultPollyPolicy : IPollyPolicy public static IEnumerable RetryTimes { get; set; } = new[] { TimeSpan.FromSeconds(1), - TimeSpan.FromSeconds(2), - TimeSpan.FromSeconds(3) + TimeSpan.FromSeconds(3), + TimeSpan.FromSeconds(5) }; private static readonly AsyncRetryPolicy RetryPolicyAsyncImp = Policy diff --git a/src/NPoco/AsyncDatabase.cs b/src/NPoco/AsyncDatabase.cs index a8528c4e..9cc0c3a5 100644 --- a/src/NPoco/AsyncDatabase.cs +++ b/src/NPoco/AsyncDatabase.cs @@ -188,7 +188,7 @@ public Task UpdateAsync(object poco, object primaryKeyValue, IEnumerable UpdateAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns) { return UpdateImp (tableName, primaryKeyName, poco, primaryKeyValue, columns, - async (sql, args, next) => next(await ExecuteAsync(sql, args)), TaskAsyncHelper.FromResult(0)); + async (sql, args, next) => next(await ExecuteAsync(sql, args)), Task.FromResult(0)); } public async Task UpdateBatchAsync(IEnumerable> pocos, BatchOptions options = null) @@ -255,7 +255,7 @@ public Task DeleteAsync(string tableName, string primaryKeyName, object poc public virtual Task DeleteAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue) { - return DeleteImp(tableName, primaryKeyName, poco, primaryKeyValue, ExecuteAsync, TaskAsyncHelper.FromResult(0)); + return DeleteImp(tableName, primaryKeyName, poco, primaryKeyValue, ExecuteAsync, Task.FromResult(0)); } public IAsyncDeleteQueryProvider DeleteManyAsync() @@ -263,14 +263,14 @@ public IAsyncDeleteQueryProvider DeleteManyAsync() return new AsyncDeleteQueryProvider(this); } - public Task> PageAsync(long page, long itemsPerPage, Sql sql) + public ValueTask> PageAsync(long page, long itemsPerPage, Sql sql) { return PageAsync(page, itemsPerPage, sql.SQL, sql.Arguments); } - public Task> PageAsync(long page, long itemsPerPage, string sql, params object[] args) + public ValueTask> PageAsync(long page, long itemsPerPage, string sql, params object[] args) { - return PageImp>>(page, itemsPerPage, sql, args, + return PageImp>>(page, itemsPerPage, sql, args, async (paged, thesql) => { paged.Items = await (await QueryAsync(thesql)).ToListAsync(); @@ -278,39 +278,39 @@ public Task> PageAsync(long page, long itemsPerPage, string sql, para }); } - public Task> FetchAsync(long page, long itemsPerPage, string sql, params object[] args) + public ValueTask> FetchAsync(long page, long itemsPerPage, string sql, params object[] args) { return SkipTakeAsync((page - 1) * itemsPerPage, itemsPerPage, sql, args); } - public Task> FetchAsync(long page, long itemsPerPage, Sql sql) + public ValueTask> FetchAsync(long page, long itemsPerPage, Sql sql) { return SkipTakeAsync((page - 1) * itemsPerPage, itemsPerPage, sql.SQL, sql.Arguments); } - public Task> SkipTakeAsync(long skip, long take, string sql, params object[] args) + public ValueTask> SkipTakeAsync(long skip, long take, string sql, params object[] args) { string sqlCount, sqlPage; BuildPageQueries(skip, take, sql, ref args, out sqlCount, out sqlPage); return FetchAsync(sqlPage, args); } - public Task> SkipTakeAsync(long skip, long take, Sql sql) + public ValueTask> SkipTakeAsync(long skip, long take, Sql sql) { return SkipTakeAsync(skip, take, sql.SQL, sql.Arguments); } - public Task> FetchAsync() + public ValueTask> FetchAsync() { return FetchAsync(""); } - public async Task> FetchAsync(string sql, params object[] args) + public async ValueTask> FetchAsync(string sql, params object[] args) { return await (await QueryAsync(sql, args)).ToListAsync(); } - public async Task> FetchAsync(Sql sql) + public async ValueTask> FetchAsync(Sql sql) { return await (await QueryAsync(sql)).ToListAsync(); } @@ -362,54 +362,54 @@ internal async Task> QueryAsync(T instance, Expression SingleAsync(string sql, params object[] args) + public async ValueTask SingleAsync(string sql, params object[] args) { return await (await QueryAsync(sql, args)).SingleAsync(); } - public async Task SingleAsync(Sql sql) + public async ValueTask SingleAsync(Sql sql) { return await (await QueryAsync(sql)).SingleAsync(); } - public async Task SingleOrDefaultAsync(string sql, params object[] args) + public async ValueTask SingleOrDefaultAsync(string sql, params object[] args) { return await (await QueryAsync(sql, args)).SingleOrDefaultAsync(); } - public async Task SingleOrDefaultAsync(Sql sql) + public async ValueTask SingleOrDefaultAsync(Sql sql) { return await (await QueryAsync(sql)).SingleOrDefaultAsync(); } - public async Task SingleByIdAsync(object primaryKey) + public async ValueTask SingleByIdAsync(object primaryKey) { var sql = GenerateSingleByIdSql(primaryKey); return await (await QueryAsync(sql)).SingleAsync(); } - public async Task SingleOrDefaultByIdAsync(object primaryKey) + public async ValueTask SingleOrDefaultByIdAsync(object primaryKey) { var sql = GenerateSingleByIdSql(primaryKey); return await (await QueryAsync(sql)).SingleOrDefaultAsync(); } - public async Task FirstAsync(string sql, params object[] args) + public async ValueTask FirstAsync(string sql, params object[] args) { return await (await QueryAsync(sql, args)).FirstAsync(); } - public async Task FirstAsync(Sql sql) + public async ValueTask FirstAsync(Sql sql) { return await (await QueryAsync(sql)).FirstAsync(); } - public async Task FirstOrDefaultAsync(string sql, params object[] args) + public async ValueTask FirstOrDefaultAsync(string sql, params object[] args) { return await (await QueryAsync(sql, args)).FirstOrDefaultAsync(); } - public async Task FirstOrDefaultAsync(Sql sql) + public async ValueTask FirstOrDefaultAsync(Sql sql) { return await (await QueryAsync(sql)).FirstOrDefaultAsync(); } @@ -462,7 +462,7 @@ public async Task ExecuteScalarAsync(Sql Sql) object val = await ExecuteScalarHelperAsync(cmd); if (val == null || val == DBNull.Value) - return await TaskAsyncHelper.FromResult(default(T)); + return default(T); Type t = typeof(T); Type u = Nullable.GetUnderlyingType(t); @@ -505,14 +505,4 @@ internal async Task ExecuteReaderHelperAsync(DbCommand cmd) return reader; } } - - public class TaskAsyncHelper - { - public static Task FromResult(T value) - { - var tcs = new TaskCompletionSource(); - tcs.SetResult(value); - return tcs.Task; - } - } } diff --git a/src/NPoco/Database.cs b/src/NPoco/Database.cs index 4860c640..86a67b52 100644 --- a/src/NPoco/Database.cs +++ b/src/NPoco/Database.cs @@ -1584,31 +1584,31 @@ public int UpdateBatch(IEnumerable> pocos, BatchOptions option var sql = new Sql(); foreach (var preparedUpdate in preparedUpdates) - { + { if (preparedUpdate.Sql != null) { sql.Append(preparedUpdate.Sql + options.StatementSeperator, preparedUpdate.Rawvalues.ToArray()); - } + } } using (var cmd = CreateCommand(_sharedConnection, sql.SQL, sql.Arguments)) - { + { result += ExecuteNonQueryHelper(cmd); - } + } } } catch (Exception x) - { + { OnExceptionInternal(x); throw; - } + } finally - { + { CloseSharedConnectionInternal(); - } + } return result; - } + } // Update a record with values from a poco. primary key value can be either supplied or read from the poco private TRet UpdateImp(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns, Func, TRet> executeFunc, TRet defaultId) @@ -1627,7 +1627,7 @@ private TRet UpdateImp(string tableName, string primaryKeyName, object poc var result = executeFunc(preparedStatement.Sql, preparedStatement.Rawvalues.ToArray(), (id) => { if (id == 0 && !string.IsNullOrEmpty(preparedStatement.VersionName) && VersionException == VersionExceptionHandling.Exception) - { + { throw new DBConcurrencyException(string.Format("A Concurrency update occurred in table '{0}' for primary key value(s) = '{1}' and version = '{2}'", tableName, string.Join(",", preparedStatement.PrimaryKeyValuePairs.Values.Select(x => x.ToString()).ToArray()), preparedStatement.VersionValue)); } diff --git a/src/NPoco/DatabaseTypes/FirebirdDatabaseType.cs b/src/NPoco/DatabaseTypes/FirebirdDatabaseType.cs index 2b1e7741..cfbef637 100644 --- a/src/NPoco/DatabaseTypes/FirebirdDatabaseType.cs +++ b/src/NPoco/DatabaseTypes/FirebirdDatabaseType.cs @@ -86,7 +86,7 @@ public override async Task ExecuteInsertAsync(Database db, DbCommand } await db.ExecuteNonQueryHelperAsync(cmd); - return TaskAsyncHelper.FromResult(-1); + return -1; } public override SqlExpression ExpressionVisitor(IDatabase db, PocoData pocoData, bool prefixTableName) diff --git a/src/NPoco/DatabaseTypes/OracleDatabaseType.cs b/src/NPoco/DatabaseTypes/OracleDatabaseType.cs index 4f75f1b3..f4e5ff93 100644 --- a/src/NPoco/DatabaseTypes/OracleDatabaseType.cs +++ b/src/NPoco/DatabaseTypes/OracleDatabaseType.cs @@ -76,7 +76,7 @@ public override async Task ExecuteInsertAsync(Database db, DbCommand } await db.ExecuteNonQueryHelperAsync(cmd); - return TaskAsyncHelper.FromResult(-1); + return -1; } public override string GetProviderName() diff --git a/src/NPoco/DatabaseTypes/PostgreSQLDatabaseType.cs b/src/NPoco/DatabaseTypes/PostgreSQLDatabaseType.cs index 71d5960a..61b0721c 100644 --- a/src/NPoco/DatabaseTypes/PostgreSQLDatabaseType.cs +++ b/src/NPoco/DatabaseTypes/PostgreSQLDatabaseType.cs @@ -45,7 +45,7 @@ public override async Task ExecuteInsertAsync(Database db, DbCommand } await db.ExecuteNonQueryHelperAsync(cmd); - return TaskAsyncHelper.FromResult(-1); + return -1; } public override string GetParameterPrefix(string connectionString) diff --git a/src/NPoco/DatabaseTypes/SQLiteDatabaseType.cs b/src/NPoco/DatabaseTypes/SQLiteDatabaseType.cs index b36f9002..92b8d859 100644 --- a/src/NPoco/DatabaseTypes/SQLiteDatabaseType.cs +++ b/src/NPoco/DatabaseTypes/SQLiteDatabaseType.cs @@ -41,7 +41,7 @@ public override async Task ExecuteInsertAsync(Database db, DbCommand } await db.ExecuteNonQueryHelperAsync(cmd); - return TaskAsyncHelper.FromResult(-1); + return -1; } public override string GetExistsSql() diff --git a/src/NPoco/IAsyncDatabase.cs b/src/NPoco/IAsyncDatabase.cs index 3017d14b..9e9f4a16 100644 --- a/src/NPoco/IAsyncDatabase.cs +++ b/src/NPoco/IAsyncDatabase.cs @@ -2,8 +2,8 @@ using System.Collections.Generic; using System.Data; using System.Linq.Expressions; -using NPoco.Linq; using System.Threading.Tasks; +using NPoco.Linq; namespace NPoco { @@ -86,52 +86,52 @@ public interface IAsyncQueryDatabase : IBaseDatabase /// Fetch the only row of type T using the sql and parameters specified /// Get an object of type T by primary key value /// - Task SingleAsync(string sql, params object[] args); + ValueTask SingleAsync(string sql, params object[] args); /// /// Fetch the only row of type T using the sql and parameters specified /// - Task SingleAsync(Sql sql); + ValueTask SingleAsync(Sql sql); /// /// Fetch the only row of type T using the sql and parameters specified /// - Task SingleOrDefaultAsync(string sql, params object[] args); + ValueTask SingleOrDefaultAsync(string sql, params object[] args); /// /// Fetch the only row of type T using the sql and parameters specified /// - Task SingleOrDefaultAsync(Sql sql); + ValueTask SingleOrDefaultAsync(Sql sql); /// /// Get an object of type T by primary key value /// - Task SingleByIdAsync(object primaryKey); + ValueTask SingleByIdAsync(object primaryKey); /// /// Get an object of type T by primary key value /// - Task SingleOrDefaultByIdAsync(object primaryKey); + ValueTask SingleOrDefaultByIdAsync(object primaryKey); /// /// Fetch the first row of type T using the sql and parameters specified /// - Task FirstAsync(string sql, params object[] args); + ValueTask FirstAsync(string sql, params object[] args); /// /// Fetch the first row of type T using the sql and parameters specified /// - Task FirstAsync(Sql sql); + ValueTask FirstAsync(Sql sql); /// /// Fetch the first row of type T using the sql and parameters specified /// - Task FirstOrDefaultAsync(string sql, params object[] args); + ValueTask FirstOrDefaultAsync(string sql, params object[] args); /// /// Fetch the first row of type T using the sql and parameters specified /// - Task FirstOrDefaultAsync(Sql sql); + ValueTask FirstOrDefaultAsync(Sql sql); /// /// Fetch objects of type T from the database using the sql and parameters specified. @@ -153,17 +153,17 @@ public interface IAsyncQueryDatabase : IBaseDatabase /// /// Fetch objects of type T from the database using the sql and parameters specified. /// - Task> FetchAsync(string sql, params object[] args); + ValueTask> FetchAsync(string sql, params object[] args); /// /// Fetch objects of type T from the database using the sql and parameters specified. /// - Task> FetchAsync(Sql sql); + ValueTask> FetchAsync(Sql sql); /// /// Fetch all objects of type T from the database. /// - Task> FetchAsync(); + ValueTask> FetchAsync(); /// /// Fetch objects of type T from the database using the sql and parameters specified. @@ -171,7 +171,7 @@ public interface IAsyncQueryDatabase : IBaseDatabase /// Extra metadata in the Page class will also be returned. /// Note: This will perform two queries. One for the paged results and one for the count of all results. /// - Task> PageAsync(long page, long itemsPerPage, string sql, params object[] args); + ValueTask> PageAsync(long page, long itemsPerPage, string sql, params object[] args); /// /// Fetch objects of type T from the database using the sql and parameters specified. @@ -179,30 +179,30 @@ public interface IAsyncQueryDatabase : IBaseDatabase /// Extra metadata in the Page class will also be returned. /// Note: This will perform two queries. One for the paged results and one for the count of all results. /// - Task> PageAsync(long page, long itemsPerPage, Sql sql); + ValueTask> PageAsync(long page, long itemsPerPage, Sql sql); /// /// Fetch objects of type T from the database using the sql and parameters specified. /// The sql provided will be converted so that only the results for the page and itemsPerPage values specified will be returned. /// - Task> FetchAsync(long page, long itemsPerPage, string sql, params object[] args); + ValueTask> FetchAsync(long page, long itemsPerPage, string sql, params object[] args); /// /// Fetch objects of type T from the database using the sql and parameters specified. /// The sql provided will be converted so that only the results for the page and itemsPerPage values specified will be returned. /// - Task> FetchAsync(long page, long itemsPerPage, Sql sql); + ValueTask> FetchAsync(long page, long itemsPerPage, Sql sql); /// /// Fetch objects of type T from the database using the sql and parameters specified. /// The sql provided will be converted so that only the results for the skip and take values specified will be returned. /// - Task> SkipTakeAsync(long skip, long take, string sql, params object[] args); + ValueTask> SkipTakeAsync(long skip, long take, string sql, params object[] args); /// /// Fetch objects of type T from the database using the sql and parameters specified. /// The sql provided will be converted so that only the results for the skip and take values specified will be returned. /// - Task> SkipTakeAsync(long skip, long take, Sql sql); + ValueTask> SkipTakeAsync(long skip, long take, Sql sql); } } diff --git a/src/NPoco/Linq/SimpleQueryProvider.cs b/src/NPoco/Linq/SimpleQueryProvider.cs index bb0a0c1c..bc11ea0c 100644 --- a/src/NPoco/Linq/SimpleQueryProvider.cs +++ b/src/NPoco/Linq/SimpleQueryProvider.cs @@ -10,25 +10,25 @@ namespace NPoco.Linq { public interface IAsyncQueryResultProvider { - Task> ToList(); - Task ToArray(); + ValueTask> ToList(); + ValueTask ToArray(); Task> ToEnumerable(); - Task FirstOrDefault(); - Task FirstOrDefault(Expression> whereExpression); - Task First(); - Task First(Expression> whereExpression); - Task SingleOrDefault(); - Task SingleOrDefault(Expression> whereExpression); - Task Single(); - Task Single(Expression> whereExpression); - Task Count(); - Task Count(Expression> whereExpression); - Task Any(); - Task Any(Expression> whereExpression); - Task> ToPage(int page, int pageSize); - Task> ProjectTo(Expression> projectionExpression); - Task> Distinct(Expression> projectionExpression); - Task> Distinct(); + ValueTask FirstOrDefault(); + ValueTask FirstOrDefault(Expression> whereExpression); + ValueTask First(); + ValueTask First(Expression> whereExpression); + ValueTask SingleOrDefault(); + ValueTask SingleOrDefault(Expression> whereExpression); + ValueTask Single(); + ValueTask Single(Expression> whereExpression); + ValueTask Count(); + ValueTask Count(Expression> whereExpression); + ValueTask Any(); + ValueTask Any(Expression> whereExpression); + ValueTask> ToPage(int page, int pageSize); + ValueTask> ProjectTo(Expression> projectionExpression); + ValueTask> Distinct(Expression> projectionExpression); + ValueTask> Distinct(); } public interface IQueryResultProvider @@ -54,25 +54,25 @@ public interface IQueryResultProvider List ProjectTo(Expression> projectionExpression); List Distinct(Expression> projectionExpression); List Distinct(); - Task> ToListAsync(); - Task ToArrayAsync(); + ValueTask> ToListAsync(); + ValueTask ToArrayAsync(); Task> ToEnumerableAsync(); - Task FirstOrDefaultAsync(); - Task FirstOrDefaultAsync(Expression> whereExpression); - Task FirstAsync(); - Task FirstAsync(Expression> whereExpression); - Task SingleOrDefaultAsync(); - Task SingleOrDefaultAsync(Expression> whereExpression); - Task SingleAsync(); - Task SingleAsync(Expression> whereExpression); - Task CountAsync(); - Task CountAsync(Expression> whereExpression); - Task AnyAsync(); - Task AnyAsync(Expression> whereExpression); - Task> ToPageAsync(int page, int pageSize); - Task> ProjectToAsync(Expression> projectionExpression); - Task> DistinctAsync(Expression> projectionExpression); - Task> DistinctAsync(); + ValueTask FirstOrDefaultAsync(); + ValueTask FirstOrDefaultAsync(Expression> whereExpression); + ValueTask FirstAsync(); + ValueTask FirstAsync(Expression> whereExpression); + ValueTask SingleOrDefaultAsync(); + ValueTask SingleOrDefaultAsync(Expression> whereExpression); + ValueTask SingleAsync(); + ValueTask SingleAsync(Expression> whereExpression); + ValueTask CountAsync(); + ValueTask CountAsync(Expression> whereExpression); + ValueTask AnyAsync(); + ValueTask AnyAsync(Expression> whereExpression); + ValueTask> ToPageAsync(int page, int pageSize); + ValueTask> ProjectToAsync(Expression> projectionExpression); + ValueTask> DistinctAsync(Expression> projectionExpression); + ValueTask> DistinctAsync(); } public interface IQueryProvider : IQueryResultProvider @@ -195,14 +195,14 @@ private IAsyncQueryProviderWithIncludes QueryProviderWithIncludes(Expression return this; } - public async Task> ToList() + public async ValueTask> ToList() { - return await (await ToEnumerable().ConfigureAwait(false)).ToListAsync(); + return await (await ToEnumerable()).ToListAsync(); } - public async Task ToArray() + public async ValueTask ToArray() { - return await (await ToEnumerable().ConfigureAwait(false)).ToArrayAsync(); + return await (await ToEnumerable()).ToArrayAsync(); } public Task> ToEnumerable() @@ -210,73 +210,73 @@ public Task> ToEnumerable() return _database.QueryAsync(default(T), _listExpression, null, BuildSql()); } - public Task FirstOrDefault() + public ValueTask FirstOrDefault() { return FirstOrDefault(null); } - public async Task FirstOrDefault(Expression> whereExpression) + public async ValueTask FirstOrDefault(Expression> whereExpression) { AddWhere(whereExpression); return await (await ToEnumerable()).FirstOrDefaultAsync(); } - public Task First() + public ValueTask First() { return First(null); } - public async Task First(Expression> whereExpression) + public async ValueTask First(Expression> whereExpression) { AddWhere(null); return await (await ToEnumerable()).FirstAsync(); } - public Task SingleOrDefault() + public ValueTask SingleOrDefault() { return SingleOrDefault(null); } - public async Task SingleOrDefault(Expression> whereExpression) + public async ValueTask SingleOrDefault(Expression> whereExpression) { AddWhere(whereExpression); - return await (await ToEnumerable().ConfigureAwait(false)).SingleOrDefaultAsync(); + return await (await ToEnumerable()).SingleOrDefaultAsync(); } - public Task Single() + public ValueTask Single() { return Single(null); } - public async Task Single(Expression> whereExpression) + public async ValueTask Single(Expression> whereExpression) { AddWhere(whereExpression); - return await (await ToEnumerable().ConfigureAwait(false)).SingleAsync(); + return await (await ToEnumerable()).SingleAsync(); } - public Task Count() + public ValueTask Count() { return Count(null); } - public async Task Count(Expression> whereExpression) + public async ValueTask Count(Expression> whereExpression) { AddWhere(whereExpression); var sql = _buildComplexSql.BuildJoin(_database, _sqlExpression, _joinSqlExpressions.Values.ToList(), null, true, false); - return await _database.ExecuteScalarAsync(sql).ConfigureAwait(false); + return await _database.ExecuteScalarAsync(sql); } - public Task Any() + public ValueTask Any() { return Any(null); } - public async Task Any(Expression> whereExpression) + public async ValueTask Any(Expression> whereExpression) { - return (await Count(whereExpression).ConfigureAwait(false)) > 0; + return (await Count(whereExpression)) > 0; } - public async Task> ToPage(int page, int pageSize) + public async ValueTask> ToPage(int page, int pageSize) { int offset = (page - 1) * pageSize; @@ -301,21 +301,21 @@ public async Task> ToPage(int page, int pageSize) return result; } - public async Task> ProjectTo(Expression> projectionExpression) + public async ValueTask> ProjectTo(Expression> projectionExpression) { var sql = _buildComplexSql.GetSqlForProjection(projectionExpression, false); - return await (await _database.QueryAsync(sql).ConfigureAwait(false)).Select(projectionExpression.Compile()).ToListAsync(); + return await (await _database.QueryAsync(sql)).Select(projectionExpression.Compile()).ToListAsync(); } - public async Task> Distinct() + public async ValueTask> Distinct() { return await (await _database.QueryAsync(new Sql(_sqlExpression.Context.ToSelectStatement(true, true), _sqlExpression.Context.Params))).ToListAsync(); } - public async Task> Distinct(Expression> projectionExpression) + public async ValueTask> Distinct(Expression> projectionExpression) { var sql = _buildComplexSql.GetSqlForProjection(projectionExpression, true); - return await (await _database.QueryAsync(sql).ConfigureAwait(false)).Select(projectionExpression.Compile()).ToListAsync(); + return await (await _database.QueryAsync(sql)).Select(projectionExpression.Compile()).ToListAsync(); } public IAsyncQueryProvider Where(Expression> whereExpression) @@ -598,12 +598,12 @@ private IEnumerable ExecuteQuery(Sql sql) } #pragma warning restore CS0109 - public Task> ToListAsync() + public ValueTask> ToListAsync() { return base.ToList(); } - public Task ToArrayAsync() + public ValueTask ToArrayAsync() { return base.ToArray(); } @@ -613,82 +613,82 @@ public Task> ToEnumerableAsync() return base.ToEnumerable(); } - public Task FirstOrDefaultAsync() + public ValueTask FirstOrDefaultAsync() { return base.FirstOrDefault(); } - public Task FirstOrDefaultAsync(Expression> whereExpression) + public ValueTask FirstOrDefaultAsync(Expression> whereExpression) { return base.FirstOrDefault(whereExpression); } - public Task FirstAsync() + public ValueTask FirstAsync() { return base.First(); } - public Task FirstAsync(Expression> whereExpression) + public ValueTask FirstAsync(Expression> whereExpression) { return base.First(whereExpression); } - public Task SingleOrDefaultAsync() + public ValueTask SingleOrDefaultAsync() { return base.SingleOrDefault(); } - public Task SingleOrDefaultAsync(Expression> whereExpression) + public ValueTask SingleOrDefaultAsync(Expression> whereExpression) { return base.SingleOrDefault(whereExpression); } - public Task SingleAsync() + public ValueTask SingleAsync() { return base.Single(); } - public Task SingleAsync(Expression> whereExpression) + public ValueTask SingleAsync(Expression> whereExpression) { return base.Single(whereExpression); } - public Task CountAsync() + public ValueTask CountAsync() { return base.Count(); } - public Task CountAsync(Expression> whereExpression) + public ValueTask CountAsync(Expression> whereExpression) { return base.Count(whereExpression); } - public Task AnyAsync() + public ValueTask AnyAsync() { return base.Any(); } - public Task AnyAsync(Expression> whereExpression) + public ValueTask AnyAsync(Expression> whereExpression) { return base.Any(whereExpression); } - public Task> ToPageAsync(int page, int pageSize) + public ValueTask> ToPageAsync(int page, int pageSize) { return base.ToPage(page, pageSize); } - public Task> ProjectToAsync(Expression> projectionExpression) + public ValueTask> ProjectToAsync(Expression> projectionExpression) { return base.ProjectTo(projectionExpression); } - public Task> DistinctAsync(Expression> projectionExpression) + public ValueTask> DistinctAsync(Expression> projectionExpression) { return base.Distinct(projectionExpression); } - public Task> DistinctAsync() + public ValueTask> DistinctAsync() { return base.Distinct(); } From 99e99d3beeee353fb7bf4d758423a81961568147 Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Mon, 28 Sep 2020 12:19:30 +1000 Subject: [PATCH 05/42] Use the cached version of the DatabaseType in the SqlServerDatabase --- src/NPoco.SqlServer/SqlServerDatabase.cs | 3 +-- src/NPoco/Singleton.cs | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/NPoco.SqlServer/SqlServerDatabase.cs b/src/NPoco.SqlServer/SqlServerDatabase.cs index b0c55ad7..12a0e76c 100644 --- a/src/NPoco.SqlServer/SqlServerDatabase.cs +++ b/src/NPoco.SqlServer/SqlServerDatabase.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using NPoco.DatabaseTypes; -// ReSharper disable once CheckNamespace namespace NPoco.SqlServer { public class SqlServerDatabase : Database @@ -12,7 +11,7 @@ public class SqlServerDatabase : Database private readonly IPollyPolicy _pollyPolicy; public SqlServerDatabase(string connectionString, IPollyPolicy pollyPolicy = null) - : this(connectionString, new SqlServer2012DatabaseType(), pollyPolicy) + : this(connectionString, Singleton.Instance, pollyPolicy) { } diff --git a/src/NPoco/Singleton.cs b/src/NPoco/Singleton.cs index 850f5b4b..fd227254 100644 --- a/src/NPoco/Singleton.cs +++ b/src/NPoco/Singleton.cs @@ -8,7 +8,6 @@ namespace NPoco static class Singleton where T : new() { public static T Instance = new T(); - } class DynamicDatabaseType From cb833ceb7dd173efbfb00ea94695b5aa22b0b310 Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Tue, 29 Sep 2020 20:39:29 +1000 Subject: [PATCH 06/42] Change ValueTask to Task for the ExecutionHook --- src/NPoco.SqlServer/SqlServerDatabase.cs | 2 +- src/NPoco/Database.cs | 2 +- src/NPoco/IAsyncDatabase.cs | 6 ++++++ .../DecoratedTests/TransactionDecoratedTests.cs | 8 ++++---- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/NPoco.SqlServer/SqlServerDatabase.cs b/src/NPoco.SqlServer/SqlServerDatabase.cs index 12a0e76c..799c8383 100644 --- a/src/NPoco.SqlServer/SqlServerDatabase.cs +++ b/src/NPoco.SqlServer/SqlServerDatabase.cs @@ -31,7 +31,7 @@ protected override T ExecutionHook(Func action) return base.ExecutionHook(action); } - protected override async ValueTask ExecutionHookAsync(Func> action) + protected override async Task ExecutionHookAsync(Func> action) { if (_pollyPolicy?.AsyncRetryPolicy != null) { diff --git a/src/NPoco/Database.cs b/src/NPoco/Database.cs index 86a67b52..7b256cf8 100644 --- a/src/NPoco/Database.cs +++ b/src/NPoco/Database.cs @@ -2007,7 +2007,7 @@ protected virtual T ExecutionHook(Func action) return action(); } - protected virtual async ValueTask ExecutionHookAsync(Func> action) + protected virtual async Task ExecutionHookAsync(Func> action) { return await action(); } diff --git a/src/NPoco/IAsyncDatabase.cs b/src/NPoco/IAsyncDatabase.cs index 9e9f4a16..8e2a76e2 100644 --- a/src/NPoco/IAsyncDatabase.cs +++ b/src/NPoco/IAsyncDatabase.cs @@ -29,6 +29,12 @@ public interface IAsyncDatabase : IAsyncQueryDatabase /// Task ExecuteAsync(Sql sql); + /// + /// Performs an SQL Insert using the table name, primary key and POCO + /// + /// The auto allocated primary key of the new record + Task InsertAsync(string tableName, string primaryKeyName, object poco); + /// /// Insert POCO into the table by convention or configuration /// diff --git a/test/NPoco.Tests/DecoratedTests/TransactionDecoratedTests.cs b/test/NPoco.Tests/DecoratedTests/TransactionDecoratedTests.cs index acb2b4ae..e301fbec 100644 --- a/test/NPoco.Tests/DecoratedTests/TransactionDecoratedTests.cs +++ b/test/NPoco.Tests/DecoratedTests/TransactionDecoratedTests.cs @@ -52,7 +52,7 @@ public void NestedTransactionThatFailsAbortsWhole() { using (var scope2 = Database.GetTransaction()) { - + var user1 = new UserDecorated { @@ -197,7 +197,7 @@ public void NestedTransactionsBothComplete() }; InMemoryExtraUserInfos.Add(extra1); Database.Insert(extra1); - + scope2.Complete(); } @@ -218,7 +218,7 @@ public void NestedTransactionsBothCompleteUsingBeginAbort() Name = "Name" + 16, Age = 20 + 16, DateOfBirth = new DateTime(1970, 1, 1).AddYears(16), - Savings = 50.00m + (1.01m*16) + Savings = 50.00m + (1.01m * 16) }; InMemoryUsers.Add(user); Database.Insert(user); @@ -239,7 +239,7 @@ public void NestedTransactionsBothCompleteUsingBeginAbort() Name = "Name" + 16, Age = 20 + 16, DateOfBirth = new DateTime(1970, 1, 1).AddYears(16), - Savings = 50.00m + (1.01m*16) + Savings = 50.00m + (1.01m * 16) }; InMemoryUsers.Add(user1); Database.Insert(user1); From da16bbdb109985fd11c7190447f880c4df48b3b4 Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Tue, 29 Sep 2020 20:40:35 +1000 Subject: [PATCH 07/42] Add ValueTupleMapper #358 from @asztal --- src/NPoco/MappingFactory.cs | 15 ++- src/NPoco/RowMappers/ValueTupleMapper.cs | 121 ++++++++++++++++++ .../NewMapper/ValueTupleMapperTests.cs | 84 ++++++++++++ 3 files changed, 213 insertions(+), 7 deletions(-) create mode 100644 src/NPoco/RowMappers/ValueTupleMapper.cs create mode 100644 test/NPoco.Tests/NewMapper/ValueTupleMapperTests.cs diff --git a/src/NPoco/MappingFactory.cs b/src/NPoco/MappingFactory.cs index d9de5953..ca2cba71 100644 --- a/src/NPoco/MappingFactory.cs +++ b/src/NPoco/MappingFactory.cs @@ -9,25 +9,26 @@ namespace NPoco { public class MappingFactory { - public static List> RowMappers { get; private set; } + public static List> RowMappers { get; private set; } private readonly PocoData _pocoData; private readonly IRowMapper _rowMapper; static MappingFactory() { - RowMappers = new List>() + RowMappers = new List>() { - () => new DictionaryMapper(), - () => new ValueTypeMapper(), - () => new ArrayMapper(), - () => new PropertyMapper() + x => new ValueTupleRowMapper(x), + _ => new DictionaryMapper(), + _ => new ValueTypeMapper(), + _ => new ArrayMapper(), + _ => new PropertyMapper() }; } public MappingFactory(PocoData pocoData, DbDataReader dataReader) { _pocoData = pocoData; - _rowMapper = RowMappers.Select(mapper => mapper()).First(x => x.ShouldMap(pocoData)); + _rowMapper = RowMappers.Select(mapper => mapper(_pocoData.Mapper)).First(x => x.ShouldMap(pocoData)); _rowMapper.Init(dataReader, pocoData); } diff --git a/src/NPoco/RowMappers/ValueTupleMapper.cs b/src/NPoco/RowMappers/ValueTupleMapper.cs new file mode 100644 index 00000000..3e903df0 --- /dev/null +++ b/src/NPoco/RowMappers/ValueTupleMapper.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Linq; +using System.Linq.Expressions; + +namespace NPoco.RowMappers +{ + public class ValueTupleRowMapper : IRowMapper + { + private Func mapper = default!; + private MapperCollection mappers; + + private static Cache<(Type, MapperCollection), Func> cache + = new Cache<(Type, MapperCollection), Func>(); + + public ValueTupleRowMapper(MapperCollection mappers) + { + this.mappers = mappers; + } + + public void Init(DbDataReader dataReader, PocoData pocoData) + { + mapper = GetRowMapper(pocoData.Type, this.mappers, dataReader); + } + + public object Map(DbDataReader dataReader, RowMapperContext context) + { + return mapper(dataReader); + } + + public static bool IsValueTuple(Type type) + { + if (!type.IsGenericType) + return false; + + var baseType = type.GetGenericTypeDefinition(); + return ( + baseType == typeof(ValueTuple<>) || + baseType == typeof(ValueTuple<,>) || + baseType == typeof(ValueTuple<,,>) || + baseType == typeof(ValueTuple<,,,>) || + baseType == typeof(ValueTuple<,,,,>) || + baseType == typeof(ValueTuple<,,,,,>) || + baseType == typeof(ValueTuple<,,,,,,>) || + baseType == typeof(ValueTuple<,,,,,,,>) + ); + } + + public bool ShouldMap(PocoData pocoData) + { + return IsValueTuple(pocoData.Type); + } + + private static Func GetRowMapper(Type type, MapperCollection mappers, DbDataReader dataReader) + { + return cache.Get((type, mappers), () => CreateRowMapper(type, mappers, dataReader)); + } + + private static Func CreateRowMapper(Type type, MapperCollection mappers, DbDataReader dataReader) + { + var reader = Expression.Parameter(typeof(DbDataReader), "reader"); + var (tupleExpr, _) = CreateTupleExpression(type, mappers, dataReader, reader, 0); + + // reader => (object)new ValueTuple(value1, value2, ...); + var expr = Expression.Lambda( + Expression.Convert(tupleExpr, typeof(object)), + new[] { reader } + ); + return (Func)expr.Compile(); + } + + private static (NewExpression expr, int fieldsIndex) CreateTupleExpression(Type type, MapperCollection mappers, DbDataReader dataReader, ParameterExpression reader, int fieldIndex) + { + var argTypes = type.GetGenericArguments(); + var ctor = type.GetConstructor(argTypes); + var getValue = typeof(DbDataReader).GetMethod("GetValue")!; + var isDBNull = typeof(DbDataReader).GetMethod("IsDBNull")!; + + if (argTypes.Count() > dataReader.FieldCount) + throw new InvalidOperationException("SQL query does not return enough fields to fill the tuple"); + + var args = new List(); + + foreach (var argType in argTypes) + { + if (IsValueTuple(argType)) + { + // It's tuples all the way down + var (expr, newFieldIndex) = CreateTupleExpression(argType, mappers, dataReader, reader, fieldIndex); + args.Add(expr); + fieldIndex += newFieldIndex; + } + else + { + if (fieldIndex >= dataReader.FieldCount) + throw new InvalidOperationException($"SQL query does not return enough fields to fill the tuple (missing type: {argType.FullName})"); + + var rawValue = Expression.Call(reader, getValue, new[] { Expression.Constant(fieldIndex) }); + var converter = MappingHelper.GetConverter(mappers, null, dataReader.GetFieldType(fieldIndex), argType); + + // reader.IsDBNull(i) ? (T)null : converter(reader.GetValue(i)) + args.Add(Expression.Condition( + Expression.Call(reader, isDBNull, new[] { Expression.Constant(fieldIndex) }), + Expression.Convert(Expression.Constant(null), argType), + Expression.Convert( + converter != null + ? (Expression)Expression.Invoke(Expression.Constant(converter), new[] { rawValue }) + : (Expression)rawValue, + argType + ) + )); + + fieldIndex++; + } + } + + return (Expression.New(ctor, args), fieldIndex); + } + } +} diff --git a/test/NPoco.Tests/NewMapper/ValueTupleMapperTests.cs b/test/NPoco.Tests/NewMapper/ValueTupleMapperTests.cs new file mode 100644 index 00000000..4b914070 --- /dev/null +++ b/test/NPoco.Tests/NewMapper/ValueTupleMapperTests.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Text; +using NPoco.Tests.Common; +using NPoco.Tests.NewMapper.Models; +using NUnit.Framework; + +namespace NPoco.Tests.NewMapper +{ + public class ValueTupleMapperTests : BaseDBDecoratedTest + { + [Test] + public void Test1() + { + var (foo, bar) = Database.Single<(string, string)>(@"select 'foo', 'bar' /*poco_dual*/"); + + Assert.AreEqual(foo, "foo"); + Assert.AreEqual(bar, "bar"); + } + + [Test] + public void Test2() + { + var data = Database.Single<((string foo, int i) first, (string bar, int j) second)>(@"select 'foo', 5, 'bar', 6 /*poco_dual*/"); + + Assert.AreEqual(data.first.foo, "foo"); + Assert.AreEqual(data.first.i, 5); + Assert.AreEqual(data.second.bar, "bar"); + Assert.AreEqual(data.second.j, 6); + } + + [Test] + public void Test3() + { + var (foo, bar) = Database.Single<(string, string)>(@"select 'foo', null /*poco_dual*/"); + + Assert.AreEqual(foo, "foo"); + Assert.AreEqual(bar, null); + } + + [Test] + public void Test4() + { + var (foo, bar) = Database.Single<(string, TestEnum)>(@"select 'foo', 'None' /*poco_dual*/"); + + Assert.AreEqual(foo, "foo"); + Assert.AreEqual(bar, TestEnum.None); + } + + [Test] + public void Test5() + { + Database.Mappers.Add(new MyMapper()); + + var (foo, bar) = Database.Single<(string, MyKey)>(@"select 'foo', 77 /*poco_dual*/"); + + Assert.AreEqual(foo, "foo"); + Assert.AreEqual(bar.Key, 77); + } + + public class MyMapper : DefaultMapper + { + public override Func GetFromDbConverter(Type destType, Type sourceType) + { + if (destType == typeof(MyKey) && sourceType == typeof(int)) + { + return x => new MyKey((int) x); + } + + return base.GetFromDbConverter(destType, sourceType); + } + } + + public class MyKey + { + public int Key { get; } + + public MyKey(int key) + { + Key = key; + } + } + } +} From 498f2299a4eeeb9e502601a7b8cb148eade412b9 Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Tue, 29 Sep 2020 22:15:53 +1000 Subject: [PATCH 08/42] Add ConfigureAwait(false) back to all await calls --- .../DatabaseTypes/SqlServerCEDatabaseType.cs | 4 +- src/NPoco.SqlServer/SqlBulkCopyHelper.cs | 2 +- src/NPoco.SqlServer/SqlServerDatabase.cs | 4 +- src/NPoco/AsyncDatabase.cs | 50 +++++++++---------- src/NPoco/Database.cs | 6 +-- src/NPoco/DatabaseType.cs | 4 +- .../DatabaseTypes/FirebirdDatabaseType.cs | 4 +- src/NPoco/DatabaseTypes/OracleDatabaseType.cs | 4 +- .../DatabaseTypes/PostgreSQLDatabaseType.cs | 4 +- src/NPoco/DatabaseTypes/SQLiteDatabaseType.cs | 4 +- src/NPoco/Linq/SimpleQueryProvider.cs | 26 +++++----- src/NPoco/Linq/UpdateQueryProvider.cs | 2 +- 12 files changed, 57 insertions(+), 57 deletions(-) diff --git a/src/NPoco.SqlServer/DatabaseTypes/SqlServerCEDatabaseType.cs b/src/NPoco.SqlServer/DatabaseTypes/SqlServerCEDatabaseType.cs index 62b5416d..071ae8fe 100644 --- a/src/NPoco.SqlServer/DatabaseTypes/SqlServerCEDatabaseType.cs +++ b/src/NPoco.SqlServer/DatabaseTypes/SqlServerCEDatabaseType.cs @@ -22,8 +22,8 @@ public override object ExecuteInsert(Database db, DbCommand cmd, string prima public override async Task ExecuteInsertAsync(Database db, DbCommand cmd, string primaryKeyName, bool useOutputClause, T poco, object[] args) { - await db.ExecuteNonQueryHelperAsync(cmd); - return await db.ExecuteScalarAsync("SELECT @@@IDENTITY AS NewID;"); + await db.ExecuteNonQueryHelperAsync(cmd).ConfigureAwait(false); + return await db.ExecuteScalarAsync("SELECT @@@IDENTITY AS NewID;").ConfigureAwait(false); } public override IsolationLevel GetDefaultTransactionIsolationLevel() diff --git a/src/NPoco.SqlServer/SqlBulkCopyHelper.cs b/src/NPoco.SqlServer/SqlBulkCopyHelper.cs index b6120fd5..64457355 100644 --- a/src/NPoco.SqlServer/SqlBulkCopyHelper.cs +++ b/src/NPoco.SqlServer/SqlBulkCopyHelper.cs @@ -37,7 +37,7 @@ public static async Task BulkInsertAsync(IDatabase db, IEnumerable list, S using (var bulkCopy = new SqlBulkCopy(SqlConnectionResolver(db.Connection), sqlBulkCopyOptions, SqlTransactionResolver(db.Transaction))) { var table = BuildBulkInsertDataTable(db, list, bulkCopy, sqlBulkCopyOptions); - await bulkCopy.WriteToServerAsync(table); + await bulkCopy.WriteToServerAsync(table).ConfigureAwait(false); } } diff --git a/src/NPoco.SqlServer/SqlServerDatabase.cs b/src/NPoco.SqlServer/SqlServerDatabase.cs index 799c8383..8e7ee9df 100644 --- a/src/NPoco.SqlServer/SqlServerDatabase.cs +++ b/src/NPoco.SqlServer/SqlServerDatabase.cs @@ -35,10 +35,10 @@ protected override async Task ExecutionHookAsync(Func> action) { if (_pollyPolicy?.AsyncRetryPolicy != null) { - return await _pollyPolicy.AsyncRetryPolicy.ExecuteAsync(action); + return await _pollyPolicy.AsyncRetryPolicy.ExecuteAsync(action).ConfigureAwait(false); } - return await base.ExecutionHookAsync(action); + return await base.ExecutionHookAsync(action).ConfigureAwait(false); } } } diff --git a/src/NPoco/AsyncDatabase.cs b/src/NPoco/AsyncDatabase.cs index 9cc0c3a5..b90f8e1a 100644 --- a/src/NPoco/AsyncDatabase.cs +++ b/src/NPoco/AsyncDatabase.cs @@ -76,12 +76,12 @@ private async Task InsertAsyncImp(PocoData pocoData, string tableName object id; if (!autoIncrement) { - await ExecuteNonQueryHelperAsync(cmd); + await ExecuteNonQueryHelperAsync(cmd).ConfigureAwait(false); id = InsertStatements.AssignNonIncrementPrimaryKey(primaryKeyName, poco, preparedInsert); } else { - id = await _dbType.ExecuteInsertAsync(this, cmd, primaryKeyName, preparedInsert.PocoData.TableInfo.UseOutputClause, poco, preparedInsert.Rawvalues.ToArray()); + id = await _dbType.ExecuteInsertAsync(this, cmd, primaryKeyName, preparedInsert.PocoData.TableInfo.UseOutputClause, poco, preparedInsert.Rawvalues.ToArray()).ConfigureAwait(false); InsertStatements.AssignPrimaryKey(primaryKeyName, poco, id, preparedInsert); } @@ -104,7 +104,7 @@ public async Task InsertBulkAsync(IEnumerable pocos) try { OpenSharedConnectionInternal(); - await _dbType.InsertBulkAsync(this, pocos); + await _dbType.InsertBulkAsync(this, pocos).ConfigureAwait(false); } catch (Exception x) { @@ -143,7 +143,7 @@ public async Task InsertBatchAsync(IEnumerable pocos, BatchOptions op using (var cmd = CreateCommand(_sharedConnection, sql.SQL, sql.Arguments)) { - result += await ExecuteNonQueryHelperAsync(cmd); + result += await ExecuteNonQueryHelperAsync(cmd).ConfigureAwait(false); } } } @@ -188,7 +188,7 @@ public Task UpdateAsync(object poco, object primaryKeyValue, IEnumerable UpdateAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns) { return UpdateImp (tableName, primaryKeyName, poco, primaryKeyValue, columns, - async (sql, args, next) => next(await ExecuteAsync(sql, args)), Task.FromResult(0)); + async (sql, args, next) => next(await ExecuteAsync(sql, args).ConfigureAwait(false)), Task.FromResult(0)); } public async Task UpdateBatchAsync(IEnumerable> pocos, BatchOptions options = null) @@ -220,7 +220,7 @@ public async Task UpdateBatchAsync(IEnumerable> pocos, Ba using (var cmd = CreateCommand(_sharedConnection, sql.SQL, sql.Arguments)) { - result += await ExecuteNonQueryHelperAsync(cmd); + result += await ExecuteNonQueryHelperAsync(cmd).ConfigureAwait(false); } } } @@ -273,7 +273,7 @@ public ValueTask> PageAsync(long page, long itemsPerPage, string sql, return PageImp>>(page, itemsPerPage, sql, args, async (paged, thesql) => { - paged.Items = await (await QueryAsync(thesql)).ToListAsync(); + paged.Items = await (await QueryAsync(thesql).ConfigureAwait(false)).ToListAsync().ConfigureAwait(false); return paged; }); } @@ -307,12 +307,12 @@ public ValueTask> FetchAsync() public async ValueTask> FetchAsync(string sql, params object[] args) { - return await (await QueryAsync(sql, args)).ToListAsync(); + return await (await QueryAsync(sql, args).ConfigureAwait(false)).ToListAsync().ConfigureAwait(false); } public async ValueTask> FetchAsync(Sql sql) { - return await (await QueryAsync(sql)).ToListAsync(); + return await (await QueryAsync(sql).ConfigureAwait(false)).ToListAsync().ConfigureAwait(false); } public IAsyncQueryProviderWithIncludes QueryAsync() @@ -344,7 +344,7 @@ internal async Task> QueryAsync(T instance, Expression> QueryAsync(T instance, Expression SingleAsync(string sql, params object[] args) { - return await (await QueryAsync(sql, args)).SingleAsync(); + return await (await QueryAsync(sql, args).ConfigureAwait(false)).SingleAsync().ConfigureAwait(false); } public async ValueTask SingleAsync(Sql sql) { - return await (await QueryAsync(sql)).SingleAsync(); + return await (await QueryAsync(sql).ConfigureAwait(false)).SingleAsync().ConfigureAwait(false); } public async ValueTask SingleOrDefaultAsync(string sql, params object[] args) { - return await (await QueryAsync(sql, args)).SingleOrDefaultAsync(); + return await (await QueryAsync(sql, args).ConfigureAwait(false)).SingleOrDefaultAsync().ConfigureAwait(false); } public async ValueTask SingleOrDefaultAsync(Sql sql) { - return await (await QueryAsync(sql)).SingleOrDefaultAsync(); + return await (await QueryAsync(sql).ConfigureAwait(false)).SingleOrDefaultAsync().ConfigureAwait(false); } public async ValueTask SingleByIdAsync(object primaryKey) { var sql = GenerateSingleByIdSql(primaryKey); - return await (await QueryAsync(sql)).SingleAsync(); + return await (await QueryAsync(sql).ConfigureAwait(false)).SingleAsync().ConfigureAwait(false); } public async ValueTask SingleOrDefaultByIdAsync(object primaryKey) { var sql = GenerateSingleByIdSql(primaryKey); - return await (await QueryAsync(sql)).SingleOrDefaultAsync(); + return await (await QueryAsync(sql).ConfigureAwait(false)).SingleOrDefaultAsync().ConfigureAwait(false); } public async ValueTask FirstAsync(string sql, params object[] args) { - return await (await QueryAsync(sql, args)).FirstAsync(); + return await (await QueryAsync(sql, args).ConfigureAwait(false)).FirstAsync().ConfigureAwait(false); } public async ValueTask FirstAsync(Sql sql) { - return await (await QueryAsync(sql)).FirstAsync(); + return await (await QueryAsync(sql).ConfigureAwait(false)).FirstAsync().ConfigureAwait(false); } public async ValueTask FirstOrDefaultAsync(string sql, params object[] args) { - return await (await QueryAsync(sql, args)).FirstOrDefaultAsync(); + return await (await QueryAsync(sql, args).ConfigureAwait(false)).FirstOrDefaultAsync().ConfigureAwait(false); } public async ValueTask FirstOrDefaultAsync(Sql sql) { - return await (await QueryAsync(sql)).FirstOrDefaultAsync(); + return await (await QueryAsync(sql).ConfigureAwait(false)).FirstOrDefaultAsync().ConfigureAwait(false); } public Task ExecuteAsync(string sql, params object[] args) @@ -429,7 +429,7 @@ public async Task ExecuteAsync(Sql Sql) OpenSharedConnectionInternal(); using (var cmd = CreateCommand(_sharedConnection, sql, args)) { - var result = await ExecuteNonQueryHelperAsync(cmd); + var result = await ExecuteNonQueryHelperAsync(cmd).ConfigureAwait(false); return result; } } @@ -459,7 +459,7 @@ public async Task ExecuteScalarAsync(Sql Sql) OpenSharedConnectionInternal(); using (var cmd = CreateCommand(_sharedConnection, sql, args)) { - object val = await ExecuteScalarHelperAsync(cmd); + object val = await ExecuteScalarHelperAsync(cmd).ConfigureAwait(false); if (val == null || val == DBNull.Value) return default(T); @@ -484,7 +484,7 @@ public async Task ExecuteScalarAsync(Sql Sql) internal async Task ExecuteNonQueryHelperAsync(DbCommand cmd) { DoPreExecute(cmd); - var result = await ExecutionHookAsync(() => _dbType.ExecuteNonQueryAsync(this, cmd)); + var result = await ExecutionHookAsync(() => _dbType.ExecuteNonQueryAsync(this, cmd)).ConfigureAwait(false); OnExecutedCommandInternal(cmd); return result; } @@ -492,7 +492,7 @@ internal async Task ExecuteNonQueryHelperAsync(DbCommand cmd) internal async Task ExecuteScalarHelperAsync(DbCommand cmd) { DoPreExecute(cmd); - var result = await ExecutionHookAsync(() => _dbType.ExecuteScalarAsync(this, cmd)); + var result = await ExecutionHookAsync(() => _dbType.ExecuteScalarAsync(this, cmd)).ConfigureAwait(false); OnExecutedCommandInternal(cmd); return result; } @@ -500,7 +500,7 @@ internal async Task ExecuteScalarHelperAsync(DbCommand cmd) internal async Task ExecuteReaderHelperAsync(DbCommand cmd) { DoPreExecute(cmd); - var reader = await ExecutionHookAsync(() => _dbType.ExecuteReaderAsync(this, cmd)); + var reader = await ExecutionHookAsync(() => _dbType.ExecuteReaderAsync(this, cmd)).ConfigureAwait(false); OnExecutedCommandInternal(cmd); return reader; } diff --git a/src/NPoco/Database.cs b/src/NPoco/Database.cs index 7b256cf8..019cabfe 100644 --- a/src/NPoco/Database.cs +++ b/src/NPoco/Database.cs @@ -866,7 +866,7 @@ private async IAsyncEnumerable ReadAsync(Type type, object instance, DbDat T poco; try { - if (!await r.ReadAsync()) yield break; + if (!await r.ReadAsync().ConfigureAwait(false)) yield break; poco = (T)factory.Map(r, instance); } catch (Exception x) @@ -916,7 +916,7 @@ private async IAsyncEnumerable ReadOneToManyAsync(T instance, DbDataReader T poco; try { - if (!await r.ReadAsync()) break; + if (!await r.ReadAsync().ConfigureAwait(false)) break; poco = (T)factory.Map(r, instance); } catch (Exception x) @@ -2009,7 +2009,7 @@ protected virtual T ExecutionHook(Func action) protected virtual async Task ExecutionHookAsync(Func> action) { - return await action(); + return await action().ConfigureAwait(false); } int IDatabaseHelpers.ExecuteNonQueryHelper(DbCommand cmd) => ExecuteNonQueryHelper(cmd); diff --git a/src/NPoco/DatabaseType.cs b/src/NPoco/DatabaseType.cs index 387cbf5b..1a263198 100644 --- a/src/NPoco/DatabaseType.cs +++ b/src/NPoco/DatabaseType.cs @@ -228,7 +228,7 @@ public virtual object ExecuteInsert(Database db, DbCommand cmd, string primar public virtual async Task ExecuteInsertAsync(Database db, DbCommand cmd, string primaryKeyName, bool useOutputClause, T poco, object[] args) { cmd.CommandText += ";\nSELECT @@IDENTITY AS NewID;"; - return await db.ExecuteScalarHelperAsync(cmd); + return await db.ExecuteScalarHelperAsync(cmd).ConfigureAwait(false); } public virtual void InsertBulk(IDatabase db, IEnumerable pocos) @@ -243,7 +243,7 @@ public virtual async Task InsertBulkAsync(IDatabase db, IEnumerable pocos) { foreach (var poco in pocos) { - await db.InsertAsync(poco); + await db.InsertAsync(poco).ConfigureAwait(false); } } diff --git a/src/NPoco/DatabaseTypes/FirebirdDatabaseType.cs b/src/NPoco/DatabaseTypes/FirebirdDatabaseType.cs index cfbef637..20e749d7 100644 --- a/src/NPoco/DatabaseTypes/FirebirdDatabaseType.cs +++ b/src/NPoco/DatabaseTypes/FirebirdDatabaseType.cs @@ -81,11 +81,11 @@ public override async Task ExecuteInsertAsync(Database db, DbCommand if (primaryKeyName != null) { var param = AdjustSqlInsertCommandText(cmd, primaryKeyName); - await db.ExecuteNonQueryHelperAsync(cmd); + await db.ExecuteNonQueryHelperAsync(cmd).ConfigureAwait(false); return param.Value; } - await db.ExecuteNonQueryHelperAsync(cmd); + await db.ExecuteNonQueryHelperAsync(cmd).ConfigureAwait(false); return -1; } diff --git a/src/NPoco/DatabaseTypes/OracleDatabaseType.cs b/src/NPoco/DatabaseTypes/OracleDatabaseType.cs index f4e5ff93..9ee21c1c 100644 --- a/src/NPoco/DatabaseTypes/OracleDatabaseType.cs +++ b/src/NPoco/DatabaseTypes/OracleDatabaseType.cs @@ -71,11 +71,11 @@ public override async Task ExecuteInsertAsync(Database db, DbCommand if (primaryKeyName != null) { var param = AdjustSqlInsertCommandText(cmd, primaryKeyName); - await db.ExecuteNonQueryHelperAsync(cmd); + await db.ExecuteNonQueryHelperAsync(cmd).ConfigureAwait(false); return param.Value; } - await db.ExecuteNonQueryHelperAsync(cmd); + await db.ExecuteNonQueryHelperAsync(cmd).ConfigureAwait(false); return -1; } diff --git a/src/NPoco/DatabaseTypes/PostgreSQLDatabaseType.cs b/src/NPoco/DatabaseTypes/PostgreSQLDatabaseType.cs index 61b0721c..71211e25 100644 --- a/src/NPoco/DatabaseTypes/PostgreSQLDatabaseType.cs +++ b/src/NPoco/DatabaseTypes/PostgreSQLDatabaseType.cs @@ -41,10 +41,10 @@ public override async Task ExecuteInsertAsync(Database db, DbCommand if (primaryKeyName != null) { AdjustSqlInsertCommandText(cmd, primaryKeyName); - return await db.ExecuteScalarHelperAsync(cmd); + return await db.ExecuteScalarHelperAsync(cmd).ConfigureAwait(false); } - await db.ExecuteNonQueryHelperAsync(cmd); + await db.ExecuteNonQueryHelperAsync(cmd).ConfigureAwait(false); return -1; } diff --git a/src/NPoco/DatabaseTypes/SQLiteDatabaseType.cs b/src/NPoco/DatabaseTypes/SQLiteDatabaseType.cs index 92b8d859..869df7af 100644 --- a/src/NPoco/DatabaseTypes/SQLiteDatabaseType.cs +++ b/src/NPoco/DatabaseTypes/SQLiteDatabaseType.cs @@ -37,10 +37,10 @@ public override async Task ExecuteInsertAsync(Database db, DbCommand if (primaryKeyName != null) { AdjustSqlInsertCommandText(cmd); - return await db.ExecuteScalarHelperAsync(cmd); + return await db.ExecuteScalarHelperAsync(cmd).ConfigureAwait(false); } - await db.ExecuteNonQueryHelperAsync(cmd); + await db.ExecuteNonQueryHelperAsync(cmd).ConfigureAwait(false); return -1; } diff --git a/src/NPoco/Linq/SimpleQueryProvider.cs b/src/NPoco/Linq/SimpleQueryProvider.cs index bc11ea0c..b736ef50 100644 --- a/src/NPoco/Linq/SimpleQueryProvider.cs +++ b/src/NPoco/Linq/SimpleQueryProvider.cs @@ -197,12 +197,12 @@ private IAsyncQueryProviderWithIncludes QueryProviderWithIncludes(Expression public async ValueTask> ToList() { - return await (await ToEnumerable()).ToListAsync(); + return await (await ToEnumerable().ConfigureAwait(false)).ToListAsync().ConfigureAwait(false); } public async ValueTask ToArray() { - return await (await ToEnumerable()).ToArrayAsync(); + return await (await ToEnumerable().ConfigureAwait(false)).ToArrayAsync().ConfigureAwait(false); } public Task> ToEnumerable() @@ -218,7 +218,7 @@ public ValueTask FirstOrDefault() public async ValueTask FirstOrDefault(Expression> whereExpression) { AddWhere(whereExpression); - return await (await ToEnumerable()).FirstOrDefaultAsync(); + return await (await ToEnumerable().ConfigureAwait(false)).FirstOrDefaultAsync().ConfigureAwait(false); } public ValueTask First() @@ -229,7 +229,7 @@ public ValueTask First() public async ValueTask First(Expression> whereExpression) { AddWhere(null); - return await (await ToEnumerable()).FirstAsync(); + return await (await ToEnumerable().ConfigureAwait(false)).FirstAsync().ConfigureAwait(false); } public ValueTask SingleOrDefault() @@ -240,7 +240,7 @@ public ValueTask SingleOrDefault() public async ValueTask SingleOrDefault(Expression> whereExpression) { AddWhere(whereExpression); - return await (await ToEnumerable()).SingleOrDefaultAsync(); + return await (await ToEnumerable().ConfigureAwait(false)).SingleOrDefaultAsync().ConfigureAwait(false); } public ValueTask Single() @@ -251,7 +251,7 @@ public ValueTask Single() public async ValueTask Single(Expression> whereExpression) { AddWhere(whereExpression); - return await (await ToEnumerable()).SingleAsync(); + return await (await ToEnumerable().ConfigureAwait(false)).SingleAsync().ConfigureAwait(false); } public ValueTask Count() @@ -263,7 +263,7 @@ public async ValueTask Count(Expression> whereExpression) { AddWhere(whereExpression); var sql = _buildComplexSql.BuildJoin(_database, _sqlExpression, _joinSqlExpressions.Values.ToList(), null, true, false); - return await _database.ExecuteScalarAsync(sql); + return await _database.ExecuteScalarAsync(sql).ConfigureAwait(false); } public ValueTask Any() @@ -273,7 +273,7 @@ public ValueTask Any() public async ValueTask Any(Expression> whereExpression) { - return (await Count(whereExpression)) > 0; + return (await Count(whereExpression).ConfigureAwait(false)) > 0; } public async ValueTask> ToPage(int page, int pageSize) @@ -287,7 +287,7 @@ public async ValueTask> ToPage(int page, int pageSize) var result = new Page(); result.CurrentPage = page; result.ItemsPerPage = pageSize; - result.TotalItems = await Count(); + result.TotalItems = await Count().ConfigureAwait(false); result.TotalPages = result.TotalItems / pageSize; if ((result.TotalItems % pageSize) != 0) result.TotalPages++; @@ -296,7 +296,7 @@ public async ValueTask> ToPage(int page, int pageSize) _sqlExpression = _sqlExpression.Limit(offset, pageSize); - result.Items = await ToList(); + result.Items = await ToList().ConfigureAwait(false); return result; } @@ -304,18 +304,18 @@ public async ValueTask> ToPage(int page, int pageSize) public async ValueTask> ProjectTo(Expression> projectionExpression) { var sql = _buildComplexSql.GetSqlForProjection(projectionExpression, false); - return await (await _database.QueryAsync(sql)).Select(projectionExpression.Compile()).ToListAsync(); + return await (await _database.QueryAsync(sql).ConfigureAwait(false)).Select(projectionExpression.Compile()).ToListAsync().ConfigureAwait(false); } public async ValueTask> Distinct() { - return await (await _database.QueryAsync(new Sql(_sqlExpression.Context.ToSelectStatement(true, true), _sqlExpression.Context.Params))).ToListAsync(); + return await (await _database.QueryAsync(new Sql(_sqlExpression.Context.ToSelectStatement(true, true), _sqlExpression.Context.Params)).ConfigureAwait(false)).ToListAsync().ConfigureAwait(false); } public async ValueTask> Distinct(Expression> projectionExpression) { var sql = _buildComplexSql.GetSqlForProjection(projectionExpression, true); - return await (await _database.QueryAsync(sql)).Select(projectionExpression.Compile()).ToListAsync(); + return await (await _database.QueryAsync(sql).ConfigureAwait(false)).Select(projectionExpression.Compile()).ToListAsync().ConfigureAwait(false); } public IAsyncQueryProvider Where(Expression> whereExpression) diff --git a/src/NPoco/Linq/UpdateQueryProvider.cs b/src/NPoco/Linq/UpdateQueryProvider.cs index dd517480..0b29b8ed 100644 --- a/src/NPoco/Linq/UpdateQueryProvider.cs +++ b/src/NPoco/Linq/UpdateQueryProvider.cs @@ -92,7 +92,7 @@ public IAsyncUpdateQueryProvider OnlyFields(Expression> onlyF public async Task Execute(T obj) { var updateStatement = _sqlExpression.Context.ToUpdateStatement(obj, _excludeDefaults, _onlyFields); - return await _database.ExecuteAsync(updateStatement, _sqlExpression.Context.Params); + return await _database.ExecuteAsync(updateStatement, _sqlExpression.Context.Params).ConfigureAwait(false); } } } From 65dc6a4e8747edb958e50063c4bb09fceffcb0af Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Tue, 29 Sep 2020 23:45:16 +1000 Subject: [PATCH 09/42] Ability to use Tuples as parameters using the @Item1, @Item2 etc names #358 --- src/NPoco/ParameterHelper.cs | 11 ++++++- .../DecoratedTests/CRUDTests/UpdateTests.cs | 33 ++++++++++++++----- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/NPoco/ParameterHelper.cs b/src/NPoco/ParameterHelper.cs index 9e09821e..bf2fd6e9 100644 --- a/src/NPoco/ParameterHelper.cs +++ b/src/NPoco/ParameterHelper.cs @@ -65,13 +65,22 @@ private static string ProcessParam(ref string sql, string rawParam, object[] arg } } - var pi = o.GetType().GetProperty(param); + var type = o.GetType(); + var pi = type.GetProperty(param); if (pi != null) { arg_val = pi.GetValue(o, null); found = true; break; } + + var fi = type.GetField(param); + if (fi != null) + { + arg_val = fi.GetValue(o); + found = true; + break; + } } if (!found) diff --git a/test/NPoco.Tests/DecoratedTests/CRUDTests/UpdateTests.cs b/test/NPoco.Tests/DecoratedTests/CRUDTests/UpdateTests.cs index 77ae9696..400eed2c 100644 --- a/test/NPoco.Tests/DecoratedTests/CRUDTests/UpdateTests.cs +++ b/test/NPoco.Tests/DecoratedTests/CRUDTests/UpdateTests.cs @@ -16,7 +16,7 @@ public void UpdatePrimaryKeyObject() Assert.IsNotNull(poco); poco.Age = InMemoryUsers[1].Age + 100; - poco.Savings = (Decimal)1234.23; + poco.Savings = (Decimal) 1234.23; Database.Update(poco); var verify = Database.SingleOrDefaultById(InMemoryUsers[1].UserId); @@ -35,7 +35,7 @@ public void UpdatePrimaryKeyObjectOverridingPrimaryKey() Assert.IsNotNull(poco); poco.Age = InMemoryUsers[1].Age + 100; - poco.Savings = (Decimal)1234.23; + poco.Savings = (Decimal) 1234.23; Database.Update(poco, InMemoryUsers[2].UserId); var verify = Database.SingleOrDefaultById(InMemoryUsers[2].UserId); @@ -81,8 +81,8 @@ public void UpdateWithFields() Assert.IsNotNull(poco); poco.Age = poco.Age + 100; - poco.Savings = (Decimal)1234.23; - Database.Update(poco, x=>x.Age); + poco.Savings = (Decimal) 1234.23; + Database.Update(poco, x => x.Age); var verify = Database.SingleOrDefaultById(1); Assert.IsNotNull(verify); @@ -98,12 +98,12 @@ public void UpdatePrimaryKeyVersionConcurrencyException() { var poco1 = Database.SingleOrDefaultById(InMemoryUsers[1].UserId); var poco2 = Database.SingleOrDefaultById(InMemoryUsers[1].UserId); - + poco1.Age = 100; Database.Update(poco1); poco2.Age = 200; - + Assert.Throws(() => Database.Update(poco2)); } @@ -111,7 +111,7 @@ public void UpdatePrimaryKeyVersionConcurrencyException() public void UpdatePrimaryKeyNoVersionConcurrencyException() { var poco1 = Database.SingleOrDefaultById(InMemoryUsers[1].UserId); - + poco1.Age = 100; Database.Update(poco1); @@ -168,8 +168,25 @@ public void UpdateBatchTest() foreach (var u in result) { Assert.AreEqual(30, u.Age); - } + } + Assert.AreEqual(14, updated); } + + [Test] + public void UpdateRecordUsingTuples() + { + var poco = Database.SingleOrDefaultById(InMemoryUsers[1].UserId); + Assert.IsNotNull(poco); + + var record = (2, "Timmy", InMemoryUsers[1].UserId); + Database.Execute("update Users set name = @Item2, age = @Item1 where userid = @Item3", record); + + var verify = Database.SingleOrDefaultById(InMemoryUsers[1].UserId); + Assert.IsNotNull(verify); + + Assert.AreEqual(record.Item2, verify.Name); + Assert.AreEqual(record.Item3, verify.Age); + } } } \ No newline at end of file From b2bcf6ab6cb7ccc69d1da91e46b56616a8fe3b5b Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Wed, 30 Sep 2020 11:30:21 +1000 Subject: [PATCH 10/42] Add concurrency checking to deletes #567 --- src/NPoco/AsyncDatabase.cs | 2 +- src/NPoco/Database.cs | 33 ++++++++- test/NPoco.Tests/Async/DeleteAsyncTests.cs | 23 +++++++ .../DecoratedTests/CRUDTests/DeleteTests.cs | 69 +++++++++++++++++++ 4 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 test/NPoco.Tests/Async/DeleteAsyncTests.cs create mode 100644 test/NPoco.Tests/DecoratedTests/CRUDTests/DeleteTests.cs diff --git a/src/NPoco/AsyncDatabase.cs b/src/NPoco/AsyncDatabase.cs index b90f8e1a..8f42cbae 100644 --- a/src/NPoco/AsyncDatabase.cs +++ b/src/NPoco/AsyncDatabase.cs @@ -255,7 +255,7 @@ public Task DeleteAsync(string tableName, string primaryKeyName, object poc public virtual Task DeleteAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue) { - return DeleteImp(tableName, primaryKeyName, poco, primaryKeyValue, ExecuteAsync, Task.FromResult(0)); + return DeleteImp(tableName, primaryKeyName, poco, primaryKeyValue, async (x, y, next) => next(await ExecuteAsync(x, y).ConfigureAwait(false)), Task.FromResult(0)); } public IAsyncDeleteQueryProvider DeleteManyAsync() diff --git a/src/NPoco/Database.cs b/src/NPoco/Database.cs index 019cabfe..cba389f3 100644 --- a/src/NPoco/Database.cs +++ b/src/NPoco/Database.cs @@ -1760,10 +1760,10 @@ public int Delete(string tableName, string primaryKeyName, object poco) public virtual int Delete(string tableName, string primaryKeyName, object poco, object primaryKeyValue) { - return DeleteImp(tableName, primaryKeyName, poco, primaryKeyValue, Execute, 0); + return DeleteImp(tableName, primaryKeyName, poco, primaryKeyValue, (x, y, next) => next(Execute(x, y)), 0); } - private TRet DeleteImp(string tableName, string primaryKeyName, object poco, object primaryKeyValue, Func executeFunc, TRet defaultRet) + private TRet DeleteImp(string tableName, string primaryKeyName, object poco, object primaryKeyValue, Func, TRet> executeFunc, TRet defaultRet) { if (!OnDeletingInternal(new DeleteContext(poco, tableName, primaryKeyName, primaryKeyValue))) return defaultRet; @@ -1774,7 +1774,34 @@ private TRet DeleteImp(string tableName, string primaryKeyName, object poc // Do it var index = 0; var sql = string.Format("DELETE FROM {0} WHERE {1}", _dbType.EscapeTableName(tableName), BuildPrimaryKeySql(this, primaryKeyValuePairs, ref index)); - return executeFunc(sql, primaryKeyValuePairs.Select(x => x.Value).ToArray()); + var rawValues = primaryKeyValuePairs.Select(x => x.Value).ToList(); + + var versionColumn = pd?.AllColumns.SingleOrDefault(x => x.VersionColumn); + string versionName = null; + object versionValue = null; + if (versionColumn != null) + { + versionName = versionColumn.ColumnName; + versionValue = versionColumn.GetColumnValue(pd, poco, this.ProcessMapper); + + if (!string.IsNullOrEmpty(versionName)) + { + sql += $" AND {DatabaseType.EscapeSqlIdentifier(versionName)} = @{index++}"; + rawValues.Add(versionValue); + } + } + + var result = executeFunc(sql, rawValues.ToArray(), id => + { + if (id == 0 && !string.IsNullOrEmpty(versionName) && VersionException == VersionExceptionHandling.Exception) + { + throw new DBConcurrencyException(string.Format("A Concurrency update occurred in table '{0}' for primary key value(s) = '{1}' and version = '{2}'", tableName, string.Join(",", primaryKeyValuePairs.Values.Select(x => x.ToString()).ToArray()), versionValue)); + } + + return id; + }); + + return result; } public int Delete(object poco) diff --git a/test/NPoco.Tests/Async/DeleteAsyncTests.cs b/test/NPoco.Tests/Async/DeleteAsyncTests.cs new file mode 100644 index 00000000..6c9b25f1 --- /dev/null +++ b/test/NPoco.Tests/Async/DeleteAsyncTests.cs @@ -0,0 +1,23 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using NPoco.Tests.Common; +using NUnit.Framework; + +namespace NPoco.Tests.Async +{ + public class DeleteAsyncTests : BaseDBFluentTest + { + [Test] + public async Task DeletePrimaryKeyObject() + { + var poco = await Database.QueryAsync().Where(x => x.UserId == InMemoryUsers[1].UserId).SingleOrDefault(); + Assert.IsNotNull(poco); + + await Database.DeleteAsync(poco); + + var verify = await Database.QueryAsync().Where(x => x.UserId == InMemoryUsers[1].UserId).SingleOrDefault(); + Assert.IsNull(verify); + } + } +} diff --git a/test/NPoco.Tests/DecoratedTests/CRUDTests/DeleteTests.cs b/test/NPoco.Tests/DecoratedTests/CRUDTests/DeleteTests.cs new file mode 100644 index 00000000..ee9d1071 --- /dev/null +++ b/test/NPoco.Tests/DecoratedTests/CRUDTests/DeleteTests.cs @@ -0,0 +1,69 @@ +using System; +using System.Data; +using System.Linq; +using NPoco.Tests.Common; +using NUnit.Framework; + +namespace NPoco.Tests.DecoratedTests.CRUDTests +{ + [TestFixture] + public class DeleteTests : BaseDBDecoratedTest + { + [Test] + public void DeletePrimaryKeyObject() + { + var poco = Database.SingleOrDefaultById(InMemoryUsers[1].UserId); + Assert.IsNotNull(poco); + + Database.Delete(poco); + + var verify = Database.SingleOrDefaultById(InMemoryUsers[1].UserId); + Assert.IsNull(verify); + } + + [Test] + public void DeleteCompositeKey() + { + var poco = Database.SingleOrDefault(@" + SELECT * + FROM CompositeObjects + WHERE Key1_ID = @0 AND Key2ID = @1 AND Key3ID = @2 + ", InMemoryCompositeObjects[1].Key1ID, InMemoryCompositeObjects[1].Key2ID, InMemoryCompositeObjects[1].Key3ID); + Assert.IsNotNull(poco); + + Database.Delete(poco); + + var verify = Database.SingleOrDefault(@" + SELECT * + FROM CompositeObjects + WHERE Key1_ID = @0 AND Key2ID = @1 AND Key3ID = @2 + ", InMemoryCompositeObjects[1].Key1ID, InMemoryCompositeObjects[1].Key2ID, InMemoryCompositeObjects[1].Key3ID); + Assert.IsNull(verify); + } + + [Test] + public void DeletePrimaryKeyVersionConcurrencyException() + { + var poco1 = Database.SingleOrDefaultById(InMemoryUsers[1].UserId); + var poco2 = Database.SingleOrDefaultById(InMemoryUsers[1].UserId); + + poco1.Age = 100; + Database.Update(poco1); + + Assert.Throws(() => Database.Delete(poco2)); + } + + + [Test] + public void DeletePrimaryKeyVersionIntConcurrencyException() + { + var poco1 = Database.SingleOrDefaultById(InMemoryUsers[1].UserId); + var poco2 = Database.SingleOrDefaultById(InMemoryUsers[1].UserId); + + poco1.Age = 100; + Database.Update(poco1); + + Assert.Throws(() => Database.Delete(poco2)); + } + } +} \ No newline at end of file From 041b7d6a7454e6491d9b2d1416b4700914105a51 Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Wed, 30 Sep 2020 11:42:53 +1000 Subject: [PATCH 11/42] Fix parameter escaping for MySql as per #492 --- src/NPoco/Expressions/MySqlSqlExpression.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/NPoco/Expressions/MySqlSqlExpression.cs b/src/NPoco/Expressions/MySqlSqlExpression.cs index b7c79607..7657c96c 100644 --- a/src/NPoco/Expressions/MySqlSqlExpression.cs +++ b/src/NPoco/Expressions/MySqlSqlExpression.cs @@ -16,8 +16,9 @@ protected override string EscapeParam(object par) { var param = par.ToString().ToUpper(); param = param - .Replace("\\", EscapeChar + EscapeChar) - .Replace("_", EscapeChar + "_"); + .Replace("\\", EscapeChar) + .Replace("_", "\\_") + .Replace("%", "\\%"); return param; } } From 3b1d085af0838c808637856e71486cd07a05b48f Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Wed, 30 Sep 2020 13:59:07 +1000 Subject: [PATCH 12/42] Add Linq support for DateTime.Year etc #484 --- src/NPoco/DatabaseTypes/OracleDatabaseType.cs | 8 +++- .../DatabaseTypes/PostgreSQLDatabaseType.cs | 6 +++ .../Expressions/FirebirdSqlExpression.cs | 19 +++++++++ src/NPoco/Expressions/MySqlSqlExpression.cs | 18 ++++++++ src/NPoco/Expressions/OracleExpression.cs | 31 ++++++++++++++ src/NPoco/Expressions/PostgreSQLExpression.cs | 30 +++++++++++++ src/NPoco/Expressions/SqlExpression.cs | 42 +++++++++++++++++++ test/NPoco.Tests/Common/User.cs | 2 +- .../QueryTests/ConverterFluentTest.cs | 2 +- .../QueryTests/QueryProviderTests.cs | 20 ++++++++- 10 files changed, 173 insertions(+), 5 deletions(-) create mode 100644 src/NPoco/Expressions/OracleExpression.cs create mode 100644 src/NPoco/Expressions/PostgreSQLExpression.cs diff --git a/src/NPoco/DatabaseTypes/OracleDatabaseType.cs b/src/NPoco/DatabaseTypes/OracleDatabaseType.cs index 9ee21c1c..b3d5b8c6 100644 --- a/src/NPoco/DatabaseTypes/OracleDatabaseType.cs +++ b/src/NPoco/DatabaseTypes/OracleDatabaseType.cs @@ -1,4 +1,5 @@ -using System; +using NPoco.Expressions; +using System; using System.Data; using System.Data.Common; using System.Reflection; @@ -8,6 +9,11 @@ namespace NPoco.DatabaseTypes { public class OracleDatabaseType : DatabaseType { + public override SqlExpression ExpressionVisitor(IDatabase db, PocoData pocoData, bool prefixTableName) + { + return new OracleExpression(db, pocoData, prefixTableName); + } + public override string GetParameterPrefix(string connectionString) { return ":"; diff --git a/src/NPoco/DatabaseTypes/PostgreSQLDatabaseType.cs b/src/NPoco/DatabaseTypes/PostgreSQLDatabaseType.cs index 71211e25..8847cfbd 100644 --- a/src/NPoco/DatabaseTypes/PostgreSQLDatabaseType.cs +++ b/src/NPoco/DatabaseTypes/PostgreSQLDatabaseType.cs @@ -1,3 +1,4 @@ +using NPoco.Expressions; using System.Data; using System.Data.Common; using System.Threading.Tasks; @@ -6,6 +7,11 @@ namespace NPoco.DatabaseTypes { public class PostgreSQLDatabaseType : DatabaseType { + public override SqlExpression ExpressionVisitor(IDatabase db, PocoData pocoData, bool prefixTableName) + { + return new PostgreSQLExpression(db, pocoData, prefixTableName); + } + public override object MapParameterValue(object value) { // Don't map bools to ints in PostgreSQL diff --git a/src/NPoco/Expressions/FirebirdSqlExpression.cs b/src/NPoco/Expressions/FirebirdSqlExpression.cs index a372431b..7c3d2a5a 100644 --- a/src/NPoco/Expressions/FirebirdSqlExpression.cs +++ b/src/NPoco/Expressions/FirebirdSqlExpression.cs @@ -1,3 +1,5 @@ +using System; + namespace NPoco.Expressions { public class FirebirdSqlExpression : SqlExpression @@ -18,5 +20,22 @@ protected override string SubstringStatement(PartialSqlString columnName, int st else return string.Format("substring({0} FROM {1})", columnName, startIndex); } + + protected override string GetDateTimeSql(string memberName, object m) + { + // http://www.firebirdsql.org/refdocs/langrefupd21.html + string sql; + switch (memberName) + { + case "Year": sql = $"EXTRACT(YEAR FROM {m})"; break; + case "Month": sql = $"EXTRACT(MONTH FROM {m})"; break; + case "Day": sql = $"EXTRACT(DAY FROM {m})"; break; + case "Hour": sql = $"EXTRACT(HOUR FROM {m})"; break; + case "Minute": sql = $"EXTRACT(MINUTE FROM {m})"; break; + case "Second": sql = $"EXTRACT(SECOND FROM {m})"; break; + default: throw new NotSupportedException("Not Supported " + memberName); + } + return sql; + } } } \ No newline at end of file diff --git a/src/NPoco/Expressions/MySqlSqlExpression.cs b/src/NPoco/Expressions/MySqlSqlExpression.cs index 7657c96c..1fc36f10 100644 --- a/src/NPoco/Expressions/MySqlSqlExpression.cs +++ b/src/NPoco/Expressions/MySqlSqlExpression.cs @@ -1,3 +1,5 @@ +using System; + namespace NPoco.Expressions { public class MySqlSqlExpression : SqlExpression @@ -21,5 +23,21 @@ protected override string EscapeParam(object par) .Replace("%", "\\%"); return param; } + + protected override string GetDateTimeSql(string memberName, object m) + { + string sql; + switch (memberName) + { + case "Year": sql = $"YEAR({m})"; break; + case "Month": sql = $"MONTH({m})"; break; + case "Day": sql = $"DAY({m})"; break; + case "Hour": sql = $"HOUR({m})"; break; + case "Minute": sql = $"MINUTE({m})"; break; + case "Second": sql = $"SECOND({m})"; break; + default: throw new NotSupportedException("Not Supported " + memberName); + } + return sql; + } } } \ No newline at end of file diff --git a/src/NPoco/Expressions/OracleExpression.cs b/src/NPoco/Expressions/OracleExpression.cs new file mode 100644 index 00000000..2aad5d64 --- /dev/null +++ b/src/NPoco/Expressions/OracleExpression.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NPoco.Expressions +{ + public class OracleExpression : SqlExpression + { + public OracleExpression(IDatabase database, PocoData pocoData, bool prefixTableName) : base(database, pocoData, prefixTableName) + { + } + + protected override string GetDateTimeSql(string memberName, object m) + { + //Oracle + // http://blog.csdn.net/gccr/article/details/1802740 + string sql; + switch (memberName) + { + case "Year": sql = $"EXTRACT(YEAR FROM TIMESTAMP {m})"; break; + case "Month": sql = $"EXTRACT(MONTH FROM TIMESTAMP {m})"; break; + case "Day": sql = $"EXTRACT(DAY FROM TIMESTAMP {m})"; break; + case "Hour": sql = $"EXTRACT(HOUR FROM TIMESTAMP {m})"; break; + case "Minute": sql = $"EXTRACT(MINUTE FROM TIMESTAMP {m})"; break; + case "Second": sql = $"EXTRACT(SECOND FROM TIMESTAMP {m})"; break; + default: throw new NotSupportedException("Not Supported " + memberName); + } + return sql; + } + } +} diff --git a/src/NPoco/Expressions/PostgreSQLExpression.cs b/src/NPoco/Expressions/PostgreSQLExpression.cs new file mode 100644 index 00000000..54ad9ce8 --- /dev/null +++ b/src/NPoco/Expressions/PostgreSQLExpression.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NPoco.Expressions +{ + public class PostgreSQLExpression : SqlExpression + { + public PostgreSQLExpression(IDatabase database, PocoData pocoData, bool prefixTableName) : base(database, pocoData, prefixTableName) + { + } + protected override string GetDateTimeSql(string memberName, object m) + { + //PostgreSQL + // https://www.postgresql.org/docs/9.1/static/functions-datetime.html + string sql; + switch (memberName) + { + case "Year": sql = $"EXTRACT(YEAR FROM TIMESTAMP {m})"; break; + case "Month": sql = $"EXTRACT(MONTH FROM TIMESTAMP {m})"; break; + case "Day": sql = $"EXTRACT(DAY FROM TIMESTAMP {m})"; break; + case "Hour": sql = $"EXTRACT(HOUR FROM TIMESTAMP {m})"; break; + case "Minute": sql = $"EXTRACT(MINUTE FROM TIMESTAMP {m})"; break; + case "Second": sql = $"EXTRACT(SECOND FROM TIMESTAMP {m})"; break; + default: throw new NotSupportedException("Not Supported " + memberName); + } + return sql; + } + } +} diff --git a/src/NPoco/Expressions/SqlExpression.cs b/src/NPoco/Expressions/SqlExpression.cs index 5fc64676..9e398afe 100644 --- a/src/NPoco/Expressions/SqlExpression.cs +++ b/src/NPoco/Expressions/SqlExpression.cs @@ -919,6 +919,32 @@ protected virtual object VisitMemberAccess(MemberExpression m) m = m.Expression as MemberExpression; } + if (m.Member.DeclaringType == typeof(DateTime) || m.Member.DeclaringType == typeof(DateTime?)) + { + if (m.Expression is MemberExpression m1) + { + var p = Expression.Convert(m1, typeof(object)); + if (p.NodeType == ExpressionType.Convert) + { + var pp = m1.Expression as ParameterExpression; + if (pp == null) + { + m1 = m1.Expression as MemberExpression; + if (m1 != null) + { + pp = m1.Expression as ParameterExpression; + } + } + if (pp != null) + { + if (m.Member.Name == "Value") + return Visit(m1); + return new PartialSqlString(GetDateTimeSql(m.Member.Name, Visit(m1))); + } + } + } + } + if (m.Expression != null && (m.Expression.NodeType == ExpressionType.Parameter || m.Expression.NodeType == ExpressionType.Convert @@ -1580,6 +1606,22 @@ protected virtual string SubstringStatement(PartialSqlString columnName, int sta else return string.Format("substring({0},{1},8000)", columnName, CreateParam(startIndex)); } + + protected virtual string GetDateTimeSql(string memberName, object m) + { + string sql; + switch (memberName) + { + case "Year": sql = $"DATEPART(YEAR,{m})"; break; + case "Month": sql = $"DATEPART(MONTH,{m})"; break; + case "Day": sql = $"DATEPART(DAY,{m})"; break; + case "Hour": sql = $"DATEPART(HOUR,{m})"; break; + case "Minute": sql = $"DATEPART(MINUTE,{m})"; break; + case "Second": sql = $"DATEPART(SECOND,{m})"; break; + default: throw new NotSupportedException("Not Supported " + memberName); + } + return sql; + } } public class PartialSqlString diff --git a/test/NPoco.Tests/Common/User.cs b/test/NPoco.Tests/Common/User.cs index ddf858e8..a474732d 100644 --- a/test/NPoco.Tests/Common/User.cs +++ b/test/NPoco.Tests/Common/User.cs @@ -13,7 +13,7 @@ public User() public int UserId { get; set; } public virtual string Name { get; set; } public int Age { get; set; } - public DateTime DateOfBirth { get; set; } + public DateTime? DateOfBirth { get; set; } public decimal Savings { get; set; } public bool IsMale { get; set; } public Guid? UniqueId { get; set; } diff --git a/test/NPoco.Tests/FluentTests/QueryTests/ConverterFluentTest.cs b/test/NPoco.Tests/FluentTests/QueryTests/ConverterFluentTest.cs index 6a1d3607..016fa3df 100644 --- a/test/NPoco.Tests/FluentTests/QueryTests/ConverterFluentTest.cs +++ b/test/NPoco.Tests/FluentTests/QueryTests/ConverterFluentTest.cs @@ -11,7 +11,7 @@ public class ConverterFluentTest : BaseDBFluentTest public void DateIsOfKindUtcWithSmartConventions() { var data = Database.SingleById(1); - Assert.AreEqual(DateTimeKind.Utc, data.DateOfBirth.Kind); + Assert.AreEqual(DateTimeKind.Utc, data.DateOfBirth?.Kind); } [Test] diff --git a/test/NPoco.Tests/FluentTests/QueryTests/QueryProviderTests.cs b/test/NPoco.Tests/FluentTests/QueryTests/QueryProviderTests.cs index 64c89e27..1334faa3 100644 --- a/test/NPoco.Tests/FluentTests/QueryTests/QueryProviderTests.cs +++ b/test/NPoco.Tests/FluentTests/QueryTests/QueryProviderTests.cs @@ -1,4 +1,5 @@ -using System.Globalization; +using System; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -440,7 +441,7 @@ private string FormatAge(User u) public void QueryWithProjectionAndMethod() { var users = Database.Query() - .ProjectTo(x => new ProjectUser2 { Age = x.Age, Date = x.DateOfBirth.ToString("yyyy-MM-dd") }); + .ProjectTo(x => new ProjectUser2 { Age = x.Age, Date = x.DateOfBirth.HasValue ? x.DateOfBirth.Value.ToString("yyyy-MM-dd") : "" }); Assert.AreEqual(21, users[0].Age); Assert.AreEqual("1969-01-01", users[0].Date); @@ -571,6 +572,21 @@ public void QueryWithIncludeInheritedReturnsNotNullObject() Assert.NotNull(ex[1].House); } + [Test] + public void QueryWithDateTimeYear() + { + var dt = new DateTime(1969, 1, 1); + var users2 = Database.Query().Where(x => x.DateOfBirth.Value.Year == dt.Year).ToList(); + Assert.AreEqual(1, users2.Count); + } + + [Test] + public void QueryWithDateTimeNullable() + { + var users2 = Database.Query().Where(x => x.DateOfBirth.HasValue).ToList(); + Assert.AreEqual(15, users2.Count); + } + //[Test] //public void QueryWithInheritedTypesAliasCorrectlyWithJoin() //{ From 3f7d956d85649fec5b854004ed3a204a67238a7e Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Wed, 30 Sep 2020 15:59:34 +1000 Subject: [PATCH 13/42] Adding BulkCopyTimeout parameter to BulkInsert #563 --- .../DatabaseTypes/SqlServerDatabaseType.cs | 8 +++---- src/NPoco.SqlServer/SqlBulkCopyHelper.cs | 21 +++++++++++-------- src/NPoco/AsyncDatabase.cs | 4 ++-- src/NPoco/Database.cs | 4 ++-- src/NPoco/DatabaseType.cs | 4 ++-- src/NPoco/IAsyncDatabase.cs | 2 +- src/NPoco/IDatabase.cs | 2 +- src/NPoco/InsertBulkOptions.cs | 11 ++++++++++ 8 files changed, 35 insertions(+), 21 deletions(-) create mode 100644 src/NPoco/InsertBulkOptions.cs diff --git a/src/NPoco.SqlServer/DatabaseTypes/SqlServerDatabaseType.cs b/src/NPoco.SqlServer/DatabaseTypes/SqlServerDatabaseType.cs index 241960ee..80f179c2 100644 --- a/src/NPoco.SqlServer/DatabaseTypes/SqlServerDatabaseType.cs +++ b/src/NPoco.SqlServer/DatabaseTypes/SqlServerDatabaseType.cs @@ -74,14 +74,14 @@ public override IsolationLevel GetDefaultTransactionIsolationLevel() return base.LookupDbType(type, name); } - public override void InsertBulk(IDatabase db, IEnumerable pocos) + public override void InsertBulk(IDatabase db, IEnumerable pocos, InsertBulkOptions options) { - SqlBulkCopyHelper.BulkInsert(db, pocos); + SqlBulkCopyHelper.BulkInsert(db, pocos, options); } - public override Task InsertBulkAsync(IDatabase db, IEnumerable pocos) + public override Task InsertBulkAsync(IDatabase db, IEnumerable pocos, InsertBulkOptions options) { - return SqlBulkCopyHelper.BulkInsertAsync(db, pocos); + return SqlBulkCopyHelper.BulkInsertAsync(db, pocos, options); } public override string GetProviderName() diff --git a/src/NPoco.SqlServer/SqlBulkCopyHelper.cs b/src/NPoco.SqlServer/SqlBulkCopyHelper.cs index 64457355..24ae7d5e 100644 --- a/src/NPoco.SqlServer/SqlBulkCopyHelper.cs +++ b/src/NPoco.SqlServer/SqlBulkCopyHelper.cs @@ -13,42 +13,45 @@ public class SqlBulkCopyHelper public static Func SqlConnectionResolver = dbConn => (SqlConnection)dbConn; public static Func SqlTransactionResolver = dbTran => (SqlTransaction)dbTran; - public static void BulkInsert(IDatabase db, IEnumerable list) + public static void BulkInsert(IDatabase db, IEnumerable list, InsertBulkOptions insertBulkOptions) { - BulkInsert(db, list, SqlBulkCopyOptions.Default); + BulkInsert(db, list, SqlBulkCopyOptions.Default, insertBulkOptions); } - public static void BulkInsert(IDatabase db, IEnumerable list, SqlBulkCopyOptions sqlBulkCopyOptions) + public static void BulkInsert(IDatabase db, IEnumerable list, SqlBulkCopyOptions sqlBulkCopyOptions, InsertBulkOptions insertBulkOptions) { using (var bulkCopy = new SqlBulkCopy(SqlConnectionResolver(db.Connection), sqlBulkCopyOptions, SqlTransactionResolver(db.Transaction))) { - var table = BuildBulkInsertDataTable(db, list, bulkCopy, sqlBulkCopyOptions); + var table = BuildBulkInsertDataTable(db, list, bulkCopy, sqlBulkCopyOptions, insertBulkOptions); bulkCopy.WriteToServer(table); } } - public static Task BulkInsertAsync(IDatabase db, IEnumerable list) + public static Task BulkInsertAsync(IDatabase db, IEnumerable list, InsertBulkOptions sqlBulkCopyOptions) { - return BulkInsertAsync(db, list, SqlBulkCopyOptions.Default); + return BulkInsertAsync(db, list, SqlBulkCopyOptions.Default, sqlBulkCopyOptions); } - public static async Task BulkInsertAsync(IDatabase db, IEnumerable list, SqlBulkCopyOptions sqlBulkCopyOptions) + public static async Task BulkInsertAsync(IDatabase db, IEnumerable list, SqlBulkCopyOptions sqlBulkCopyOptions, InsertBulkOptions insertBulkOptions) { using (var bulkCopy = new SqlBulkCopy(SqlConnectionResolver(db.Connection), sqlBulkCopyOptions, SqlTransactionResolver(db.Transaction))) { - var table = BuildBulkInsertDataTable(db, list, bulkCopy, sqlBulkCopyOptions); + var table = BuildBulkInsertDataTable(db, list, bulkCopy, sqlBulkCopyOptions, insertBulkOptions); await bulkCopy.WriteToServerAsync(table).ConfigureAwait(false); } } - private static DataTable BuildBulkInsertDataTable(IDatabase db, IEnumerable list, SqlBulkCopy bulkCopy, SqlBulkCopyOptions sqlBulkCopyOptions) + private static DataTable BuildBulkInsertDataTable(IDatabase db, IEnumerable list, SqlBulkCopy bulkCopy, SqlBulkCopyOptions sqlBulkCopyOptions, InsertBulkOptions insertBulkOptions) { var pocoData = db.PocoDataFactory.ForType(typeof (T)); bulkCopy.BatchSize = 4096; bulkCopy.DestinationTableName = pocoData.TableInfo.TableName; + if (insertBulkOptions?.BulkCopyTimeout != null) + bulkCopy.BulkCopyTimeout = insertBulkOptions.BulkCopyTimeout.Value; + var table = new DataTable(); var cols = pocoData.Columns.Where(x => { diff --git a/src/NPoco/AsyncDatabase.cs b/src/NPoco/AsyncDatabase.cs index 8f42cbae..3dfde912 100644 --- a/src/NPoco/AsyncDatabase.cs +++ b/src/NPoco/AsyncDatabase.cs @@ -99,12 +99,12 @@ private async Task InsertAsyncImp(PocoData pocoData, string tableName } } - public async Task InsertBulkAsync(IEnumerable pocos) + public async Task InsertBulkAsync(IEnumerable pocos, InsertBulkOptions options = null) { try { OpenSharedConnectionInternal(); - await _dbType.InsertBulkAsync(this, pocos).ConfigureAwait(false); + await _dbType.InsertBulkAsync(this, pocos, options).ConfigureAwait(false); } catch (Exception x) { diff --git a/src/NPoco/Database.cs b/src/NPoco/Database.cs index cba389f3..76adbd3d 100644 --- a/src/NPoco/Database.cs +++ b/src/NPoco/Database.cs @@ -1536,12 +1536,12 @@ public int InsertBatch(IEnumerable pocos, BatchOptions options = null) return result; } - public void InsertBulk(IEnumerable pocos) + public void InsertBulk(IEnumerable pocos, InsertBulkOptions options = null) { try { OpenSharedConnectionInternal(); - _dbType.InsertBulk(this, pocos); + _dbType.InsertBulk(this, pocos, options); } catch (Exception x) { diff --git a/src/NPoco/DatabaseType.cs b/src/NPoco/DatabaseType.cs index 1a263198..4bb222f5 100644 --- a/src/NPoco/DatabaseType.cs +++ b/src/NPoco/DatabaseType.cs @@ -231,7 +231,7 @@ public virtual async Task ExecuteInsertAsync(Database db, DbCommand c return await db.ExecuteScalarHelperAsync(cmd).ConfigureAwait(false); } - public virtual void InsertBulk(IDatabase db, IEnumerable pocos) + public virtual void InsertBulk(IDatabase db, IEnumerable pocos, InsertBulkOptions options) { foreach (var poco in pocos) { @@ -239,7 +239,7 @@ public virtual void InsertBulk(IDatabase db, IEnumerable pocos) } } - public virtual async Task InsertBulkAsync(IDatabase db, IEnumerable pocos) + public virtual async Task InsertBulkAsync(IDatabase db, IEnumerable pocos, InsertBulkOptions options) { foreach (var poco in pocos) { diff --git a/src/NPoco/IAsyncDatabase.cs b/src/NPoco/IAsyncDatabase.cs index 8e2a76e2..2c63f97b 100644 --- a/src/NPoco/IAsyncDatabase.cs +++ b/src/NPoco/IAsyncDatabase.cs @@ -43,7 +43,7 @@ public interface IAsyncDatabase : IAsyncQueryDatabase /// /// Insert POCO's into database using SqlBulkCopy for SqlServer (other DB's currently fall back to looping each row) /// - Task InsertBulkAsync(IEnumerable pocos); + Task InsertBulkAsync(IEnumerable pocos, InsertBulkOptions options = null); /// /// Insert POCO's into database by concatenating sql using the provided batch options diff --git a/src/NPoco/IDatabase.cs b/src/NPoco/IDatabase.cs index 6b552778..b72069e2 100644 --- a/src/NPoco/IDatabase.cs +++ b/src/NPoco/IDatabase.cs @@ -26,7 +26,7 @@ public interface IDatabase : IAsyncDatabase, IDatabaseQuery, IDatabaseConfig /// /// Insert POCO's into database using SqlBulkCopy for SqlServer (other DB's currently fall back to looping each row) /// - void InsertBulk(IEnumerable pocos); + void InsertBulk(IEnumerable pocos, InsertBulkOptions options = null); /// /// Insert POCO's into database by concatenating sql using the provided batch options diff --git a/src/NPoco/InsertBulkOptions.cs b/src/NPoco/InsertBulkOptions.cs new file mode 100644 index 00000000..59d68ea0 --- /dev/null +++ b/src/NPoco/InsertBulkOptions.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NPoco +{ + public class InsertBulkOptions + { + public int? BulkCopyTimeout { get; set; } + } +} From 4f499c13f77eb69b05e151477db529d3e2a61af2 Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Wed, 30 Sep 2020 16:53:04 +1000 Subject: [PATCH 14/42] Infer the Include property based on the generic type parameter for one-to-one and foreign references #521 --- src/NPoco/Linq/SimpleQueryProvider.cs | 23 ++++++++++++++++++++ test/NPoco.Tests/NewMapper/NewMapperTests.cs | 13 +++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/NPoco/Linq/SimpleQueryProvider.cs b/src/NPoco/Linq/SimpleQueryProvider.cs index b736ef50..0068ee53 100644 --- a/src/NPoco/Linq/SimpleQueryProvider.cs +++ b/src/NPoco/Linq/SimpleQueryProvider.cs @@ -108,6 +108,7 @@ public interface IAsyncQueryProvider : IAsyncQueryResultProvider public interface IAsyncQueryProviderWithIncludes : IAsyncQueryProvider { IAsyncQueryProvider IncludeMany(Expression> expression, JoinType joinType = JoinType.Left); + IAsyncQueryProviderWithIncludes Include(JoinType joinType = JoinType.Left) where T2 : class; IAsyncQueryProviderWithIncludes Include(Expression> expression, JoinType joinType = JoinType.Left) where T2 : class; IAsyncQueryProviderWithIncludes Include(Expression> expression, string tableAlias, JoinType joinType = JoinType.Left) where T2 : class; IAsyncQueryProviderWithIncludes UsingAlias(string empty); @@ -116,6 +117,7 @@ public interface IAsyncQueryProviderWithIncludes : IAsyncQueryProvider public interface IQueryProviderWithIncludes : IQueryProvider { IQueryProvider IncludeMany(Expression> expression, JoinType joinType = JoinType.Left); + IQueryProviderWithIncludes Include(JoinType joinType = JoinType.Left) where T2 : class; IQueryProviderWithIncludes Include(Expression> expression, JoinType joinType = JoinType.Left) where T2 : class; IQueryProviderWithIncludes Include(Expression> expression, string tableAlias, JoinType joinType = JoinType.Left) where T2 : class; IQueryProviderWithIncludes UsingAlias(string empty); @@ -166,6 +168,22 @@ public IAsyncQueryProvider IncludeMany(Expression> expression, _listExpression = expression; return QueryProviderWithIncludes(expression, null, joinType); } + + public IAsyncQueryProviderWithIncludes Include(JoinType joinType = JoinType.Left) where T2 : class + { + var oneToOneMembers = _database.PocoDataFactory.ForType(typeof(T)) + .Members.Where(x => (x.ReferenceType == ReferenceType.OneToOne || x.ReferenceType == ReferenceType.Foreign) + && x.MemberInfoData.MemberType == typeof(T2)); + + foreach (var o2oMember in oneToOneMembers) + { + var entityParam = Expression.Parameter(typeof(T), "entity"); + var joinProperty = Expression.Lambda>(Expression.PropertyOrField(entityParam, o2oMember.Name), entityParam); + Include(joinProperty, joinType); + } + + return this; + } public IAsyncQueryProviderWithIncludes Include(Expression> expression, JoinType joinType = JoinType.Left) where T2 : class { @@ -698,6 +716,11 @@ public ValueTask> DistinctAsync() return (IQueryProvider)base.IncludeMany(expression, joinType); } + public new IQueryProviderWithIncludes Include(JoinType joinType = JoinType.Left) where T2 : class + { + return (IQueryProviderWithIncludes)base.Include(joinType); + } + public new IQueryProviderWithIncludes Include(Expression> expression, JoinType joinType = JoinType.Left) where T2 : class { return (IQueryProviderWithIncludes)base.Include(expression, joinType); diff --git a/test/NPoco.Tests/NewMapper/NewMapperTests.cs b/test/NPoco.Tests/NewMapper/NewMapperTests.cs index b957a7dc..755e9fe4 100644 --- a/test/NPoco.Tests/NewMapper/NewMapperTests.cs +++ b/test/NPoco.Tests/NewMapper/NewMapperTests.cs @@ -443,6 +443,19 @@ public void Test22_1() Assert.AreEqual("1 Road Street, Suburb", data[1].Address); } + [Test] + public void Test22_2() + { + var data = Database.Query() + .Include() + .ProjectTo(x => x.House) + .ToList(); + + Assert.AreEqual(15, data.Count); + Assert.AreEqual(2, data[1].HouseId); + Assert.AreEqual("1 Road Street, Suburb", data[1].Address); + } + [TableName("Users"), PrimaryKey("UserId")] public class MyUserDec { From 9c66c3e58c20efd854ac8b3cb0aa983295966a8a Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Thu, 1 Oct 2020 11:54:57 +1000 Subject: [PATCH 15/42] Use aliases for queries if they exist #557 --- src/NPoco/Expressions/SqlExpression.cs | 10 ++++++- src/NPoco/RowMappers/PropertyMapper.cs | 5 +++- test/NPoco.Tests/Common/UserDecorated.cs | 12 ++++++++ .../QueryTests/FetchAndQueryDecoratedTests.cs | 28 +++++++++++++++++++ 4 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/NPoco/Expressions/SqlExpression.cs b/src/NPoco/Expressions/SqlExpression.cs index 9e398afe..dea296f9 100644 --- a/src/NPoco/Expressions/SqlExpression.cs +++ b/src/NPoco/Expressions/SqlExpression.cs @@ -1425,9 +1425,17 @@ private string BuildSelectExpression(List fields, bool distinct) string.Join(", ", cols.Select(x => { if (x.SelectSql == null) + { + var pocoColumn = x.PocoColumns.Last(); return (PrefixFieldWithTableName - ? _databaseType.EscapeTableName(_pocoData.TableInfo.AutoAlias) + "." + _databaseType.EscapeSqlIdentifier(x.PocoColumn.ColumnName) + " as " + _databaseType.EscapeSqlIdentifier(x.PocoColumns.Last().MemberInfoKey) + ? _databaseType.EscapeTableName(_pocoData.TableInfo.AutoAlias) + + "." + _databaseType.EscapeSqlIdentifier(x.PocoColumn.ColumnName) + + " as " + (string.IsNullOrWhiteSpace(pocoColumn.ColumnAlias) + ? _databaseType.EscapeSqlIdentifier(pocoColumn.MemberInfoKey) + : _databaseType.EscapeSqlIdentifier(pocoColumn.ColumnAlias)) : _databaseType.EscapeSqlIdentifier(x.PocoColumn.ColumnName)); + } + return x.SelectSql; }).ToArray()), _databaseType.EscapeTableName(_pocoData.TableInfo.TableName) + (PrefixFieldWithTableName ? " " + _databaseType.EscapeTableName(_pocoData.TableInfo.AutoAlias) : string.Empty)); diff --git a/src/NPoco/RowMappers/PropertyMapper.cs b/src/NPoco/RowMappers/PropertyMapper.cs index dc813d8b..0e1887dc 100644 --- a/src/NPoco/RowMappers/PropertyMapper.cs +++ b/src/NPoco/RowMappers/PropertyMapper.cs @@ -74,7 +74,7 @@ private MapPlan BuildMapPlan(DbDataReader dataReader, PocoData pocoData) private IEnumerable BuildMapPlans(GroupResult groupedName, DbDataReader dataReader, PocoData pocoData, List pocoMembers) { // find pocomember by property name - var pocoMember = pocoMembers.FirstOrDefault(x => IsEqual(groupedName.Item, x.Name)); + var pocoMember = pocoMembers.FirstOrDefault(x => IsEqual(groupedName.Item, x.Name) || IsEqual(groupedName.Item, x.PocoColumn?.ColumnAlias)); if (pocoMember == null) { @@ -129,6 +129,9 @@ private IEnumerable BuildMapPlans(GroupResult groupedName, DbD public static bool IsEqual(string name, string value) { + if (value is null) + return false; + return string.Equals(value, name, StringComparison.OrdinalIgnoreCase) || string.Equals(value, name.Replace("_", ""), StringComparison.OrdinalIgnoreCase); } diff --git a/test/NPoco.Tests/Common/UserDecorated.cs b/test/NPoco.Tests/Common/UserDecorated.cs index 097a1ea9..a5449d33 100644 --- a/test/NPoco.Tests/Common/UserDecorated.cs +++ b/test/NPoco.Tests/Common/UserDecorated.cs @@ -182,4 +182,16 @@ public class UserDecoratedWithAlias [Column("Age")] public int? Age { get; set; } } + + public class UserAliasNested + { + [ComplexMapping] + public Nested N { get; set; } + + public class Nested + { + [Alias("A")] + public string Aliased { get; set; } + } + } } diff --git a/test/NPoco.Tests/DecoratedTests/QueryTests/FetchAndQueryDecoratedTests.cs b/test/NPoco.Tests/DecoratedTests/QueryTests/FetchAndQueryDecoratedTests.cs index 7f5cc0d2..d7ba816b 100644 --- a/test/NPoco.Tests/DecoratedTests/QueryTests/FetchAndQueryDecoratedTests.cs +++ b/test/NPoco.Tests/DecoratedTests/QueryTests/FetchAndQueryDecoratedTests.cs @@ -74,6 +74,34 @@ public void FetchWithAlias() Assert.True(!string.IsNullOrWhiteSpace(user.Name)); } + [Test] + public void FetchWithAliasLinq() + { + var user = Database.Query().Where(x => x.UserId == 1).Single(); + + Assert.NotNull(user); + Assert.True(!string.IsNullOrWhiteSpace(user.Name)); + } + + [Test] + public void FetchWithAliasNested() + { + var user = Database.Single("select 'howdy' N__A /*poco_dual*/"); + + Assert.NotNull(user); + Assert.AreEqual("howdy", user.N.Aliased); + } + + [Test] + public void FetchWithAliasAutoSelect() + { + var user = Database.Single("where userid = 1"); + + Assert.NotNull(user); + Assert.True(!string.IsNullOrWhiteSpace(user.Name)); + } + + [Test] public void FetchWithAliasUsingAutoSelect() { From eac871951beb154980c04ba56fde248fc2bdb69c Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Thu, 1 Oct 2020 14:08:20 +1000 Subject: [PATCH 16/42] Fix custom mapper being called twice #439 --- src/NPoco/Expressions/SqlExpression.cs | 9 +-- .../DecoratedTests/CRUDTests/UpdateTests.cs | 58 +++++++++++++++++++ 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/src/NPoco/Expressions/SqlExpression.cs b/src/NPoco/Expressions/SqlExpression.cs index dea296f9..6b42780a 100644 --- a/src/NPoco/Expressions/SqlExpression.cs +++ b/src/NPoco/Expressions/SqlExpression.cs @@ -444,12 +444,9 @@ protected virtual string ToUpdateStatement(T item, bool excludeDefaults) continue; if (Context.UpdateFields.Count > 0 && !Context.UpdateFields.Contains(fieldDef.Value.MemberInfoData.Name)) continue; // added - object value = fieldDef.Value.GetColumnValue(_pocoData, item, (pocoColumn, val) => ProcessMapperExtensions.ProcessMapper(_database, pocoColumn, val)); - if (_database.Mappers != null) - { - value = _database.Mappers.FindAndExecute(x => x.GetToDbConverter(fieldDef.Value.ColumnType, fieldDef.Value.MemberInfoData.MemberInfo), value); - } - + + object value = fieldDef.Value.GetColumnValue(_pocoData, item, (pocoColumn, val) => _database.ProcessMapper(pocoColumn, val)); + if (excludeDefaults && (value == null || value.Equals(MappingHelper.GetDefault(value.GetType())))) continue; //GetDefaultValue? if (setFields.Length > 0) diff --git a/test/NPoco.Tests/DecoratedTests/CRUDTests/UpdateTests.cs b/test/NPoco.Tests/DecoratedTests/CRUDTests/UpdateTests.cs index 400eed2c..1ddae3fa 100644 --- a/test/NPoco.Tests/DecoratedTests/CRUDTests/UpdateTests.cs +++ b/test/NPoco.Tests/DecoratedTests/CRUDTests/UpdateTests.cs @@ -1,6 +1,9 @@ using System; +using System.Collections.Generic; using System.Data; using System.Linq; +using System.Reflection; +using Newtonsoft.Json; using NPoco.Tests.Common; using NUnit.Framework; @@ -188,5 +191,60 @@ public void UpdateRecordUsingTuples() Assert.AreEqual(record.Item2, verify.Name); Assert.AreEqual(record.Item3, verify.Age); } + + [Test] + public void UpdateManyWithMapper() + { + var updateData = new UserModel() + { + Id = 1, + Suggestion = new Dictionary() + { + {"test", 2} + } + }; + + var myMapper = new MyMapper(); + Database.Mappers.Add(myMapper); + + Database.UpdateMany() + .Where(x => x.Id == 1) + .OnlyFields(x => new { x.Suggestion }) + .Execute(updateData); + + Database.Mappers.Remove(myMapper); + + var user = Database.Single<(int, string)>("select userid, name from users where userid = 1"); + + Assert.AreEqual(JsonConvert.SerializeObject(updateData.Suggestion), user.Item2); + } + + [TableName("Users")] + [PrimaryKey("UserId")] + public class UserModel + { + [Column("userid")] + public int Id { get; set; } + [Column("name")] + [ColumnType(typeof(string))] + public IDictionary Suggestion { get; set; } + } + + public class MyMapper : DefaultMapper + { + public override Func GetToDbConverter(Type destType, MemberInfo sourceMemberInfo) + { + if ((destType == typeof(string)) && (sourceMemberInfo.GetMemberInfoType() == typeof(IDictionary))) + { + return src => + { + var data = src as IDictionary; + return data == null || data.Count <= 0 ? null : JsonConvert.SerializeObject(data); + }; + } + + return base.GetToDbConverter(destType, sourceMemberInfo); + } + } } } \ No newline at end of file From f5e3d745ae026e81b373f7f57a5af3cacab9a0ac Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Thu, 1 Oct 2020 22:49:35 +1000 Subject: [PATCH 17/42] Map strings to Guids automatically --- src/NPoco/MappingHelper.cs | 4 ++++ test/NPoco.Tests/NewMapper/NewMapperTests.cs | 23 ++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/NPoco/MappingHelper.cs b/src/NPoco/MappingHelper.cs index 62cd3f81..26b8c865 100644 --- a/src/NPoco/MappingHelper.cs +++ b/src/NPoco/MappingHelper.cs @@ -55,6 +55,10 @@ public static Func GetConverter(MapperCollection mapper, PocoCol return converter; } } + else if (srcType == typeof(string) && (dstType == typeof(Guid) || dstType == typeof(Guid?))) + { + converter = src => Guid.Parse((string) src); + } else if ((!pc?.ValueObjectColumn ?? true) && !dstType.IsAssignableFrom(srcType)) { converter = src => Convert.ChangeType(src, (underlyingType ?? dstType), null); diff --git a/test/NPoco.Tests/NewMapper/NewMapperTests.cs b/test/NPoco.Tests/NewMapper/NewMapperTests.cs index 755e9fe4..143b26ec 100644 --- a/test/NPoco.Tests/NewMapper/NewMapperTests.cs +++ b/test/NPoco.Tests/NewMapper/NewMapperTests.cs @@ -466,6 +466,29 @@ public class MyUserDec public HouseDecorated House { get; set; } } + [Test] + public void Test22_3() + { + var result = Database.Single("SELECT '8440F7B5-F1A6-E911-8100-005056833617' as [TID], '3686a4b6-75a6-e911-8e46-5cc5d488af58' as [OID], '3686a4b6-75a6-e911-8e46-5cc5d488af58' as [Order__OID], 'the desc' as [Order__Description] /*poco_dual*/"); + Assert.AreEqual("the desc", result.Order.Description); + Assert.AreEqual("8440F7B5-F1A6-E911-8100-005056833617", result.TID); + } + + public class Ticket + { + public Guid TID { get; set; } + public Guid? OID { get; set; } + [ComputedColumn] + [Reference(ReferenceType.OneToOne, ColumnName = "OID", ReferenceMemberName = "OID")] + public Order Order { get; set; } + } + + public class Order + { + public Guid OID { get; set; } + public string Description { get; set; } + } + [Test] public void Test23() { From 03af98aea27ffe652da45162cd2100aae2106e35 Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Fri, 2 Oct 2020 11:19:12 +1000 Subject: [PATCH 18/42] Implement SaveAsync and refactor duplicate async/sync versions of methods #415 --- src/NPoco/AsyncDatabase.cs | 99 ++++++-- src/NPoco/AsyncHelper.cs | 15 ++ src/NPoco/Database.cs | 233 +++++-------------- src/NPoco/IAsyncDatabase.cs | 14 +- test/NPoco.Tests/NewMapper/NewMapperTests.cs | 2 +- 5 files changed, 163 insertions(+), 200 deletions(-) create mode 100644 src/NPoco/AsyncHelper.cs diff --git a/src/NPoco/AsyncDatabase.cs b/src/NPoco/AsyncDatabase.cs index 3dfde912..3f138fce 100644 --- a/src/NPoco/AsyncDatabase.cs +++ b/src/NPoco/AsyncDatabase.cs @@ -54,10 +54,10 @@ public Task InsertAsync(T poco) public virtual Task InsertAsync(string tableName, string primaryKeyName, bool autoIncrement, T poco) { var pd = PocoDataFactory.ForObject(poco, primaryKeyName, autoIncrement); - return InsertAsyncImp(pd, tableName, primaryKeyName, autoIncrement, poco); + return InsertAsyncImp(pd, tableName, primaryKeyName, autoIncrement, poco, false); } - private async Task InsertAsyncImp(PocoData pocoData, string tableName, string primaryKeyName, bool autoIncrement, T poco) + private async Task InsertAsyncImp(PocoData pocoData, string tableName, string primaryKeyName, bool autoIncrement, T poco, bool sync) { if (!OnInsertingInternal(new InsertContext(poco, tableName, autoIncrement, primaryKeyName))) return 0; @@ -76,12 +76,18 @@ private async Task InsertAsyncImp(PocoData pocoData, string tableName object id; if (!autoIncrement) { - await ExecuteNonQueryHelperAsync(cmd).ConfigureAwait(false); + _ = sync + ? ExecuteNonQueryHelper(cmd) + : await ExecuteNonQueryHelperAsync(cmd).ConfigureAwait(false); + id = InsertStatements.AssignNonIncrementPrimaryKey(primaryKeyName, poco, preparedInsert); } else { - id = await _dbType.ExecuteInsertAsync(this, cmd, primaryKeyName, preparedInsert.PocoData.TableInfo.UseOutputClause, poco, preparedInsert.Rawvalues.ToArray()).ConfigureAwait(false); + id = sync + ? _dbType.ExecuteInsert(this, cmd, primaryKeyName, preparedInsert.PocoData.TableInfo.UseOutputClause, poco, preparedInsert.Rawvalues.ToArray()) + : await _dbType.ExecuteInsertAsync(this, cmd, primaryKeyName, preparedInsert.PocoData.TableInfo.UseOutputClause, poco, preparedInsert.Rawvalues.ToArray()).ConfigureAwait(false); + InsertStatements.AssignPrimaryKey(primaryKeyName, poco, id, preparedInsert); } @@ -117,7 +123,12 @@ public async Task InsertBulkAsync(IEnumerable pocos, InsertBulkOptions opt } } - public async Task InsertBatchAsync(IEnumerable pocos, BatchOptions options = null) + public Task InsertBatchAsync(IEnumerable pocos, BatchOptions options = null) + { + return InsertBatchAsyncImp(pocos, options, false); + } + + private async Task InsertBatchAsyncImp(IEnumerable pocos, BatchOptions options, bool sync) { options = options ?? new BatchOptions(); var result = 0; @@ -143,7 +154,9 @@ public async Task InsertBatchAsync(IEnumerable pocos, BatchOptions op using (var cmd = CreateCommand(_sharedConnection, sql.SQL, sql.Arguments)) { - result += await ExecuteNonQueryHelperAsync(cmd).ConfigureAwait(false); + result += sync + ? ExecuteNonQueryHelper(cmd) + : await ExecuteNonQueryHelperAsync(cmd).ConfigureAwait(false); } } } @@ -187,11 +200,15 @@ public Task UpdateAsync(object poco, object primaryKeyValue, IEnumerable UpdateAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns) { - return UpdateImp (tableName, primaryKeyName, poco, primaryKeyValue, columns, - async (sql, args, next) => next(await ExecuteAsync(sql, args).ConfigureAwait(false)), Task.FromResult(0)); + return UpdateImpAsync(tableName, primaryKeyName, poco, primaryKeyValue, columns, false); + } + + public Task UpdateBatchAsync(IEnumerable> pocos, BatchOptions options = null) + { + return UpdateBatchAsyncImp(pocos, options, false); } - public async Task UpdateBatchAsync(IEnumerable> pocos, BatchOptions options = null) + private async Task UpdateBatchAsyncImp(IEnumerable> pocos, BatchOptions options, bool sync) { options = options ?? new BatchOptions(); int result = 0; @@ -205,7 +222,7 @@ public async Task UpdateBatchAsync(IEnumerable> pocos, Ba { var preparedUpdates = batchedPocos.Select(x => { - if (pd == null) pd = PocoDataFactory.ForType(x.GetType()); + if (pd == null) pd = PocoDataFactory.ForType(x.Poco.GetType()); return UpdateStatements.PrepareUpdate(this, pd, pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, x.Poco, null, x.Snapshot?.UpdatedColumns()); }).ToArray(); @@ -220,7 +237,9 @@ public async Task UpdateBatchAsync(IEnumerable> pocos, Ba using (var cmd = CreateCommand(_sharedConnection, sql.SQL, sql.Arguments)) { - result += await ExecuteNonQueryHelperAsync(cmd).ConfigureAwait(false); + result += sync + ? ExecuteNonQueryHelper(cmd) + : await ExecuteNonQueryHelperAsync(cmd).ConfigureAwait(false); } } } @@ -255,7 +274,7 @@ public Task DeleteAsync(string tableName, string primaryKeyName, object poc public virtual Task DeleteAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue) { - return DeleteImp(tableName, primaryKeyName, poco, primaryKeyValue, async (x, y, next) => next(await ExecuteAsync(x, y).ConfigureAwait(false)), Task.FromResult(0)); + return DeleteImpAsync(tableName, primaryKeyName, poco, primaryKeyValue, false); } public IAsyncDeleteQueryProvider DeleteManyAsync() @@ -263,19 +282,14 @@ public IAsyncDeleteQueryProvider DeleteManyAsync() return new AsyncDeleteQueryProvider(this); } - public ValueTask> PageAsync(long page, long itemsPerPage, Sql sql) + public Task> PageAsync(long page, long itemsPerPage, Sql sql) { return PageAsync(page, itemsPerPage, sql.SQL, sql.Arguments); } - public ValueTask> PageAsync(long page, long itemsPerPage, string sql, params object[] args) + public Task> PageAsync(long page, long itemsPerPage, string sql, params object[] args) { - return PageImp>>(page, itemsPerPage, sql, args, - async (paged, thesql) => - { - paged.Items = await (await QueryAsync(thesql).ConfigureAwait(false)).ToListAsync().ConfigureAwait(false); - return paged; - }); + return PageImpAsync(page, itemsPerPage, sql, args, false); } public ValueTask> FetchAsync(long page, long itemsPerPage, string sql, params object[] args) @@ -300,6 +314,51 @@ public ValueTask> SkipTakeAsync(long skip, long take, Sql sql) return SkipTakeAsync(skip, take, sql.SQL, sql.Arguments); } + /// Checks if a poco represents a new record. + public Task IsNewAsync(T poco) + { + return IsNewAsync(poco, false); + } + + public async Task SaveAsync(T poco) + { + var tableInfo = PocoDataFactory.TableInfoForType(poco.GetType()); + if (await IsNewAsync(poco)) + { + await InsertAsync(tableInfo.TableName, tableInfo.PrimaryKey, tableInfo.AutoIncrement, poco).ConfigureAwait(false); + } + else + { + await UpdateAsync(tableInfo.TableName, tableInfo.PrimaryKey, poco, null, null).ConfigureAwait(false); + } + } + + private async Task PocoExistsAsync(T poco, bool sync) + { + var index = 0; + var pd = PocoDataFactory.ForType(typeof(T)); + var primaryKeyValuePairs = GetPrimaryKeyValues(this, pd, pd.TableInfo.PrimaryKey, poco, true); + var sql = string.Format(DatabaseType.GetExistsSql(), DatabaseType.EscapeTableName(pd.TableInfo.TableName), BuildPrimaryKeySql(this, primaryKeyValuePairs, ref index)); + var args = primaryKeyValuePairs.Select(x => x.Value).ToArray(); + var result = sync + ? ExecuteScalar(sql, args) + : await ExecuteScalarAsync(sql, args); + return result > 0; + } + + private async Task ExistsAsync(object primaryKey, bool sync) + { + var index = 0; + var pd = PocoDataFactory.ForType(typeof(T)); + var primaryKeyValuePairs = GetPrimaryKeyValues(this, pd, pd.TableInfo.PrimaryKey, primaryKey, false); + var sql = string.Format(DatabaseType.GetExistsSql(), DatabaseType.EscapeTableName(pd.TableInfo.TableName), BuildPrimaryKeySql(this, primaryKeyValuePairs, ref index)); + var args = primaryKeyValuePairs.Select(x => x.Value).ToArray(); + var result = sync + ? ExecuteScalar(sql, args) + : await ExecuteScalarAsync(sql, args).ConfigureAwait(false); + return result > 0; + } + public ValueTask> FetchAsync() { return FetchAsync(""); diff --git a/src/NPoco/AsyncHelper.cs b/src/NPoco/AsyncHelper.cs new file mode 100644 index 00000000..818f683b --- /dev/null +++ b/src/NPoco/AsyncHelper.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace NPoco +{ + internal static class AsyncHelper + { + internal static T RunSync(this Task task) + { + return task.ConfigureAwait(false).GetAwaiter().GetResult(); + } + } +} diff --git a/src/NPoco/Database.cs b/src/NPoco/Database.cs index 76adbd3d..17c7583e 100644 --- a/src/NPoco/Database.cs +++ b/src/NPoco/Database.cs @@ -1192,15 +1192,11 @@ public List FetchOneToMany(Expression> many, Func Page(long page, long itemsPerPage, string sql, params object[] args) { - return PageImp>(page, itemsPerPage, sql, args, (paged, thesql) => - { - paged.Items = Query(thesql).ToList(); - return paged; - }); + return PageImpAsync(page, itemsPerPage, sql, args, true).RunSync(); } // Actual implementation of the multi-poco paging - protected TRet PageImp(long page, long itemsPerPage, string sql, object[] args, Func, Sql, TRet> executeQueryFunc) + private async Task> PageImpAsync(long page, long itemsPerPage, string sql, object[] args, bool sync) { if (page <= 0 || itemsPerPage <= 0) { @@ -1228,7 +1224,11 @@ protected TRet PageImp(long page, long itemsPerPage, string sql, object OneTimeCommandTimeout = saveTimeout; // Get the records - return executeQueryFunc(result, new Sql(sqlPage, args)); + result.Items = sync + ? Fetch(new Sql(sqlPage, args)) + : await FetchAsync(new Sql(sqlPage, args)); + + return result; } public TRet FetchMultiple(Func, List, TRet> cb, string sql, params object[] args) { return FetchMultiple(new[] { typeof(T1), typeof(T2) }, cb, new Sql(sql, args)); } @@ -1327,20 +1327,9 @@ private TRet FetchMultiple(Type[] types, object cb, Sql Sq } } - private bool PocoExists(T poco) - { - var index = 0; - var pd = PocoDataFactory.ForType(typeof(T)); - var primaryKeyValuePairs = GetPrimaryKeyValues(this, pd, pd.TableInfo.PrimaryKey, poco, true); - return ExecuteScalar(string.Format(DatabaseType.GetExistsSql(), DatabaseType.EscapeTableName(pd.TableInfo.TableName), BuildPrimaryKeySql(this, primaryKeyValuePairs, ref index)), primaryKeyValuePairs.Select(x => x.Value).ToArray()) > 0; - } - public bool Exists(object primaryKey) { - var index = 0; - var pd = PocoDataFactory.ForType(typeof (T)); - var primaryKeyValuePairs = GetPrimaryKeyValues(this, pd, pd.TableInfo.PrimaryKey, primaryKey, false); - return ExecuteScalar(string.Format(DatabaseType.GetExistsSql(), DatabaseType.EscapeTableName(pd.TableInfo.TableName), BuildPrimaryKeySql(this, primaryKeyValuePairs, ref index)), primaryKeyValuePairs.Select(x => x.Value).ToArray()) > 0; + return ExistsAsync(primaryKey, true).RunSync(); } public T SingleById(object primaryKey) @@ -1448,92 +1437,12 @@ public object Insert(string tableName, string primaryKeyName, T poco) public virtual object Insert(string tableName, string primaryKeyName, bool autoIncrement, T poco) { var pd = PocoDataFactory.ForObject(poco, primaryKeyName, autoIncrement); - return InsertImp(pd, tableName, primaryKeyName, autoIncrement, poco); - } - - private object InsertImp(PocoData pocoData, string tableName, string primaryKeyName, bool autoIncrement, T poco) - { - if (!OnInsertingInternal(new InsertContext(poco, tableName, autoIncrement, primaryKeyName))) - return 0; - - try - { - OpenSharedConnectionInternal(); - - var preparedInsert = InsertStatements.PrepareInsertSql(this, pocoData, tableName, primaryKeyName, autoIncrement, poco); - - using (var cmd = CreateCommand(_sharedConnection, preparedInsert.Sql, preparedInsert.Rawvalues.ToArray())) - { - // Assign the Version column - InsertStatements.AssignVersion(poco, preparedInsert); - - object id; - if (!autoIncrement) - { - ExecuteNonQueryHelper(cmd); - id = InsertStatements.AssignNonIncrementPrimaryKey(primaryKeyName, poco, preparedInsert); - } - else - { - id = _dbType.ExecuteInsert(this, cmd, primaryKeyName, preparedInsert.PocoData.TableInfo.UseOutputClause, poco, preparedInsert.Rawvalues.ToArray()); - InsertStatements.AssignPrimaryKey(primaryKeyName, poco, id, preparedInsert); - } - - return id; - } - } - catch (Exception x) - { - OnExceptionInternal(x); - throw; - } - finally - { - CloseSharedConnectionInternal(); - } + return InsertAsyncImp(pd, tableName, primaryKeyName, autoIncrement, poco, true).RunSync(); } public int InsertBatch(IEnumerable pocos, BatchOptions options = null) { - options = options ?? new BatchOptions(); - var result = 0; - - try - { - OpenSharedConnectionInternal(); - PocoData pd = null; - - foreach (var batchedPocos in pocos.Chunkify(options.BatchSize)) - { - var preparedInserts = batchedPocos.Select(x => - { - if (pd == null) pd = PocoDataFactory.ForType(x.GetType()); - return InsertStatements.PrepareInsertSql(this, pd, pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, pd.TableInfo.AutoIncrement, x); - }).ToArray(); - - var sql = new Sql(); - foreach (var preparedInsertSql in preparedInserts) - { - sql.Append(preparedInsertSql.Sql + options.StatementSeperator, preparedInsertSql.Rawvalues.ToArray()); - } - - using (var cmd = CreateCommand(_sharedConnection, sql.SQL, sql.Arguments)) - { - result += ExecuteNonQueryHelper(cmd); - } - } - } - catch (Exception x) - { - OnExceptionInternal(x); - throw; - } - finally - { - CloseSharedConnectionInternal(); - } - - return result; + return InsertBatchAsyncImp(pocos, options, true).RunSync(); } public void InsertBulk(IEnumerable pocos, InsertBulkOptions options = null) @@ -1561,88 +1470,47 @@ public int Update(string tableName, string primaryKeyName, object poco, object p public virtual int Update(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns) { - return UpdateImp(tableName, primaryKeyName, poco, primaryKeyValue, columns, (sql, args, next) => next(Execute(sql, args)), 0); + return UpdateImpAsync(tableName, primaryKeyName, poco, primaryKeyValue, columns, true).RunSync(); } public int UpdateBatch(IEnumerable> pocos, BatchOptions options = null) { - options = options ?? new BatchOptions(); - int result = 0; - - try - { - OpenSharedConnectionInternal(); - PocoData pd = null; - - foreach (var batchedPocos in pocos.Chunkify(options.BatchSize)) - { - var preparedUpdates = batchedPocos.Select(x => - { - if (pd == null) pd = PocoDataFactory.ForType(x.Poco.GetType()); - return UpdateStatements.PrepareUpdate(this, pd, pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, x.Poco, null, x.Snapshot?.UpdatedColumns()); - }).ToArray(); - - var sql = new Sql(); - foreach (var preparedUpdate in preparedUpdates) - { - if (preparedUpdate.Sql != null) - { - sql.Append(preparedUpdate.Sql + options.StatementSeperator, preparedUpdate.Rawvalues.ToArray()); - } - } - - using (var cmd = CreateCommand(_sharedConnection, sql.SQL, sql.Arguments)) - { - result += ExecuteNonQueryHelper(cmd); - } - } - } - catch (Exception x) - { - OnExceptionInternal(x); - throw; - } - finally - { - CloseSharedConnectionInternal(); - } - - return result; + return UpdateBatchAsyncImp(pocos, options, true).RunSync(); } // Update a record with values from a poco. primary key value can be either supplied or read from the poco - private TRet UpdateImp(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns, Func, TRet> executeFunc, TRet defaultId) + private async Task UpdateImpAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns, bool sync) { if (!OnUpdatingInternal(new UpdateContext(poco, tableName, primaryKeyName, primaryKeyValue, columns))) - return defaultId; + return 0; if (columns != null && !columns.Any()) - return defaultId; + return 0; var pd = PocoDataFactory.ForObject(poco, primaryKeyName, true); var preparedStatement = UpdateStatements.PrepareUpdate(this, pd, tableName, primaryKeyName, poco, primaryKeyValue, columns); if (preparedStatement.Sql == null) - return defaultId; + return 0; + + var result = sync + ? Execute(preparedStatement.Sql, preparedStatement.Rawvalues.ToArray()) + : await ExecuteAsync(preparedStatement.Sql, preparedStatement.Rawvalues.ToArray()).ConfigureAwait(false); - var result = executeFunc(preparedStatement.Sql, preparedStatement.Rawvalues.ToArray(), (id) => + if (result == 0 && !string.IsNullOrEmpty(preparedStatement.VersionName) && VersionException == VersionExceptionHandling.Exception) { - if (id == 0 && !string.IsNullOrEmpty(preparedStatement.VersionName) && VersionException == VersionExceptionHandling.Exception) - { - throw new DBConcurrencyException(string.Format("A Concurrency update occurred in table '{0}' for primary key value(s) = '{1}' and version = '{2}'", tableName, string.Join(",", preparedStatement.PrimaryKeyValuePairs.Values.Select(x => x.ToString()).ToArray()), preparedStatement.VersionValue)); - } + throw new DBConcurrencyException(string.Format("A Concurrency update occurred in table '{0}' for primary key value(s) = '{1}' and version = '{2}'", tableName, + string.Join(",", preparedStatement.PrimaryKeyValuePairs.Values.Select(x => x.ToString()).ToArray()), preparedStatement.VersionValue)); + } - // Set Version - if (!string.IsNullOrEmpty(preparedStatement.VersionName) && preparedStatement.VersionColumnType == VersionColumnType.Number) + // Set Version + if (!string.IsNullOrEmpty(preparedStatement.VersionName) && preparedStatement.VersionColumnType == VersionColumnType.Number) + { + PocoColumn pc; + if (preparedStatement.PocoData.Columns.TryGetValue(preparedStatement.VersionName, out pc)) { - PocoColumn pc; - if (preparedStatement.PocoData.Columns.TryGetValue(preparedStatement.VersionName, out pc)) - { - pc.SetValue(poco, Convert.ChangeType(Convert.ToInt64(preparedStatement.VersionValue) + 1, pc.MemberInfoData.MemberType)); - } + pc.SetValue(poco, Convert.ChangeType(Convert.ToInt64(preparedStatement.VersionValue) + 1, pc.MemberInfoData.MemberType)); } - - return id; - }); + } return result; } @@ -1760,13 +1628,13 @@ public int Delete(string tableName, string primaryKeyName, object poco) public virtual int Delete(string tableName, string primaryKeyName, object poco, object primaryKeyValue) { - return DeleteImp(tableName, primaryKeyName, poco, primaryKeyValue, (x, y, next) => next(Execute(x, y)), 0); + return DeleteImpAsync(tableName, primaryKeyName, poco, primaryKeyValue, true).RunSync(); } - private TRet DeleteImp(string tableName, string primaryKeyName, object poco, object primaryKeyValue, Func, TRet> executeFunc, TRet defaultRet) + private async Task DeleteImpAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue, bool sync) { if (!OnDeletingInternal(new DeleteContext(poco, tableName, primaryKeyName, primaryKeyValue))) - return defaultRet; + return 0; var pd = poco != null ? PocoDataFactory.ForObject(poco, primaryKeyName, true) : null; var primaryKeyValuePairs = GetPrimaryKeyValues(this, pd, primaryKeyName, primaryKeyValue ?? poco, primaryKeyValue == null); @@ -1790,16 +1658,16 @@ private TRet DeleteImp(string tableName, string primaryKeyName, object poc rawValues.Add(versionValue); } } - - var result = executeFunc(sql, rawValues.ToArray(), id => - { - if (id == 0 && !string.IsNullOrEmpty(versionName) && VersionException == VersionExceptionHandling.Exception) - { - throw new DBConcurrencyException(string.Format("A Concurrency update occurred in table '{0}' for primary key value(s) = '{1}' and version = '{2}'", tableName, string.Join(",", primaryKeyValuePairs.Values.Select(x => x.ToString()).ToArray()), versionValue)); - } - return id; - }); + var result = sync + ? Execute(sql, rawValues.ToArray()) + : await ExecuteAsync(sql, rawValues.ToArray()); + + if (result == 0 && !string.IsNullOrEmpty(versionName) && VersionException == VersionExceptionHandling.Exception) + { + throw new DBConcurrencyException(string.Format("A Concurrency update occurred in table '{0}' for primary key value(s) = '{1}' and version = '{2}'", tableName, + string.Join(",", primaryKeyValuePairs.Values.Select(x => x.ToString()).ToArray()), versionValue)); + } return result; } @@ -1832,6 +1700,11 @@ public int Delete(Sql sql) /// Checks if a poco represents a new record. public bool IsNew(T poco) + { + return IsNewAsync(poco, true).RunSync(); + } + + private async Task IsNewAsync(T poco, bool sync) { if (poco is System.Dynamic.ExpandoObject || poco is PocoExpando) { @@ -1848,7 +1721,9 @@ public bool IsNew(T poco) } else if (pd.TableInfo.PrimaryKey.Contains(",")) { - return !PocoExists(poco); + return sync + ? !PocoExistsAsync(poco, true).RunSync() + : !await PocoExistsAsync(poco, false).ConfigureAwait(false); } else { @@ -1861,7 +1736,11 @@ public bool IsNew(T poco) return true; if (!pd.TableInfo.AutoIncrement) - return !Exists(pk); + { + return sync + ? !ExistsAsync(pk, true).RunSync() + : !await ExistsAsync(pk, false).ConfigureAwait(false); + } var type = pk.GetType(); diff --git a/src/NPoco/IAsyncDatabase.cs b/src/NPoco/IAsyncDatabase.cs index 2c63f97b..097c8ea4 100644 --- a/src/NPoco/IAsyncDatabase.cs +++ b/src/NPoco/IAsyncDatabase.cs @@ -84,6 +84,16 @@ public interface IAsyncDatabase : IAsyncQueryDatabase /// Generate a delete statement using a Fluent syntax. Remember to call Execute. /// IAsyncDeleteQueryProvider DeleteManyAsync(); + + /// + /// Determines whether the POCO already exists + /// + Task IsNewAsync(T poco); + + /// + /// Performs an insert or an update depending on whether the POCO already exists. (i.e. an upsert/merge) + /// + Task SaveAsync(T poco); } public interface IAsyncQueryDatabase : IBaseDatabase @@ -177,7 +187,7 @@ public interface IAsyncQueryDatabase : IBaseDatabase /// Extra metadata in the Page class will also be returned. /// Note: This will perform two queries. One for the paged results and one for the count of all results. /// - ValueTask> PageAsync(long page, long itemsPerPage, string sql, params object[] args); + Task> PageAsync(long page, long itemsPerPage, string sql, params object[] args); /// /// Fetch objects of type T from the database using the sql and parameters specified. @@ -185,7 +195,7 @@ public interface IAsyncQueryDatabase : IBaseDatabase /// Extra metadata in the Page class will also be returned. /// Note: This will perform two queries. One for the paged results and one for the count of all results. /// - ValueTask> PageAsync(long page, long itemsPerPage, Sql sql); + Task> PageAsync(long page, long itemsPerPage, Sql sql); /// /// Fetch objects of type T from the database using the sql and parameters specified. diff --git a/test/NPoco.Tests/NewMapper/NewMapperTests.cs b/test/NPoco.Tests/NewMapper/NewMapperTests.cs index 143b26ec..5cd6469f 100644 --- a/test/NPoco.Tests/NewMapper/NewMapperTests.cs +++ b/test/NPoco.Tests/NewMapper/NewMapperTests.cs @@ -471,7 +471,7 @@ public void Test22_3() { var result = Database.Single("SELECT '8440F7B5-F1A6-E911-8100-005056833617' as [TID], '3686a4b6-75a6-e911-8e46-5cc5d488af58' as [OID], '3686a4b6-75a6-e911-8e46-5cc5d488af58' as [Order__OID], 'the desc' as [Order__Description] /*poco_dual*/"); Assert.AreEqual("the desc", result.Order.Description); - Assert.AreEqual("8440F7B5-F1A6-E911-8100-005056833617", result.TID); + Assert.AreEqual(Guid.Parse("8440F7B5-F1A6-E911-8100-005056833617"), result.TID); } public class Ticket From 6b48cb7418635adcdfcc0ea25ecd4a10f1826e50 Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Fri, 2 Oct 2020 11:27:52 +1000 Subject: [PATCH 19/42] Refactor exists sql to be reused --- src/NPoco/AsyncDatabase.cs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/NPoco/AsyncDatabase.cs b/src/NPoco/AsyncDatabase.cs index 3f138fce..a676c906 100644 --- a/src/NPoco/AsyncDatabase.cs +++ b/src/NPoco/AsyncDatabase.cs @@ -335,28 +335,30 @@ public async Task SaveAsync(T poco) private async Task PocoExistsAsync(T poco, bool sync) { - var index = 0; - var pd = PocoDataFactory.ForType(typeof(T)); - var primaryKeyValuePairs = GetPrimaryKeyValues(this, pd, pd.TableInfo.PrimaryKey, poco, true); - var sql = string.Format(DatabaseType.GetExistsSql(), DatabaseType.EscapeTableName(pd.TableInfo.TableName), BuildPrimaryKeySql(this, primaryKeyValuePairs, ref index)); - var args = primaryKeyValuePairs.Select(x => x.Value).ToArray(); + var sql = GetExistsSql(poco, true); var result = sync - ? ExecuteScalar(sql, args) - : await ExecuteScalarAsync(sql, args); + ? ExecuteScalar(sql) + : await ExecuteScalarAsync(sql); return result > 0; } private async Task ExistsAsync(object primaryKey, bool sync) + { + var sql = GetExistsSql(primaryKey, false); + var result = sync + ? ExecuteScalar(sql) + : await ExecuteScalarAsync(sql).ConfigureAwait(false); + return result > 0; + } + + private Sql GetExistsSql(object primaryKeyorPoco, bool isPoco) { var index = 0; var pd = PocoDataFactory.ForType(typeof(T)); - var primaryKeyValuePairs = GetPrimaryKeyValues(this, pd, pd.TableInfo.PrimaryKey, primaryKey, false); + var primaryKeyValuePairs = GetPrimaryKeyValues(this, pd, pd.TableInfo.PrimaryKey, primaryKeyorPoco, isPoco); var sql = string.Format(DatabaseType.GetExistsSql(), DatabaseType.EscapeTableName(pd.TableInfo.TableName), BuildPrimaryKeySql(this, primaryKeyValuePairs, ref index)); var args = primaryKeyValuePairs.Select(x => x.Value).ToArray(); - var result = sync - ? ExecuteScalar(sql, args) - : await ExecuteScalarAsync(sql, args).ConfigureAwait(false); - return result > 0; + return new Sql(sql, args); } public ValueTask> FetchAsync() From 2609261b13728d8f8b3435b2d073e71ab6092717 Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Fri, 2 Oct 2020 18:49:47 +1000 Subject: [PATCH 20/42] Support private properties #414 --- .../ColumnConfigurationBuilder.cs | 18 ++++++++ .../FluentMappingsPocoDataBuilder.cs | 9 ++++ src/NPoco/MemberAccessor.cs | 6 ++- src/NPoco/PocoDataBuilder.cs | 7 ++- src/NPoco/ReflectionUtils.cs | 10 ++++- .../ColumnConfigurationBuilderTests.cs | 45 +++++++++++++++++++ test/NPoco.Tests/NewMapper/NewMapperTests.cs | 22 +++++++++ 7 files changed, 113 insertions(+), 4 deletions(-) diff --git a/src/NPoco/FluentMappings/ColumnConfigurationBuilder.cs b/src/NPoco/FluentMappings/ColumnConfigurationBuilder.cs index d2a69306..436adcd0 100644 --- a/src/NPoco/FluentMappings/ColumnConfigurationBuilder.cs +++ b/src/NPoco/FluentMappings/ColumnConfigurationBuilder.cs @@ -26,6 +26,24 @@ public IColumnBuilder Column(Expression> property) return builder; } + public IColumnBuilder Column(string privateProperty) + { + var memberInfo = ReflectionUtils.GetPrivatePropertiesForClasses(typeof(T)).FirstOrDefault(x => x.Name == privateProperty); + if (memberInfo is null) + { + throw new Exception($"The name of the private property '{privateProperty}' does not exist of the type '{typeof(T)}'"); + } + var columnDefinition = new ColumnDefinition() { MemberInfo = memberInfo }; + if (memberInfo.GetMemberInfoType() != typeof(T2)) + { + throw new Exception($"The type of the property '{memberInfo.GetMemberInfoType()}' doesn't match the generic type provided '{typeof(T2)}'"); + } + var builder = new ColumnBuilder(columnDefinition); + var key = memberInfo.Name; + _columnDefinitions[key] = columnDefinition; + return builder; + } + public IManyColumnBuilder Many(Expression>> property) { var members = MemberHelper.GetMembers(property); diff --git a/src/NPoco/FluentMappings/FluentMappingsPocoDataBuilder.cs b/src/NPoco/FluentMappings/FluentMappingsPocoDataBuilder.cs index f6f41b85..4689b9b9 100644 --- a/src/NPoco/FluentMappings/FluentMappingsPocoDataBuilder.cs +++ b/src/NPoco/FluentMappings/FluentMappingsPocoDataBuilder.cs @@ -65,6 +65,15 @@ protected override TableInfoPlan GetTableInfo(Type type, ColumnInfo[] columnInfo }; } + protected override bool ShouldIncludePrivateColumn(MemberInfo mi, Type type) + { + if (_mappings.Config.ContainsKey(type) + && _mappings.Config[type].ColumnConfiguration.ContainsKey(mi.Name)) + return true; + + return base.ShouldIncludePrivateColumn(mi, type); + } + protected override ColumnInfo GetColumnInfo(MemberInfo mi, Type type) { if (!_mappings.Config.ContainsKey(type)) diff --git a/src/NPoco/MemberAccessor.cs b/src/NPoco/MemberAccessor.cs index 5d4b9e3b..4f5ef470 100644 --- a/src/NPoco/MemberAccessor.cs +++ b/src/NPoco/MemberAccessor.cs @@ -41,7 +41,9 @@ public class MemberAccessor public MemberAccessor(Type targetType, string memberName) { _targetType = targetType; - MemberInfo memberInfo = ReflectionUtils.GetFieldsAndPropertiesForClasses(targetType).First(x => x.Name == memberName); + MemberInfo memberInfo = ReflectionUtils.GetFieldsAndPropertiesForClasses(targetType) + .Concat(ReflectionUtils.GetPrivatePropertiesForClasses(targetType)) + .First(x => x.Name == memberName); if (memberInfo == null) { @@ -186,7 +188,7 @@ private Func GetGetDelegate() } else { - var targetGetMethod = ((PropertyInfo)_member).GetGetMethod(); + var targetGetMethod = ((PropertyInfo)_member).GetGetMethod(true); var opCode = _targetType.GetTypeInfo().IsValueType ? OpCodes.Call : OpCodes.Callvirt; getIL.Emit(opCode, targetGetMethod); returnType = targetGetMethod.ReturnType; diff --git a/src/NPoco/PocoDataBuilder.cs b/src/NPoco/PocoDataBuilder.cs index cddfc497..7e3ef233 100644 --- a/src/NPoco/PocoDataBuilder.cs +++ b/src/NPoco/PocoDataBuilder.cs @@ -47,11 +47,16 @@ public InitializedPocoDataBuilder Init() return this; } + protected virtual bool ShouldIncludePrivateColumn(MemberInfo mi, Type t) => mi.GetCustomAttribute() != null; + public ColumnInfo[] GetColumnInfos(Type type) { return ReflectionUtils.GetFieldsAndPropertiesForClasses(type) .Where(x => !IsDictionaryType(x.DeclaringType)) - .Select(x => GetColumnInfo(x, type)).ToArray(); + .Concat(ReflectionUtils.GetPrivatePropertiesForClasses(type) + .Where(x => ShouldIncludePrivateColumn(x, type))) + .Select(x => GetColumnInfo(x, type)) + .ToArray(); } public static bool IsDictionaryType(Type type) diff --git a/src/NPoco/ReflectionUtils.cs b/src/NPoco/ReflectionUtils.cs index 78834b19..5fec912d 100644 --- a/src/NPoco/ReflectionUtils.cs +++ b/src/NPoco/ReflectionUtils.cs @@ -22,6 +22,14 @@ public static List GetFieldsAndPropertiesForClasses(Type type) return GetFieldsAndProperties(type); } + public static List GetPrivatePropertiesForClasses(Type type) + { + if (type.GetTypeInfo().IsValueType || type == typeof(string) || type == typeof(byte[]) || type == typeof(Dictionary) || type.IsArray) + return new List(); + + return GetFieldsAndProperties(type, BindingFlags.Instance | BindingFlags.NonPublic); + } + public static List GetFieldsAndProperties(Type type) { return GetFieldsAndProperties(type, BindingFlags.Instance | BindingFlags.Public); @@ -31,7 +39,7 @@ public static List GetFieldsAndProperties(Type type, BindingFlags bi { List targetMembers = new List(); - targetMembers.AddRange(type.GetFields(bindingAttr).Where(x=>!x.IsInitOnly).ToArray()); + targetMembers.AddRange(type.GetFields(bindingAttr).Where(x => !x.IsInitOnly).ToArray()); targetMembers.AddRange(type.GetProperties(bindingAttr)); return targetMembers; diff --git a/test/NPoco.Tests/FluentMappings/ColumnConfigurationBuilderTests.cs b/test/NPoco.Tests/FluentMappings/ColumnConfigurationBuilderTests.cs index 6aff8ec5..a9b33708 100644 --- a/test/NPoco.Tests/FluentMappings/ColumnConfigurationBuilderTests.cs +++ b/test/NPoco.Tests/FluentMappings/ColumnConfigurationBuilderTests.cs @@ -5,6 +5,7 @@ using NPoco.Tests.Common; using NUnit.Framework; using System.Reflection; +using NPoco.Tests.NewMapper; namespace NPoco.Tests.FluentMappings { @@ -219,5 +220,49 @@ public void FluentMappingOverridesShouldOverrideComplexMappingPrefix() var pd = map.Config(new MapperCollection()).Resolver(typeof(User), new PocoDataFactory(new MapperCollection())).Build(); Assert.AreEqual(true, pd.Columns.ContainsKey("CM__Street")); } + + public class FluentMappingOverridesForPrivates : Mappings + { + public FluentMappingOverridesForPrivates() + { + For().Columns(x => + { + x.Column("Data"); + }); + } + } + + [Test] + public void FluentMappingForPrivates() + { + var map = FluentMappingConfiguration.Configure(new FluentMappingOverridesForPrivates()); + var pd = map.Config(new MapperCollection()).Resolver(typeof(NewMapperTests.Result36), new PocoDataFactory(new MapperCollection())).Build(); + Assert.AreEqual(true, pd.Columns.ContainsKey("Data")); + } + + public class FluentMappingOverridesForPrivatesScan : Mappings + { + public FluentMappingOverridesForPrivatesScan() + { + For().Columns(x => + { + x.Column("Data1"); + }); + } + } + + [Test] + public void FluentMappingForPrivatesScan() + { + var map = FluentMappingConfiguration.Scan(s => + { + s.Assembly(typeof(NewMapperTests.Result36).GetTypeInfo().Assembly); + s.IncludeTypes(t => t == typeof(NewMapperTests.Result36)); + s.OverrideMappingsWith(new FluentMappingOverridesForPrivatesScan()); + }); + var pd = map.Config(new MapperCollection()).Resolver(typeof(NewMapperTests.Result36), new PocoDataFactory(new MapperCollection())).Build(); + Assert.AreEqual(true, pd.Columns.ContainsKey("Data")); + Assert.AreEqual(true, pd.Columns.ContainsKey("Data1")); + } } } \ No newline at end of file diff --git a/test/NPoco.Tests/NewMapper/NewMapperTests.cs b/test/NPoco.Tests/NewMapper/NewMapperTests.cs index 5cd6469f..a2006ea1 100644 --- a/test/NPoco.Tests/NewMapper/NewMapperTests.cs +++ b/test/NPoco.Tests/NewMapper/NewMapperTests.cs @@ -725,6 +725,28 @@ public class ResultData public int Age { get; set; } } } + + [Test] + public void Test36() + { + var result = Database.Single("select 'Test' Data, 'Test2' Data1 /*poco_dual*/"); + + Database.Insert(result); + + Assert.AreEqual("Test", result.GetData()); + Assert.AreEqual(null, result.GetData1()); + } + + public class Result36 + { + [Column] + private string Data { get; set; } + + private string Data1 { get; set; } + + public string GetData() => Data; + public string GetData1() => Data1; + } } public class NoPrimaryKey From 783cc6005a7df13d52d5b7fa613da915fe382c1a Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Fri, 2 Oct 2020 23:48:01 +1000 Subject: [PATCH 21/42] Implement FetchMultipleAsync and change to using ValueTuples #460 --- src/NPoco/AsyncDatabase.cs | 31 ++-- src/NPoco/Database.cs | 167 ++++++++----------- src/NPoco/IAsyncDatabase.cs | 66 ++++++++ src/NPoco/IDatabaseQuery.cs | 12 +- test/NPoco.Tests/Async/QueryAsyncTests.cs | 8 + test/NPoco.Tests/NewMapper/NewMapperTests.cs | 3 - 6 files changed, 168 insertions(+), 119 deletions(-) diff --git a/src/NPoco/AsyncDatabase.cs b/src/NPoco/AsyncDatabase.cs index a676c906..85730d5d 100644 --- a/src/NPoco/AsyncDatabase.cs +++ b/src/NPoco/AsyncDatabase.cs @@ -376,6 +376,20 @@ public async ValueTask> FetchAsync(Sql sql) return await (await QueryAsync(sql).ConfigureAwait(false)).ToListAsync().ConfigureAwait(false); } + public Task FetchMultipleAsync(Func, List, TRet> cb, string sql, params object[] args) { return FetchMultipleImp(new[] { typeof(T1), typeof(T2) }, cb, new Sql(sql, args), false); } + public Task FetchMultipleAsync(Func, List, List, TRet> cb, string sql, params object[] args) { return FetchMultipleImp(new[] { typeof(T1), typeof(T2), typeof(T3) }, cb, new Sql(sql, args), false); } + public Task FetchMultipleAsync(Func, List, List, List, TRet> cb, string sql, params object[] args) { return FetchMultipleImp(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, new Sql(sql, args), false); } + public Task FetchMultipleAsync(Func, List, TRet> cb, Sql sql) { return FetchMultipleImp(new[] { typeof(T1), typeof(T2) }, cb, sql, false); } + public Task FetchMultipleAsync(Func, List, List, TRet> cb, Sql sql) { return FetchMultipleImp(new[] { typeof(T1), typeof(T2), typeof(T3) }, cb, sql, false); } + public Task FetchMultipleAsync(Func, List, List, List, TRet> cb, Sql sql) { return FetchMultipleImp(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, sql, false); } + + public Task<(List, List)> FetchMultipleAsync(string sql, params object[] args) { return FetchMultipleImp, List)>(new[] { typeof(T1), typeof(T2) }, new Func, List, (List, List)>((y, z) => (y, z)), new Sql(sql, args), false); } + public Task<(List, List, List)> FetchMultipleAsync(string sql, params object[] args) { return FetchMultipleImp, List, List)>(new[] { typeof(T1), typeof(T2), typeof(T3) }, new Func, List, List, (List, List, List)>((x, y, z) => (x, y, z)), new Sql(sql, args), false); } + public Task<(List, List, List, List)> FetchMultipleAsync(string sql, params object[] args) { return FetchMultipleImp, List, List, List)>(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, new Func, List, List, List, (List, List, List, List)>((w, x, y, z) => (w, x, y, z)), new Sql(sql, args), false); } + public Task<(List, List)> FetchMultipleAsync(Sql sql) { return FetchMultipleImp, List)>(new[] { typeof(T1), typeof(T2) }, new Func, List, (List, List)>((y, z) => (y, z)), sql, false); } + public Task<(List, List, List)> FetchMultipleAsync(Sql sql) { return FetchMultipleImp, List, List)>(new[] { typeof(T1), typeof(T2), typeof(T3) }, new Func, List, List, (List, List, List)>((x, y, z) => (x, y, z)), sql, false); } + public Task<(List, List, List, List)> FetchMultipleAsync(Sql sql) { return FetchMultipleImp, List, List, List)>(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, new Func, List, List, List, (List, List, List, List)>((w, x, y, z) => (w, x, y, z)), sql, false); } + public IAsyncQueryProviderWithIncludes QueryAsync() { return new AsyncQueryProvider(this); @@ -401,20 +415,9 @@ internal async Task> QueryAsync(T instance, Expression(typeof(T), instance, r, cmd)); + using var cmd = CreateCommand(_sharedConnection, sql, args); + var reader = await ExecuteDataReader(cmd, false).ConfigureAwait(false); + return (listExpression != null ? ReadOneToManyAsync(instance, reader, cmd, listExpression, idFunc) : ReadAsync(typeof(T), instance, reader, cmd)); } catch { diff --git a/src/NPoco/Database.cs b/src/NPoco/Database.cs index 17c7583e..9d7063a0 100644 --- a/src/NPoco/Database.cs +++ b/src/NPoco/Database.cs @@ -1096,19 +1096,9 @@ public IEnumerable Query(Type type, Sql Sql) try { OpenSharedConnectionInternal(); - var cmd = CreateCommand(_sharedConnection, sql, args); - DbDataReader r; - try - { - r = ExecuteDataReader(cmd); - } - catch - { - cmd.Dispose(); - throw; - } - - var read = Read(type, null, r, cmd); + using var cmd = CreateCommand(_sharedConnection, sql, args); + var reader = ExecuteDataReader(cmd, true).RunSync(); + var read = Read(type, null, reader, cmd); foreach (var item in read) { yield return item; @@ -1130,24 +1120,13 @@ internal IEnumerable QueryImp(T instance, Expression> listE try { OpenSharedConnectionInternal(); - var cmd = CreateCommand(_sharedConnection, sql, args); - DbDataReader r; - try - { - r = ExecuteDataReader(cmd); - } - catch - { - cmd.Dispose(); - throw; - } - - var read = listExpression != null ? ReadOneToMany(instance, r, cmd, listExpression, idFunc) : Read(typeof(T), instance, r, cmd); + using var cmd = CreateCommand(_sharedConnection, sql, args); + var reader = ExecuteDataReader(cmd, true).RunSync(); + var read = listExpression != null ? ReadOneToMany(instance, reader, cmd, listExpression, idFunc) : Read(typeof(T), instance, reader, cmd); foreach (var item in read) { yield return item; } - } finally { @@ -1155,12 +1134,12 @@ internal IEnumerable QueryImp(T instance, Expression> listE } } - private DbDataReader ExecuteDataReader(DbCommand cmd) + private async Task ExecuteDataReader(DbCommand cmd, bool sync) { DbDataReader r; try { - r = ExecuteReaderHelper(cmd); + r = sync ? ExecuteReaderHelper(cmd) : await ExecuteReaderHelperAsync(cmd).ConfigureAwait(false); } catch (Exception x) { @@ -1231,24 +1210,24 @@ private async Task> PageImpAsync(long page, long itemsPerPage, string return result; } - public TRet FetchMultiple(Func, List, TRet> cb, string sql, params object[] args) { return FetchMultiple(new[] { typeof(T1), typeof(T2) }, cb, new Sql(sql, args)); } - public TRet FetchMultiple(Func, List, List, TRet> cb, string sql, params object[] args) { return FetchMultiple(new[] { typeof(T1), typeof(T2), typeof(T3) }, cb, new Sql(sql, args)); } - public TRet FetchMultiple(Func, List, List, List, TRet> cb, string sql, params object[] args) { return FetchMultiple(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, new Sql(sql, args)); } - public TRet FetchMultiple(Func, List, TRet> cb, Sql sql) { return FetchMultiple(new[] { typeof(T1), typeof(T2) }, cb, sql); } - public TRet FetchMultiple(Func, List, List, TRet> cb, Sql sql) { return FetchMultiple(new[] { typeof(T1), typeof(T2), typeof(T3) }, cb, sql); } - public TRet FetchMultiple(Func, List, List, List, TRet> cb, Sql sql) { return FetchMultiple(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, sql); } + public TRet FetchMultiple(Func, List, TRet> cb, string sql, params object[] args) { return FetchMultipleImp(new[] { typeof(T1), typeof(T2) }, cb, new Sql(sql, args), true).RunSync(); } + public TRet FetchMultiple(Func, List, List, TRet> cb, string sql, params object[] args) { return FetchMultipleImp(new[] { typeof(T1), typeof(T2), typeof(T3) }, cb, new Sql(sql, args), true).RunSync(); } + public TRet FetchMultiple(Func, List, List, List, TRet> cb, string sql, params object[] args) { return FetchMultipleImp(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, new Sql(sql, args), true).RunSync(); } + public TRet FetchMultiple(Func, List, TRet> cb, Sql sql) { return FetchMultipleImp(new[] { typeof(T1), typeof(T2) }, cb, sql, true).RunSync(); } + public TRet FetchMultiple(Func, List, List, TRet> cb, Sql sql) { return FetchMultipleImp(new[] { typeof(T1), typeof(T2), typeof(T3) }, cb, sql, true).RunSync(); } + public TRet FetchMultiple(Func, List, List, List, TRet> cb, Sql sql) { return FetchMultipleImp(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, sql, true).RunSync(); } - public Tuple, List> FetchMultiple(string sql, params object[] args) { return FetchMultiple, List>>(new[] { typeof(T1), typeof(T2) }, new Func, List, Tuple, List>>((y, z) => new Tuple, List>(y, z)), new Sql(sql, args)); } - public Tuple, List, List> FetchMultiple(string sql, params object[] args) { return FetchMultiple, List, List>>(new[] { typeof(T1), typeof(T2), typeof(T3) }, new Func, List, List, Tuple, List, List>>((x, y, z) => new Tuple, List, List>(x, y, z)), new Sql(sql, args)); } - public Tuple, List, List, List> FetchMultiple(string sql, params object[] args) { return FetchMultiple, List, List, List>>(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, new Func, List, List, List, Tuple, List, List, List>>((w, x, y, z) => new Tuple, List, List, List>(w, x, y, z)), new Sql(sql, args)); } - public Tuple, List> FetchMultiple(Sql sql) { return FetchMultiple, List>>(new[] { typeof(T1), typeof(T2) }, new Func, List, Tuple, List>>((y, z) => new Tuple, List>(y, z)), sql); } - public Tuple, List, List> FetchMultiple(Sql sql) { return FetchMultiple, List, List>>(new[] { typeof(T1), typeof(T2), typeof(T3) }, new Func, List, List, Tuple, List, List>>((x, y, z) => new Tuple, List, List>(x, y, z)), sql); } - public Tuple, List, List, List> FetchMultiple(Sql sql) { return FetchMultiple, List, List, List>>(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, new Func, List, List, List, Tuple, List, List, List>>((w, x, y, z) => new Tuple, List, List, List>(w, x, y, z)), sql); } + public (List, List) FetchMultiple(string sql, params object[] args) { return FetchMultipleImp, List)>(new[] { typeof(T1), typeof(T2) }, new Func, List, (List, List)>((y, z) => (y, z)), new Sql(sql, args), true).RunSync(); } + public (List, List, List) FetchMultiple(string sql, params object[] args) { return FetchMultipleImp, List, List)>(new[] { typeof(T1), typeof(T2), typeof(T3) }, new Func, List, List, (List, List, List)>((x, y, z) => (x, y, z)), new Sql(sql, args), true).RunSync(); } + public (List, List, List, List) FetchMultiple(string sql, params object[] args) { return FetchMultipleImp, List, List, List)>(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, new Func, List, List, List, (List, List, List, List)>((w, x, y, z) => (w, x, y, z)), new Sql(sql, args), true).RunSync(); } + public (List, List) FetchMultiple(Sql sql) { return FetchMultipleImp, List)>(new[] { typeof(T1), typeof(T2) }, new Func, List, (List, List)>((y, z) => (y, z)), sql, true).RunSync(); } + public (List, List, List) FetchMultiple(Sql sql) { return FetchMultipleImp, List, List)>(new[] { typeof(T1), typeof(T2), typeof(T3) }, new Func, List, List, (List, List, List)>((x, y, z) => (x, y, z)), sql, true).RunSync(); } + public (List, List, List, List) FetchMultiple(Sql sql) { return FetchMultipleImp, List, List, List)>(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, new Func, List, List, List, (List, List, List, List)>((w, x, y, z) => (w, x, y, z)), sql, true).RunSync(); } public class DontMap { } // Actual implementation of the multi query - private TRet FetchMultiple(Type[] types, object cb, Sql Sql) + private async Task FetchMultipleImp(Type[] types, object cb, Sql Sql, bool sync) { var sql = Sql.SQL; var args = Sql.Arguments; @@ -1256,70 +1235,66 @@ private TRet FetchMultiple(Type[] types, object cb, Sql Sq try { OpenSharedConnectionInternal(); - using (var cmd = CreateCommand(_sharedConnection, sql, args)) + using var cmd = CreateCommand(_sharedConnection, sql, args); + using var r = sync ? ExecuteDataReader(cmd, true).RunSync() : await ExecuteDataReader(cmd, false).ConfigureAwait(false); + + var typeIndex = 1; + var list1 = new List(); + var list2 = types.Length > 1 ? new List() : null; + var list3 = types.Length > 2 ? new List() : null; + var list4 = types.Length > 3 ? new List() : null; + do { - var r = ExecuteDataReader(cmd); - using (r) + if (typeIndex > types.Length) + break; + + var pd = PocoDataFactory.ForType(types[typeIndex - 1]); + var factory = new MappingFactory(pd, r); + + while (true) { - var typeIndex = 1; - var list1 = new List(); - var list2 = types.Length > 1 ? new List() : null; - var list3 = types.Length > 2 ? new List() : null; - var list4 = types.Length > 3 ? new List() : null; - do + try { - if (typeIndex > types.Length) + if (sync ? !r.Read() : !await r.ReadAsync().ConfigureAwait(false)) break; - var pd = PocoDataFactory.ForType(types[typeIndex - 1]); - var factory = new MappingFactory(pd, r); - - while (true) + switch (typeIndex) { - try - { - if (!r.Read()) - break; - - switch (typeIndex) - { - case 1: - list1.Add((T1) factory.Map(r, default(T1))); - break; - case 2: - list2.Add((T2) factory.Map(r, default(T2))); - break; - case 3: - list3.Add((T3) factory.Map(r, default(T3))); - break; - case 4: - list4.Add((T4) factory.Map(r, default(T4))); - break; - } - } - catch (Exception x) - { - OnExceptionInternal(x); - throw; - } + case 1: + list1.Add((T1) factory.Map(r, default(T1))); + break; + case 2: + list2.Add((T2) factory.Map(r, default(T2))); + break; + case 3: + list3.Add((T3) factory.Map(r, default(T3))); + break; + case 4: + list4.Add((T4) factory.Map(r, default(T4))); + break; } - - typeIndex++; - } while (r.NextResult()); - - switch (types.Length) + } + catch (Exception x) { - case 2: - return ((Func, List, TRet>) cb)(list1, list2); - case 3: - return ((Func, List, List, TRet>) cb)(list1, list2, list3); - case 4: - return ((Func, List, List, List, TRet>) cb)(list1, list2, list3, list4); + OnExceptionInternal(x); + throw; } - - return default(TRet); } + + typeIndex++; + } while (sync ? r.NextResult() : await r.NextResultAsync().ConfigureAwait(false)); + + switch (types.Length) + { + case 2: + return ((Func, List, TRet>) cb)(list1, list2); + case 3: + return ((Func, List, List, TRet>) cb)(list1, list2, list3); + case 4: + return ((Func, List, List, List, TRet>) cb)(list1, list2, list3, list4); } + + return default(TRet); } finally { @@ -1475,7 +1450,7 @@ public virtual int Update(string tableName, string primaryKeyName, object poco, public int UpdateBatch(IEnumerable> pocos, BatchOptions options = null) { - return UpdateBatchAsyncImp(pocos, options, true).RunSync(); + return UpdateBatchAsyncImp(pocos, options, true).RunSync(); } // Update a record with values from a poco. primary key value can be either supplied or read from the poco diff --git a/src/NPoco/IAsyncDatabase.cs b/src/NPoco/IAsyncDatabase.cs index 097c8ea4..8f0f7459 100644 --- a/src/NPoco/IAsyncDatabase.cs +++ b/src/NPoco/IAsyncDatabase.cs @@ -220,5 +220,71 @@ public interface IAsyncQueryDatabase : IBaseDatabase /// The sql provided will be converted so that only the results for the skip and take values specified will be returned. /// ValueTask> SkipTakeAsync(long skip, long take, Sql sql); + + /// + /// Fetches multiple result sets into the one object. + /// In this method you must provide how you will take the results and combine them + /// + Task FetchMultipleAsync(Func, List, TRet> cb, string sql, params object[] args); + + /// + /// Fetches multiple result sets into the one object. + /// In this method you must provide how you will take the results and combine them + /// + Task FetchMultipleAsync(Func, List, List, TRet> cb, string sql, params object[] args); + + /// + /// Fetches multiple result sets into the one object. + /// In this method you must provide how you will take the results and combine them + /// + Task FetchMultipleAsync(Func, List, List, List, TRet> cb, string sql, params object[] args); + + /// + /// Fetches multiple result sets into the one object. + /// In this method you must provide how you will take the results and combine them + /// + Task FetchMultipleAsync(Func, List, TRet> cb, Sql sql); + + /// + /// Fetches multiple result sets into the one object. + /// In this method you must provide how you will take the results and combine them + /// + Task FetchMultipleAsync(Func, List, List, TRet> cb, Sql sql); + + /// + /// Fetches multiple result sets into the one object. + /// In this method you must provide how you will take the results and combine them + /// + Task FetchMultipleAsync(Func, List, List, List, TRet> cb, Sql sql); + + /// + /// Fetches multiple result sets into the one Tuple. + /// + Task<(List, List)> FetchMultipleAsync(string sql, params object[] args); + + /// + /// Fetches multiple result sets into the one Tuple. + /// + Task<(List, List, List)> FetchMultipleAsync(string sql, params object[] args); + + /// + /// Fetches multiple result sets into the one Tuple. + /// + Task<(List, List, List, List)> FetchMultipleAsync(string sql, params object[] args); + + /// + /// Fetches multiple result sets into the one Tuple. + /// + Task<(List, List)> FetchMultipleAsync(Sql sql); + + /// + /// Fetches multiple result sets into the one Tuple. + /// + Task<(List, List, List)> FetchMultipleAsync(Sql sql); + + /// + /// Fetches multiple result sets into the one Tuple. + /// + Task<(List, List, List, List)> FetchMultipleAsync(Sql sql); } } diff --git a/src/NPoco/IDatabaseQuery.cs b/src/NPoco/IDatabaseQuery.cs index a5d3ade2..07fa1e60 100644 --- a/src/NPoco/IDatabaseQuery.cs +++ b/src/NPoco/IDatabaseQuery.cs @@ -317,31 +317,31 @@ public interface IDatabaseQuery : IAsyncQueryDatabase /// /// Fetches multiple result sets into the one Tuple. /// - Tuple, List> FetchMultiple(string sql, params object[] args); + (List, List) FetchMultiple(string sql, params object[] args); /// /// Fetches multiple result sets into the one Tuple. /// - Tuple, List, List> FetchMultiple(string sql, params object[] args); + (List, List, List) FetchMultiple(string sql, params object[] args); /// /// Fetches multiple result sets into the one Tuple. /// - Tuple, List, List, List> FetchMultiple(string sql, params object[] args); + (List, List, List, List) FetchMultiple(string sql, params object[] args); /// /// Fetches multiple result sets into the one Tuple. /// - Tuple, List> FetchMultiple(Sql sql); + (List, List) FetchMultiple(Sql sql); /// /// Fetches multiple result sets into the one Tuple. /// - Tuple, List, List> FetchMultiple(Sql sql); + (List, List, List) FetchMultiple(Sql sql); /// /// Fetches multiple result sets into the one Tuple. /// - Tuple, List, List, List> FetchMultiple(Sql sql); + (List, List, List, List) FetchMultiple(Sql sql); } } \ No newline at end of file diff --git a/test/NPoco.Tests/Async/QueryAsyncTests.cs b/test/NPoco.Tests/Async/QueryAsyncTests.cs index 785fc3c2..5494f840 100644 --- a/test/NPoco.Tests/Async/QueryAsyncTests.cs +++ b/test/NPoco.Tests/Async/QueryAsyncTests.cs @@ -42,5 +42,13 @@ public async Task PagingAsync() var records = await Database.PageAsync(2, 5, "SELECT u.* FROM Users u WHERE UserID <= 15"); Assert.AreEqual(records.Items.Count, 5); } + + [Test] + public async Task FetchMultipleAsync() + { + var (users, houses) = await Database.FetchMultipleAsync("select * from users;select * from houses"); + Assert.AreEqual(15, users.Count); + Assert.AreEqual(6, houses.Count); + } } } diff --git a/test/NPoco.Tests/NewMapper/NewMapperTests.cs b/test/NPoco.Tests/NewMapper/NewMapperTests.cs index a2006ea1..d6353db8 100644 --- a/test/NPoco.Tests/NewMapper/NewMapperTests.cs +++ b/test/NPoco.Tests/NewMapper/NewMapperTests.cs @@ -730,9 +730,6 @@ public class ResultData public void Test36() { var result = Database.Single("select 'Test' Data, 'Test2' Data1 /*poco_dual*/"); - - Database.Insert(result); - Assert.AreEqual("Test", result.GetData()); Assert.AreEqual(null, result.GetData1()); } From 8ead03fa97a3592df6b707592cd1f231dce2b6d8 Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Sat, 3 Oct 2020 22:35:12 +1000 Subject: [PATCH 22/42] Fix async and parent child mappings #569 --- NPoco.sln | 2 +- src/NPoco/AsyncDatabase.cs | 43 +-- src/NPoco/Database.cs | 284 +++++++----------- src/NPoco/IAsyncDatabase.cs | 4 +- src/NPoco/Linq/SimpleQueryProvider.cs | 40 +-- src/NPoco/PocoData.cs | 3 + src/NPoco/RowMappers/IRowMapper.cs | 7 +- src/NPoco/RowMappers/PropertyMapper.cs | 3 +- .../PropertyMapperNameConvention.cs | 73 ++--- test/NPoco.Tests/Common/SQLLocalDatabase.cs | 6 + .../QueryTests/ParentChildIncludeTests.cs | 36 +++ .../NewMapper/Models/ParentChild.cs | 27 ++ 12 files changed, 282 insertions(+), 246 deletions(-) create mode 100644 test/NPoco.Tests/DecoratedTests/QueryTests/ParentChildIncludeTests.cs create mode 100644 test/NPoco.Tests/NewMapper/Models/ParentChild.cs diff --git a/NPoco.sln b/NPoco.sln index 1a462279..334dee97 100644 --- a/NPoco.sln +++ b/NPoco.sln @@ -18,7 +18,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NPoco.Tests", "test\NPoco.T EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NPoco.JsonNet", "src\NPoco.JsonNet\NPoco.JsonNet.csproj", "{8DCCBC0A-36D4-4F3C-9B16-39B23AAFD726}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPoco.SqlServer", "src\NPoco.SqlServer\NPoco.SqlServer.csproj", "{15554441-8CF4-4B7B-A6D3-914463BFFF42}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NPoco.SqlServer", "src\NPoco.SqlServer\NPoco.SqlServer.csproj", "{15554441-8CF4-4B7B-A6D3-914463BFFF42}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/NPoco/AsyncDatabase.cs b/src/NPoco/AsyncDatabase.cs index 85730d5d..bb47997f 100644 --- a/src/NPoco/AsyncDatabase.cs +++ b/src/NPoco/AsyncDatabase.cs @@ -368,12 +368,12 @@ public ValueTask> FetchAsync() public async ValueTask> FetchAsync(string sql, params object[] args) { - return await (await QueryAsync(sql, args).ConfigureAwait(false)).ToListAsync().ConfigureAwait(false); + return await QueryAsync(sql, args).ToListAsync().ConfigureAwait(false); } public async ValueTask> FetchAsync(Sql sql) { - return await (await QueryAsync(sql).ConfigureAwait(false)).ToListAsync().ConfigureAwait(false); + return await QueryAsync(sql).ToListAsync().ConfigureAwait(false); } public Task FetchMultipleAsync(Func, List, TRet> cb, string sql, params object[] args) { return FetchMultipleImp(new[] { typeof(T1), typeof(T2) }, cb, new Sql(sql, args), false); } @@ -395,18 +395,20 @@ public IAsyncQueryProviderWithIncludes QueryAsync() return new AsyncQueryProvider(this); } - public Task> QueryAsync(string sql, params object[] args) + public IAsyncEnumerable QueryAsync(string sql, params object[] args) { return QueryAsync(new Sql(sql, args)); } - public Task> QueryAsync(Sql sql) + public IAsyncEnumerable QueryAsync(Sql sql) { return QueryAsync(default(T), null, null, sql); } - internal async Task> QueryAsync(T instance, Expression> listExpression, Func idFunc, Sql Sql) + internal async IAsyncEnumerable QueryAsync(T instance, Expression> listExpression, Func idFunc, Sql Sql, PocoData pocoData = null) { + pocoData ??= PocoDataFactory.ForType(typeof(T)); + var sql = Sql.SQL; var args = Sql.Arguments; @@ -416,66 +418,69 @@ internal async Task> QueryAsync(T instance, Expression(typeof(T), instance, reader, cmd)); + using var reader = await ExecuteDataReader(cmd, false).ConfigureAwait(false); + var read = (listExpression != null ? ReadOneToManyAsync(instance, reader, listExpression, idFunc, pocoData) : ReadAsync(instance, reader, pocoData)); + await foreach (var item in read) + { + yield return item; + } } - catch + finally { CloseSharedConnectionInternal(); - throw; } } public async ValueTask SingleAsync(string sql, params object[] args) { - return await (await QueryAsync(sql, args).ConfigureAwait(false)).SingleAsync().ConfigureAwait(false); + return await QueryAsync(sql, args).SingleAsync().ConfigureAwait(false); } public async ValueTask SingleAsync(Sql sql) { - return await (await QueryAsync(sql).ConfigureAwait(false)).SingleAsync().ConfigureAwait(false); + return await QueryAsync(sql).SingleAsync().ConfigureAwait(false); } public async ValueTask SingleOrDefaultAsync(string sql, params object[] args) { - return await (await QueryAsync(sql, args).ConfigureAwait(false)).SingleOrDefaultAsync().ConfigureAwait(false); + return await QueryAsync(sql, args).SingleOrDefaultAsync().ConfigureAwait(false); } public async ValueTask SingleOrDefaultAsync(Sql sql) { - return await (await QueryAsync(sql).ConfigureAwait(false)).SingleOrDefaultAsync().ConfigureAwait(false); + return await QueryAsync(sql).SingleOrDefaultAsync().ConfigureAwait(false); } public async ValueTask SingleByIdAsync(object primaryKey) { var sql = GenerateSingleByIdSql(primaryKey); - return await (await QueryAsync(sql).ConfigureAwait(false)).SingleAsync().ConfigureAwait(false); + return await QueryAsync(sql).SingleAsync().ConfigureAwait(false); } public async ValueTask SingleOrDefaultByIdAsync(object primaryKey) { var sql = GenerateSingleByIdSql(primaryKey); - return await (await QueryAsync(sql).ConfigureAwait(false)).SingleOrDefaultAsync().ConfigureAwait(false); + return await QueryAsync(sql).SingleOrDefaultAsync().ConfigureAwait(false); } public async ValueTask FirstAsync(string sql, params object[] args) { - return await (await QueryAsync(sql, args).ConfigureAwait(false)).FirstAsync().ConfigureAwait(false); + return await QueryAsync(sql).FirstAsync().ConfigureAwait(false); } public async ValueTask FirstAsync(Sql sql) { - return await (await QueryAsync(sql).ConfigureAwait(false)).FirstAsync().ConfigureAwait(false); + return await QueryAsync(sql).FirstAsync().ConfigureAwait(false); } public async ValueTask FirstOrDefaultAsync(string sql, params object[] args) { - return await (await QueryAsync(sql, args).ConfigureAwait(false)).FirstOrDefaultAsync().ConfigureAwait(false); + return await QueryAsync(sql).FirstOrDefaultAsync().ConfigureAwait(false); } public async ValueTask FirstOrDefaultAsync(Sql sql) { - return await (await QueryAsync(sql).ConfigureAwait(false)).FirstOrDefaultAsync().ConfigureAwait(false); + return await QueryAsync(sql).FirstOrDefaultAsync().ConfigureAwait(false); } public Task ExecuteAsync(string sql, params object[] args) diff --git a/src/NPoco/Database.cs b/src/NPoco/Database.cs index 9d7063a0..b32b389b 100644 --- a/src/NPoco/Database.cs +++ b/src/NPoco/Database.cs @@ -851,213 +851,157 @@ public IEnumerable Query(Sql Sql) return Query(default(T), Sql); } - private async IAsyncEnumerable ReadAsync(Type type, object instance, DbDataReader r, DbCommand cmd) + private async IAsyncEnumerable ReadAsync(object instance, DbDataReader r, PocoData pd) { - try + var factory = new MappingFactory(pd, r); + while (true) { - using (cmd) + T poco; + try { - using (r) - { - var pd = PocoDataFactory.ForType(type); - var factory = new MappingFactory(pd, r); - while (true) - { - T poco; - try - { - if (!await r.ReadAsync().ConfigureAwait(false)) yield break; - poco = (T)factory.Map(r, instance); - } - catch (Exception x) - { - OnExceptionInternal(x); - throw; - } - - yield return poco; - } - } + if (!await r.ReadAsync().ConfigureAwait(false)) yield break; + poco = (T)factory.Map(r, instance); } - } - finally - { - CloseSharedConnectionInternal(); + catch (Exception x) + { + OnExceptionInternal(x); + throw; + } + + yield return poco; } } - private async IAsyncEnumerable ReadOneToManyAsync(T instance, DbDataReader r, DbCommand cmd, Expression> listExpression, Func idFunc) + private async IAsyncEnumerable ReadOneToManyAsync(T instance, DbDataReader r, Expression> listExpression, Func idFunc, PocoData pocoData) { Func listFunc = null; PocoMember pocoMember = null; PocoMember foreignMember = null; - try + if (listExpression != null) { - using (cmd) - { - using (r) - { - var pocoData = PocoDataFactory.ForType(typeof(T)); - if (listExpression != null) - { - idFunc = idFunc ?? (x => pocoData.GetPrimaryKeyValues(x)); - listFunc = listExpression.Compile(); - var key = PocoColumn.GenerateKey(MemberChainHelper.GetMembers(listExpression)); - pocoMember = pocoData.Members.FirstOrDefault(x => x.Name == key); - foreignMember = pocoMember != null ? pocoMember.PocoMemberChildren.FirstOrDefault(x => x.Name == pocoMember.ReferenceMemberName && x.ReferenceType == ReferenceType.Foreign) : null; - } - - var factory = new MappingFactory(pocoData, r); - object prevPoco = null; - - while (true) - { - T poco; - try - { - if (!await r.ReadAsync().ConfigureAwait(false)) break; - poco = (T)factory.Map(r, instance); - } - catch (Exception x) - { - OnExceptionInternal(x); - throw; - } + idFunc = idFunc ?? (x => pocoData.GetPrimaryKeyValues(x)); + listFunc = listExpression.Compile(); + var key = PocoColumn.GenerateKey(MemberChainHelper.GetMembers(listExpression)); + pocoMember = pocoData.Members.FirstOrDefault(x => x.Name == key); + foreignMember = pocoMember != null ? pocoMember.PocoMemberChildren.FirstOrDefault(x => x.Name == pocoMember.ReferenceMemberName && x.ReferenceType == ReferenceType.Foreign) : null; + } - if (prevPoco != null) - { - if (listFunc != null - && pocoMember != null - && idFunc(poco).SequenceEqual(idFunc((T)prevPoco))) - { - OneToManyHelper.SetListValue(listFunc, pocoMember, prevPoco, poco); - continue; - } - - OneToManyHelper.SetForeignList(listFunc, foreignMember, prevPoco); - yield return (T)prevPoco; - } + var factory = new MappingFactory(pocoData, r); + object prevPoco = null; - prevPoco = poco; - } + while (true) + { + T poco; + try + { + if (!await r.ReadAsync().ConfigureAwait(false)) break; + poco = (T)factory.Map(r, instance); + } + catch (Exception x) + { + OnExceptionInternal(x); + throw; + } - if (prevPoco != null) - { - OneToManyHelper.SetForeignList(listFunc, foreignMember, prevPoco); - yield return (T)prevPoco; - } + if (prevPoco != null) + { + if (listFunc != null + && pocoMember != null + && idFunc(poco).SequenceEqual(idFunc((T)prevPoco))) + { + OneToManyHelper.SetListValue(listFunc, pocoMember, prevPoco, poco); + continue; } + + OneToManyHelper.SetForeignList(listFunc, foreignMember, prevPoco); + yield return (T)prevPoco; } + + prevPoco = poco; } - finally + + if (prevPoco != null) { - CloseSharedConnectionInternal(); + OneToManyHelper.SetForeignList(listFunc, foreignMember, prevPoco); + yield return (T)prevPoco; } } - private IEnumerable Read(Type type, object instance, DbDataReader r, DbCommand cmd) + private IEnumerable Read(object instance, DbDataReader r, PocoData pd) { - try + var factory = new MappingFactory(pd, r); + while (true) { - using (cmd) + T poco; + try { - using (r) - { - var pd = PocoDataFactory.ForType(type); - var factory = new MappingFactory(pd, r); - while (true) - { - T poco; - try - { - if (!r.Read()) yield break; - poco = (T)factory.Map(r, instance); - } - catch (Exception x) - { - OnExceptionInternal(x); - throw; - } - - yield return poco; - } - } + if (!r.Read()) yield break; + poco = (T)factory.Map(r, instance); } - } - finally - { - CloseSharedConnectionInternal(); + catch (Exception x) + { + OnExceptionInternal(x); + throw; + } + + yield return poco; } } - private IEnumerable ReadOneToMany(T instance, DbDataReader r, DbCommand cmd, Expression> listExpression, Func idFunc) + private IEnumerable ReadOneToMany(T instance, DbDataReader r, Expression> listExpression, Func idFunc, PocoData pocoData) { Func listFunc = null; PocoMember pocoMember = null; PocoMember foreignMember = null; - try + if (listExpression != null) { - using (cmd) - { - using (r) - { - var pocoData = PocoDataFactory.ForType(typeof(T)); - if (listExpression != null) - { - idFunc = idFunc ?? (x => pocoData.GetPrimaryKeyValues(x)); - listFunc = listExpression.Compile(); - var key = PocoColumn.GenerateKey(MemberChainHelper.GetMembers(listExpression)); - pocoMember = pocoData.Members.FirstOrDefault(x => x.Name == key); - foreignMember = pocoMember != null ? pocoMember.PocoMemberChildren.FirstOrDefault(x => x.Name == pocoMember.ReferenceMemberName && x.ReferenceType == ReferenceType.Foreign) : null; - } - - var factory = new MappingFactory(pocoData, r); - object prevPoco = null; - - while (true) - { - T poco; - try - { - if (!r.Read()) break; - poco = (T)factory.Map(r, instance); - } - catch (Exception x) - { - OnExceptionInternal(x); - throw; - } + idFunc = idFunc ?? (x => pocoData.GetPrimaryKeyValues(x)); + listFunc = listExpression.Compile(); + var key = PocoColumn.GenerateKey(MemberChainHelper.GetMembers(listExpression)); + pocoMember = pocoData.Members.FirstOrDefault(x => x.Name == key); + foreignMember = pocoMember != null ? pocoMember.PocoMemberChildren.FirstOrDefault(x => x.Name == pocoMember.ReferenceMemberName && x.ReferenceType == ReferenceType.Foreign) : null; + } - if (prevPoco != null) - { - if (listFunc != null - && pocoMember != null - && idFunc(poco).SequenceEqual(idFunc((T)prevPoco))) - { - OneToManyHelper.SetListValue(listFunc, pocoMember, prevPoco, poco); - continue; - } - - OneToManyHelper.SetForeignList(listFunc, foreignMember, prevPoco); - yield return (T)prevPoco; - } + var factory = new MappingFactory(pocoData, r); + object prevPoco = null; - prevPoco = poco; - } + while (true) + { + T poco; + try + { + if (!r.Read()) break; + poco = (T)factory.Map(r, instance); + } + catch (Exception x) + { + OnExceptionInternal(x); + throw; + } - if (prevPoco != null) - { - OneToManyHelper.SetForeignList(listFunc, foreignMember, prevPoco); - yield return (T)prevPoco; - } + if (prevPoco != null) + { + if (listFunc != null + && pocoMember != null + && idFunc(poco).SequenceEqual(idFunc((T)prevPoco))) + { + OneToManyHelper.SetListValue(listFunc, pocoMember, prevPoco, poco); + continue; } + + OneToManyHelper.SetForeignList(listFunc, foreignMember, prevPoco); + yield return (T)prevPoco; } + + prevPoco = poco; } - finally + + if (prevPoco != null) { - CloseSharedConnectionInternal(); + OneToManyHelper.SetForeignList(listFunc, foreignMember, prevPoco); + yield return (T)prevPoco; } } @@ -1097,8 +1041,8 @@ public IEnumerable Query(Type type, Sql Sql) { OpenSharedConnectionInternal(); using var cmd = CreateCommand(_sharedConnection, sql, args); - var reader = ExecuteDataReader(cmd, true).RunSync(); - var read = Read(type, null, reader, cmd); + using var reader = ExecuteDataReader(cmd, true).RunSync(); + var read = Read(null, reader, PocoDataFactory.ForType(type)); foreach (var item in read) { yield return item; @@ -1110,8 +1054,10 @@ public IEnumerable Query(Type type, Sql Sql) } } - internal IEnumerable QueryImp(T instance, Expression> listExpression, Func idFunc, Sql Sql) + internal IEnumerable QueryImp(T instance, Expression> listExpression, Func idFunc, Sql Sql, PocoData pocoData = null) { + pocoData ??= PocoDataFactory.ForType(typeof(T)); + var sql = Sql.SQL; var args = Sql.Arguments; @@ -1121,8 +1067,8 @@ internal IEnumerable QueryImp(T instance, Expression> listE { OpenSharedConnectionInternal(); using var cmd = CreateCommand(_sharedConnection, sql, args); - var reader = ExecuteDataReader(cmd, true).RunSync(); - var read = listExpression != null ? ReadOneToMany(instance, reader, cmd, listExpression, idFunc) : Read(typeof(T), instance, reader, cmd); + using var reader = ExecuteDataReader(cmd, true).RunSync(); + var read = listExpression != null ? ReadOneToMany(instance, reader, listExpression, idFunc, pocoData) : Read(instance, reader, pocoData); foreach (var item in read) { yield return item; diff --git a/src/NPoco/IAsyncDatabase.cs b/src/NPoco/IAsyncDatabase.cs index 8f0f7459..b232a9cb 100644 --- a/src/NPoco/IAsyncDatabase.cs +++ b/src/NPoco/IAsyncDatabase.cs @@ -153,13 +153,13 @@ public interface IAsyncQueryDatabase : IBaseDatabase /// Fetch objects of type T from the database using the sql and parameters specified. /// Caution: This query will only be executed once you start iterating the result /// - Task> QueryAsync(string sql, params object[] args); + IAsyncEnumerable QueryAsync(string sql, params object[] args); /// /// Fetch objects of type T from the database using the sql and parameters specified. /// Caution: This query will only be executed once you start iterating the result /// - Task> QueryAsync(Sql sql); + IAsyncEnumerable QueryAsync(Sql sql); /// /// Entry point for LINQ queries diff --git a/src/NPoco/Linq/SimpleQueryProvider.cs b/src/NPoco/Linq/SimpleQueryProvider.cs index 0068ee53..ba2a3b08 100644 --- a/src/NPoco/Linq/SimpleQueryProvider.cs +++ b/src/NPoco/Linq/SimpleQueryProvider.cs @@ -12,7 +12,7 @@ public interface IAsyncQueryResultProvider { ValueTask> ToList(); ValueTask ToArray(); - Task> ToEnumerable(); + IAsyncEnumerable ToEnumerable(); ValueTask FirstOrDefault(); ValueTask FirstOrDefault(Expression> whereExpression); ValueTask First(); @@ -56,7 +56,7 @@ public interface IQueryResultProvider List Distinct(); ValueTask> ToListAsync(); ValueTask ToArrayAsync(); - Task> ToEnumerableAsync(); + IAsyncEnumerable ToEnumerableAsync(); ValueTask FirstOrDefaultAsync(); ValueTask FirstOrDefaultAsync(Expression> whereExpression); ValueTask FirstAsync(); @@ -136,6 +136,7 @@ public AsyncQueryProvider(Database database, Expression> whereExpr { _database = database; _pocoData = database.PocoDataFactory.ForType(typeof(T)); + _pocoData.IsQueryGenerated = true; _sqlExpression = database.DatabaseType.ExpressionVisitor(database, _pocoData, true); _buildComplexSql = new ComplexSqlBuilder(database, _pocoData, _sqlExpression, _joinSqlExpressions); _sqlExpression = _sqlExpression.Where(whereExpression); @@ -215,17 +216,22 @@ private IAsyncQueryProviderWithIncludes QueryProviderWithIncludes(Expression public async ValueTask> ToList() { - return await (await ToEnumerable().ConfigureAwait(false)).ToListAsync().ConfigureAwait(false); + return await ToEnumerable().ToListAsync().ConfigureAwait(false); } public async ValueTask ToArray() { - return await (await ToEnumerable().ConfigureAwait(false)).ToArrayAsync().ConfigureAwait(false); + return await ToEnumerable().ToArrayAsync().ConfigureAwait(false); } - public Task> ToEnumerable() + public IAsyncEnumerable ToEnumerable() { - return _database.QueryAsync(default(T), _listExpression, null, BuildSql()); + return ExecuteQueryAsync(BuildSql()); + } + + private IAsyncEnumerable ExecuteQueryAsync(Sql sql) + { + return _database.QueryAsync(default, _listExpression, null, sql, _pocoData); } public ValueTask FirstOrDefault() @@ -236,7 +242,7 @@ public ValueTask FirstOrDefault() public async ValueTask FirstOrDefault(Expression> whereExpression) { AddWhere(whereExpression); - return await (await ToEnumerable().ConfigureAwait(false)).FirstOrDefaultAsync().ConfigureAwait(false); + return await ToEnumerable().FirstOrDefaultAsync().ConfigureAwait(false); } public ValueTask First() @@ -247,7 +253,7 @@ public ValueTask First() public async ValueTask First(Expression> whereExpression) { AddWhere(null); - return await (await ToEnumerable().ConfigureAwait(false)).FirstAsync().ConfigureAwait(false); + return await ToEnumerable().FirstAsync().ConfigureAwait(false); } public ValueTask SingleOrDefault() @@ -258,7 +264,7 @@ public ValueTask SingleOrDefault() public async ValueTask SingleOrDefault(Expression> whereExpression) { AddWhere(whereExpression); - return await (await ToEnumerable().ConfigureAwait(false)).SingleOrDefaultAsync().ConfigureAwait(false); + return await ToEnumerable().SingleOrDefaultAsync().ConfigureAwait(false); } public ValueTask Single() @@ -269,7 +275,7 @@ public ValueTask Single() public async ValueTask Single(Expression> whereExpression) { AddWhere(whereExpression); - return await (await ToEnumerable().ConfigureAwait(false)).SingleAsync().ConfigureAwait(false); + return await ToEnumerable().SingleAsync().ConfigureAwait(false); } public ValueTask Count() @@ -322,18 +328,18 @@ public async ValueTask> ToPage(int page, int pageSize) public async ValueTask> ProjectTo(Expression> projectionExpression) { var sql = _buildComplexSql.GetSqlForProjection(projectionExpression, false); - return await (await _database.QueryAsync(sql).ConfigureAwait(false)).Select(projectionExpression.Compile()).ToListAsync().ConfigureAwait(false); + return await ExecuteQueryAsync(sql).Select(projectionExpression.Compile()).ToListAsync().ConfigureAwait(false); } public async ValueTask> Distinct() { - return await (await _database.QueryAsync(new Sql(_sqlExpression.Context.ToSelectStatement(true, true), _sqlExpression.Context.Params)).ConfigureAwait(false)).ToListAsync().ConfigureAwait(false); + return await ExecuteQueryAsync(new Sql(_sqlExpression.Context.ToSelectStatement(true, true), _sqlExpression.Context.Params)).ToListAsync().ConfigureAwait(false); } public async ValueTask> Distinct(Expression> projectionExpression) { var sql = _buildComplexSql.GetSqlForProjection(projectionExpression, true); - return await (await _database.QueryAsync(sql).ConfigureAwait(false)).Select(projectionExpression.Compile()).ToListAsync().ConfigureAwait(false); + return await ExecuteQueryAsync(sql).Select(projectionExpression.Compile()).ToListAsync().ConfigureAwait(false); } public IAsyncQueryProvider Where(Expression> whereExpression) @@ -539,7 +545,6 @@ public QueryProvider(Database database) : base(database, null) { AddWhere(whereExpression); var sql = _buildComplexSql.BuildJoin(_database, _sqlExpression, _joinSqlExpressions.Values.ToList(), null, true, false); - return _database.ExecuteScalar(sql); } @@ -606,13 +611,12 @@ public QueryProvider(Database database) : base(database, null) public new IEnumerable ToEnumerable() { - var sql = BuildSql(); - return ExecuteQuery(sql); + return ExecuteQuery(BuildSql()); } private IEnumerable ExecuteQuery(Sql sql) { - return _database.QueryImp(default(T), _listExpression, null, sql); + return _database.QueryImp(default(T), _listExpression, null, sql, _pocoData); } #pragma warning restore CS0109 @@ -626,7 +630,7 @@ public ValueTask ToArrayAsync() return base.ToArray(); } - public Task> ToEnumerableAsync() + public IAsyncEnumerable ToEnumerableAsync() { return base.ToEnumerable(); } diff --git a/src/NPoco/PocoData.cs b/src/NPoco/PocoData.cs index fbbf1681..2d26c2ab 100644 --- a/src/NPoco/PocoData.cs +++ b/src/NPoco/PocoData.cs @@ -19,6 +19,9 @@ public class PocoData public List Members { get; protected internal set; } public List AllColumns { get; protected internal set; } + // This is used on a per query basis, if we have cache PocoData then this will need to change. + public bool IsQueryGenerated { get; set; } + public PocoData() { } diff --git a/src/NPoco/RowMappers/IRowMapper.cs b/src/NPoco/RowMappers/IRowMapper.cs index 36db2d2f..72676772 100644 --- a/src/NPoco/RowMappers/IRowMapper.cs +++ b/src/NPoco/RowMappers/IRowMapper.cs @@ -31,7 +31,12 @@ protected PosName[] GetColumnNames(DbDataReader dataReader, PocoData pocoData) var cols = Enumerable.Range(0, dataReader.FieldCount) .Select(x => new PosName { Pos = x, Name = dataReader.GetName(x) }) .Where(x => !string.Equals("poco_rn", x.Name)) - .ToList(); + .ToArray(); + + if (pocoData.IsQueryGenerated) + { + return cols; + } if (cols.Any(x => x.Name.StartsWith(PropertyMapperNameConvention.SplitPrefix, StringComparison.OrdinalIgnoreCase))) { diff --git a/src/NPoco/RowMappers/PropertyMapper.cs b/src/NPoco/RowMappers/PropertyMapper.cs index 0e1887dc..07e6e285 100644 --- a/src/NPoco/RowMappers/PropertyMapper.cs +++ b/src/NPoco/RowMappers/PropertyMapper.cs @@ -74,7 +74,8 @@ private MapPlan BuildMapPlan(DbDataReader dataReader, PocoData pocoData) private IEnumerable BuildMapPlans(GroupResult groupedName, DbDataReader dataReader, PocoData pocoData, List pocoMembers) { // find pocomember by property name - var pocoMember = pocoMembers.FirstOrDefault(x => IsEqual(groupedName.Item, x.Name) || IsEqual(groupedName.Item, x.PocoColumn?.ColumnAlias)); + var pocoMember = pocoMembers.FirstOrDefault(x => IsEqual(groupedName.Item, x.Name) + || IsEqual(groupedName.Item, x.PocoColumn?.ColumnAlias)); if (pocoMember == null) { diff --git a/src/NPoco/RowMappers/PropertyMapperNameConvention.cs b/src/NPoco/RowMappers/PropertyMapperNameConvention.cs index 2a172735..89165cf4 100644 --- a/src/NPoco/RowMappers/PropertyMapperNameConvention.cs +++ b/src/NPoco/RowMappers/PropertyMapperNameConvention.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; @@ -8,9 +9,9 @@ public static class PropertyMapperNameConvention { public static string SplitPrefix = "npoco_"; - public static IEnumerable ConvertFromNewConvention(this IEnumerable posNames, PocoData pocoData) + internal static IEnumerable ConvertFromNewConvention(this IEnumerable posNames, PocoData pocoData) { - var allMembers = pocoData.GetAllMembers(); + var allMembers = pocoData.GetAllMembers().ToList(); var scopedPocoMembers = pocoData.Members; string prefix = null; @@ -49,58 +50,69 @@ public static IEnumerable ConvertFromNewConvention(this IEnumerable ConvertFromOldConvention(this IEnumerable posNames, List pocoMembers) + internal static IEnumerable ConvertFromOldConvention(this IEnumerable posNames, List pocoMembers) { var used = new Dictionary(); - var members = FlattenPocoMembers(pocoMembers, new LevelMonitor()).ToList(); + var members = FlattenPocoMembers(pocoMembers).ToList(); var level = 0; foreach (var posName in posNames) { - var unusedPocoMembers = members.Where(x => !used.ContainsKey(x.PocoMember) && x.Level >= level) - .Select(x => x.PocoMember) - .ToList(); - - var member = FindMember(unusedPocoMembers, posName.Name); - if (member != null && member.PocoColumn != null) + var pocoMemberLevels = members.Where(x => !used.ContainsKey(x.PocoMember) && x.Level >= level); + var member = FindMember(pocoMemberLevels, posName.Name); + + if (member?.PocoMember?.PocoColumn != null) { - level = members.Single(x => x.PocoMember == member).Level; - used.Add(member, level); - posName.Name = member.PocoColumn.MemberInfoKey; + level = member.Level; + used.Add(member.PocoMember, level); + posName.Name = member.PocoMember.PocoColumn.MemberInfoKey; } yield return posName; } } - - public static PocoMember FindMember(List pocoMembers, string name) + + internal static PocoMemberLevel FindMember(IEnumerable pocoMembers, string name) + { + return pocoMembers + .Where(x => x.PocoMember.ReferenceType == ReferenceType.None) + .FirstOrDefault(x => IsPocoMemberEqual(x.PocoMember, name)); + } + + internal static PocoMember FindMember(IEnumerable pocoMembers, string name) { return pocoMembers .Where(x => x.ReferenceType == ReferenceType.None) - .FirstOrDefault(x => - { - var col = x.PocoColumn; + .FirstOrDefault(x => IsPocoMemberEqual(x, name)); + } - if (col != null && PropertyMapper.IsEqual(name, col.ColumnAlias ?? col.ColumnName)) - return true; + private static bool IsPocoMemberEqual(PocoMember pocoMember, string name) + { + if (pocoMember.PocoColumn == null) + return PropertyMapper.IsEqual(name, pocoMember.Name); + + if (pocoMember.PocoColumn.MemberInfoKey == name) + return true; + + if (PropertyMapper.IsEqual(name, pocoMember.PocoColumn.ColumnAlias ?? pocoMember.PocoColumn.ColumnName)) + return true; - return PropertyMapper.IsEqual(name, x.Name); - }); + return PropertyMapper.IsEqual(name, pocoMember.Name); } - private static IEnumerable FlattenPocoMembers(List pocoMembers, LevelMonitor levelMonitor) + private static IEnumerable FlattenPocoMembers(List pocoMembers, int levelMonitor = 1) { foreach (var pocoMember in pocoMembers.OrderBy(x => x.PocoMemberChildren.Count != 0)) { if (pocoMember.PocoColumn != null) { - yield return new PocoMemberLevel { PocoMember = pocoMember, Level = levelMonitor.Level }; + yield return new PocoMemberLevel { PocoMember = pocoMember, Level = levelMonitor }; } if (pocoMember.PocoMemberChildren.Count == 0) continue; - levelMonitor.Level++; + levelMonitor++; foreach (var pocoMemberLevel in FlattenPocoMembers(pocoMember.PocoMemberChildren, levelMonitor)) { yield return pocoMemberLevel; @@ -108,19 +120,10 @@ private static IEnumerable FlattenPocoMembers(List } } - private struct PocoMemberLevel + internal class PocoMemberLevel { public PocoMember PocoMember { get; set; } public int Level { get; set; } } - - private class LevelMonitor - { - public LevelMonitor() - { - Level = 1; - } - public int Level { get; set; } - } } } \ No newline at end of file diff --git a/test/NPoco.Tests/Common/SQLLocalDatabase.cs b/test/NPoco.Tests/Common/SQLLocalDatabase.cs index 9ca882e5..ac415aeb 100644 --- a/test/NPoco.Tests/Common/SQLLocalDatabase.cs +++ b/test/NPoco.Tests/Common/SQLLocalDatabase.cs @@ -219,6 +219,12 @@ select @Name "; cmd.ExecuteNonQuery(); + cmd.CommandText = "CREATE TABLE Parent (ParentId int NOT NULL PRIMARY KEY, Id int NOT NULL)"; + cmd.ExecuteNonQuery(); + cmd.CommandText = "CREATE TABLE Child (ChildId int NOT NULL PRIMARY KEY, ParentId int NOT NULL, CONSTRAINT fk FOREIGN KEY (ParentId) REFERENCES Parent(ParentId))"; + cmd.ExecuteNonQuery(); + + // Console.WriteLine("Tables (CreateDB): " + Environment.NewLine); // var dt = conn.GetSchema("Tables"); // foreach (DataRow row in dt.Rows) diff --git a/test/NPoco.Tests/DecoratedTests/QueryTests/ParentChildIncludeTests.cs b/test/NPoco.Tests/DecoratedTests/QueryTests/ParentChildIncludeTests.cs new file mode 100644 index 00000000..6a9cf1d2 --- /dev/null +++ b/test/NPoco.Tests/DecoratedTests/QueryTests/ParentChildIncludeTests.cs @@ -0,0 +1,36 @@ +using NPoco.Tests.Common; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using NPoco.Tests.NewMapper.Models; + +namespace NPoco.Tests.DecoratedTests.QueryTests +{ + [TestFixture] + public class ParentChildIncludeTests : BaseDBDecoratedTest + { + [Test] + public async Task TestParentChildDataIsCorrectlyPopulated() + { + await Database.InsertAsync(new Parent { ParentId = 1, Id = 11 }); + await Database.InsertAsync(new Parent { ParentId = 2, Id = 22 }); + await Database.InsertAsync(new Child { ChildId = 100, ParentId = 1 }); + await Database.InsertAsync(new Child { ChildId = 200, ParentId = 2 }); + + var children = await Database.Query() + .Include(c => c.Parent) + .ToListAsync(); + + Assert.AreEqual(children[0].ChildId, 100); + Assert.AreEqual(children[0].ParentId, 1); + Assert.AreEqual(children[0].Parent.ParentId, 1); + Assert.AreEqual(children[0].Parent.Id, 11); + Assert.AreEqual(children[1].ChildId, 200); + Assert.AreEqual(children[1].ParentId, 2); + Assert.AreEqual(children[1].Parent.ParentId, 2); + Assert.AreEqual(children[1].Parent.Id, 22); + } + } +} diff --git a/test/NPoco.Tests/NewMapper/Models/ParentChild.cs b/test/NPoco.Tests/NewMapper/Models/ParentChild.cs new file mode 100644 index 00000000..c6348d6e --- /dev/null +++ b/test/NPoco.Tests/NewMapper/Models/ParentChild.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NPoco.Tests.NewMapper.Models +{ + [PrimaryKey("ParentId", AutoIncrement = false)] + public class Parent + { + public int ParentId { get; set; } + public int Id { get; set; } + + public override string ToString() => $"ParentId={ParentId}, Id={Id}"; + } + + [PrimaryKey("ChildId", AutoIncrement = false)] + public class Child + { + public int ChildId { get; set; } + public int ParentId { get; set; } + + [Reference(ReferenceType.OneToOne, ColumnName = "ParentId", ReferenceMemberName = "ParentId")] + public Parent Parent { get; set; } + + public override string ToString() => $"ChildId={ChildId}, ParentId={ParentId} ({Parent?.ToString()})"; + } +} From abc0ca069de2399b0faaffc42755d9522b4d43a1 Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Sun, 4 Oct 2020 14:53:33 +1100 Subject: [PATCH 23/42] Add quick test for OneToOne --- .../QueryTests/ParentChildIncludeTests.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/NPoco.Tests/DecoratedTests/QueryTests/ParentChildIncludeTests.cs b/test/NPoco.Tests/DecoratedTests/QueryTests/ParentChildIncludeTests.cs index 6a9cf1d2..e64b4d2d 100644 --- a/test/NPoco.Tests/DecoratedTests/QueryTests/ParentChildIncludeTests.cs +++ b/test/NPoco.Tests/DecoratedTests/QueryTests/ParentChildIncludeTests.cs @@ -32,5 +32,29 @@ public async Task TestParentChildDataIsCorrectlyPopulated() Assert.AreEqual(children[1].Parent.ParentId, 2); Assert.AreEqual(children[1].Parent.Id, 22); } + + [Test] + public void TestOneToOneWithIdAndRefObject() + { + var userDec = Database.Query() + .Include(x => x.House) + .Where(x => x.UserId == 2) + .Single(); + + Assert.AreEqual(userDec.UserId, 2); + Assert.AreEqual(userDec.HouseId, 2); + Assert.AreEqual(userDec.House.HouseId, 2); + Assert.AreEqual(userDec.House.Address, "1 Road Street, Suburb"); + } + + [TableName("Users"), PrimaryKey("UserId")] + public class MyUserDec + { + public int UserId { get; set; } + public int HouseId { get; set; } + + [Reference(ReferenceType.OneToOne, ColumnName = "HouseId", ReferenceMemberName = "HouseId")] + public HouseDecorated House { get; set; } + } } } From 6947369a414b63467928587e7759321c714587bc Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Mon, 5 Oct 2020 11:59:00 +1100 Subject: [PATCH 24/42] Revert back to Task --- src/NPoco/AsyncDatabase.cs | 34 ++--- src/NPoco/IAsyncDatabase.cs | 34 ++--- src/NPoco/Linq/SimpleQueryProvider.cs | 144 +++++++++++----------- test/NPoco.Tests/Async/QueryAsyncTests.cs | 11 ++ 4 files changed, 117 insertions(+), 106 deletions(-) diff --git a/src/NPoco/AsyncDatabase.cs b/src/NPoco/AsyncDatabase.cs index bb47997f..2c8d7b3f 100644 --- a/src/NPoco/AsyncDatabase.cs +++ b/src/NPoco/AsyncDatabase.cs @@ -292,24 +292,24 @@ public Task> PageAsync(long page, long itemsPerPage, string sql, para return PageImpAsync(page, itemsPerPage, sql, args, false); } - public ValueTask> FetchAsync(long page, long itemsPerPage, string sql, params object[] args) + public Task> FetchAsync(long page, long itemsPerPage, string sql, params object[] args) { return SkipTakeAsync((page - 1) * itemsPerPage, itemsPerPage, sql, args); } - public ValueTask> FetchAsync(long page, long itemsPerPage, Sql sql) + public Task> FetchAsync(long page, long itemsPerPage, Sql sql) { return SkipTakeAsync((page - 1) * itemsPerPage, itemsPerPage, sql.SQL, sql.Arguments); } - public ValueTask> SkipTakeAsync(long skip, long take, string sql, params object[] args) + public Task> SkipTakeAsync(long skip, long take, string sql, params object[] args) { string sqlCount, sqlPage; BuildPageQueries(skip, take, sql, ref args, out sqlCount, out sqlPage); return FetchAsync(sqlPage, args); } - public ValueTask> SkipTakeAsync(long skip, long take, Sql sql) + public Task> SkipTakeAsync(long skip, long take, Sql sql) { return SkipTakeAsync(skip, take, sql.SQL, sql.Arguments); } @@ -361,17 +361,17 @@ private Sql GetExistsSql(object primaryKeyorPoco, bool isPoco) return new Sql(sql, args); } - public ValueTask> FetchAsync() + public Task> FetchAsync() { return FetchAsync(""); } - public async ValueTask> FetchAsync(string sql, params object[] args) + public async Task> FetchAsync(string sql, params object[] args) { return await QueryAsync(sql, args).ToListAsync().ConfigureAwait(false); } - public async ValueTask> FetchAsync(Sql sql) + public async Task> FetchAsync(Sql sql) { return await QueryAsync(sql).ToListAsync().ConfigureAwait(false); } @@ -431,54 +431,54 @@ internal async IAsyncEnumerable QueryAsync(T instance, Expression SingleAsync(string sql, params object[] args) + public async Task SingleAsync(string sql, params object[] args) { return await QueryAsync(sql, args).SingleAsync().ConfigureAwait(false); } - public async ValueTask SingleAsync(Sql sql) + public async Task SingleAsync(Sql sql) { return await QueryAsync(sql).SingleAsync().ConfigureAwait(false); } - public async ValueTask SingleOrDefaultAsync(string sql, params object[] args) + public async Task SingleOrDefaultAsync(string sql, params object[] args) { return await QueryAsync(sql, args).SingleOrDefaultAsync().ConfigureAwait(false); } - public async ValueTask SingleOrDefaultAsync(Sql sql) + public async Task SingleOrDefaultAsync(Sql sql) { return await QueryAsync(sql).SingleOrDefaultAsync().ConfigureAwait(false); } - public async ValueTask SingleByIdAsync(object primaryKey) + public async Task SingleByIdAsync(object primaryKey) { var sql = GenerateSingleByIdSql(primaryKey); return await QueryAsync(sql).SingleAsync().ConfigureAwait(false); } - public async ValueTask SingleOrDefaultByIdAsync(object primaryKey) + public async Task SingleOrDefaultByIdAsync(object primaryKey) { var sql = GenerateSingleByIdSql(primaryKey); return await QueryAsync(sql).SingleOrDefaultAsync().ConfigureAwait(false); } - public async ValueTask FirstAsync(string sql, params object[] args) + public async Task FirstAsync(string sql, params object[] args) { return await QueryAsync(sql).FirstAsync().ConfigureAwait(false); } - public async ValueTask FirstAsync(Sql sql) + public async Task FirstAsync(Sql sql) { return await QueryAsync(sql).FirstAsync().ConfigureAwait(false); } - public async ValueTask FirstOrDefaultAsync(string sql, params object[] args) + public async Task FirstOrDefaultAsync(string sql, params object[] args) { return await QueryAsync(sql).FirstOrDefaultAsync().ConfigureAwait(false); } - public async ValueTask FirstOrDefaultAsync(Sql sql) + public async Task FirstOrDefaultAsync(Sql sql) { return await QueryAsync(sql).FirstOrDefaultAsync().ConfigureAwait(false); } diff --git a/src/NPoco/IAsyncDatabase.cs b/src/NPoco/IAsyncDatabase.cs index b232a9cb..94ad06df 100644 --- a/src/NPoco/IAsyncDatabase.cs +++ b/src/NPoco/IAsyncDatabase.cs @@ -102,52 +102,52 @@ public interface IAsyncQueryDatabase : IBaseDatabase /// Fetch the only row of type T using the sql and parameters specified /// Get an object of type T by primary key value /// - ValueTask SingleAsync(string sql, params object[] args); + Task SingleAsync(string sql, params object[] args); /// /// Fetch the only row of type T using the sql and parameters specified /// - ValueTask SingleAsync(Sql sql); + Task SingleAsync(Sql sql); /// /// Fetch the only row of type T using the sql and parameters specified /// - ValueTask SingleOrDefaultAsync(string sql, params object[] args); + Task SingleOrDefaultAsync(string sql, params object[] args); /// /// Fetch the only row of type T using the sql and parameters specified /// - ValueTask SingleOrDefaultAsync(Sql sql); + Task SingleOrDefaultAsync(Sql sql); /// /// Get an object of type T by primary key value /// - ValueTask SingleByIdAsync(object primaryKey); + Task SingleByIdAsync(object primaryKey); /// /// Get an object of type T by primary key value /// - ValueTask SingleOrDefaultByIdAsync(object primaryKey); + Task SingleOrDefaultByIdAsync(object primaryKey); /// /// Fetch the first row of type T using the sql and parameters specified /// - ValueTask FirstAsync(string sql, params object[] args); + Task FirstAsync(string sql, params object[] args); /// /// Fetch the first row of type T using the sql and parameters specified /// - ValueTask FirstAsync(Sql sql); + Task FirstAsync(Sql sql); /// /// Fetch the first row of type T using the sql and parameters specified /// - ValueTask FirstOrDefaultAsync(string sql, params object[] args); + Task FirstOrDefaultAsync(string sql, params object[] args); /// /// Fetch the first row of type T using the sql and parameters specified /// - ValueTask FirstOrDefaultAsync(Sql sql); + Task FirstOrDefaultAsync(Sql sql); /// /// Fetch objects of type T from the database using the sql and parameters specified. @@ -169,17 +169,17 @@ public interface IAsyncQueryDatabase : IBaseDatabase /// /// Fetch objects of type T from the database using the sql and parameters specified. /// - ValueTask> FetchAsync(string sql, params object[] args); + Task> FetchAsync(string sql, params object[] args); /// /// Fetch objects of type T from the database using the sql and parameters specified. /// - ValueTask> FetchAsync(Sql sql); + Task> FetchAsync(Sql sql); /// /// Fetch all objects of type T from the database. /// - ValueTask> FetchAsync(); + Task> FetchAsync(); /// /// Fetch objects of type T from the database using the sql and parameters specified. @@ -201,25 +201,25 @@ public interface IAsyncQueryDatabase : IBaseDatabase /// Fetch objects of type T from the database using the sql and parameters specified. /// The sql provided will be converted so that only the results for the page and itemsPerPage values specified will be returned. /// - ValueTask> FetchAsync(long page, long itemsPerPage, string sql, params object[] args); + Task> FetchAsync(long page, long itemsPerPage, string sql, params object[] args); /// /// Fetch objects of type T from the database using the sql and parameters specified. /// The sql provided will be converted so that only the results for the page and itemsPerPage values specified will be returned. /// - ValueTask> FetchAsync(long page, long itemsPerPage, Sql sql); + Task> FetchAsync(long page, long itemsPerPage, Sql sql); /// /// Fetch objects of type T from the database using the sql and parameters specified. /// The sql provided will be converted so that only the results for the skip and take values specified will be returned. /// - ValueTask> SkipTakeAsync(long skip, long take, string sql, params object[] args); + Task> SkipTakeAsync(long skip, long take, string sql, params object[] args); /// /// Fetch objects of type T from the database using the sql and parameters specified. /// The sql provided will be converted so that only the results for the skip and take values specified will be returned. /// - ValueTask> SkipTakeAsync(long skip, long take, Sql sql); + Task> SkipTakeAsync(long skip, long take, Sql sql); /// /// Fetches multiple result sets into the one object. diff --git a/src/NPoco/Linq/SimpleQueryProvider.cs b/src/NPoco/Linq/SimpleQueryProvider.cs index ba2a3b08..59104384 100644 --- a/src/NPoco/Linq/SimpleQueryProvider.cs +++ b/src/NPoco/Linq/SimpleQueryProvider.cs @@ -10,25 +10,25 @@ namespace NPoco.Linq { public interface IAsyncQueryResultProvider { - ValueTask> ToList(); - ValueTask ToArray(); + Task> ToList(); + Task ToArray(); IAsyncEnumerable ToEnumerable(); - ValueTask FirstOrDefault(); - ValueTask FirstOrDefault(Expression> whereExpression); - ValueTask First(); - ValueTask First(Expression> whereExpression); - ValueTask SingleOrDefault(); - ValueTask SingleOrDefault(Expression> whereExpression); - ValueTask Single(); - ValueTask Single(Expression> whereExpression); - ValueTask Count(); - ValueTask Count(Expression> whereExpression); - ValueTask Any(); - ValueTask Any(Expression> whereExpression); - ValueTask> ToPage(int page, int pageSize); - ValueTask> ProjectTo(Expression> projectionExpression); - ValueTask> Distinct(Expression> projectionExpression); - ValueTask> Distinct(); + Task FirstOrDefault(); + Task FirstOrDefault(Expression> whereExpression); + Task First(); + Task First(Expression> whereExpression); + Task SingleOrDefault(); + Task SingleOrDefault(Expression> whereExpression); + Task Single(); + Task Single(Expression> whereExpression); + Task Count(); + Task Count(Expression> whereExpression); + Task Any(); + Task Any(Expression> whereExpression); + Task> ToPage(int page, int pageSize); + Task> ProjectTo(Expression> projectionExpression); + Task> Distinct(Expression> projectionExpression); + Task> Distinct(); } public interface IQueryResultProvider @@ -54,25 +54,25 @@ public interface IQueryResultProvider List ProjectTo(Expression> projectionExpression); List Distinct(Expression> projectionExpression); List Distinct(); - ValueTask> ToListAsync(); - ValueTask ToArrayAsync(); + Task> ToListAsync(); + Task ToArrayAsync(); IAsyncEnumerable ToEnumerableAsync(); - ValueTask FirstOrDefaultAsync(); - ValueTask FirstOrDefaultAsync(Expression> whereExpression); - ValueTask FirstAsync(); - ValueTask FirstAsync(Expression> whereExpression); - ValueTask SingleOrDefaultAsync(); - ValueTask SingleOrDefaultAsync(Expression> whereExpression); - ValueTask SingleAsync(); - ValueTask SingleAsync(Expression> whereExpression); - ValueTask CountAsync(); - ValueTask CountAsync(Expression> whereExpression); - ValueTask AnyAsync(); - ValueTask AnyAsync(Expression> whereExpression); - ValueTask> ToPageAsync(int page, int pageSize); - ValueTask> ProjectToAsync(Expression> projectionExpression); - ValueTask> DistinctAsync(Expression> projectionExpression); - ValueTask> DistinctAsync(); + Task FirstOrDefaultAsync(); + Task FirstOrDefaultAsync(Expression> whereExpression); + Task FirstAsync(); + Task FirstAsync(Expression> whereExpression); + Task SingleOrDefaultAsync(); + Task SingleOrDefaultAsync(Expression> whereExpression); + Task SingleAsync(); + Task SingleAsync(Expression> whereExpression); + Task CountAsync(); + Task CountAsync(Expression> whereExpression); + Task AnyAsync(); + Task AnyAsync(Expression> whereExpression); + Task> ToPageAsync(int page, int pageSize); + Task> ProjectToAsync(Expression> projectionExpression); + Task> DistinctAsync(Expression> projectionExpression); + Task> DistinctAsync(); } public interface IQueryProvider : IQueryResultProvider @@ -214,12 +214,12 @@ private IAsyncQueryProviderWithIncludes QueryProviderWithIncludes(Expression return this; } - public async ValueTask> ToList() + public async Task> ToList() { return await ToEnumerable().ToListAsync().ConfigureAwait(false); } - public async ValueTask ToArray() + public async Task ToArray() { return await ToEnumerable().ToArrayAsync().ConfigureAwait(false); } @@ -234,73 +234,73 @@ private IAsyncEnumerable ExecuteQueryAsync(Sql sql) return _database.QueryAsync(default, _listExpression, null, sql, _pocoData); } - public ValueTask FirstOrDefault() + public Task FirstOrDefault() { return FirstOrDefault(null); } - public async ValueTask FirstOrDefault(Expression> whereExpression) + public async Task FirstOrDefault(Expression> whereExpression) { AddWhere(whereExpression); return await ToEnumerable().FirstOrDefaultAsync().ConfigureAwait(false); } - public ValueTask First() + public Task First() { return First(null); } - public async ValueTask First(Expression> whereExpression) + public async Task First(Expression> whereExpression) { AddWhere(null); return await ToEnumerable().FirstAsync().ConfigureAwait(false); } - public ValueTask SingleOrDefault() + public Task SingleOrDefault() { return SingleOrDefault(null); } - public async ValueTask SingleOrDefault(Expression> whereExpression) + public async Task SingleOrDefault(Expression> whereExpression) { AddWhere(whereExpression); return await ToEnumerable().SingleOrDefaultAsync().ConfigureAwait(false); } - public ValueTask Single() + public Task Single() { return Single(null); } - public async ValueTask Single(Expression> whereExpression) + public async Task Single(Expression> whereExpression) { AddWhere(whereExpression); return await ToEnumerable().SingleAsync().ConfigureAwait(false); } - public ValueTask Count() + public Task Count() { return Count(null); } - public async ValueTask Count(Expression> whereExpression) + public async Task Count(Expression> whereExpression) { AddWhere(whereExpression); var sql = _buildComplexSql.BuildJoin(_database, _sqlExpression, _joinSqlExpressions.Values.ToList(), null, true, false); return await _database.ExecuteScalarAsync(sql).ConfigureAwait(false); } - public ValueTask Any() + public Task Any() { return Any(null); } - public async ValueTask Any(Expression> whereExpression) + public async Task Any(Expression> whereExpression) { return (await Count(whereExpression).ConfigureAwait(false)) > 0; } - public async ValueTask> ToPage(int page, int pageSize) + public async Task> ToPage(int page, int pageSize) { int offset = (page - 1) * pageSize; @@ -325,18 +325,18 @@ public async ValueTask> ToPage(int page, int pageSize) return result; } - public async ValueTask> ProjectTo(Expression> projectionExpression) + public async Task> ProjectTo(Expression> projectionExpression) { var sql = _buildComplexSql.GetSqlForProjection(projectionExpression, false); return await ExecuteQueryAsync(sql).Select(projectionExpression.Compile()).ToListAsync().ConfigureAwait(false); } - public async ValueTask> Distinct() + public async Task> Distinct() { return await ExecuteQueryAsync(new Sql(_sqlExpression.Context.ToSelectStatement(true, true), _sqlExpression.Context.Params)).ToListAsync().ConfigureAwait(false); } - public async ValueTask> Distinct(Expression> projectionExpression) + public async Task> Distinct(Expression> projectionExpression) { var sql = _buildComplexSql.GetSqlForProjection(projectionExpression, true); return await ExecuteQueryAsync(sql).Select(projectionExpression.Compile()).ToListAsync().ConfigureAwait(false); @@ -620,12 +620,12 @@ private IEnumerable ExecuteQuery(Sql sql) } #pragma warning restore CS0109 - public ValueTask> ToListAsync() + public Task> ToListAsync() { return base.ToList(); } - public ValueTask ToArrayAsync() + public Task ToArrayAsync() { return base.ToArray(); } @@ -635,82 +635,82 @@ public IAsyncEnumerable ToEnumerableAsync() return base.ToEnumerable(); } - public ValueTask FirstOrDefaultAsync() + public Task FirstOrDefaultAsync() { return base.FirstOrDefault(); } - public ValueTask FirstOrDefaultAsync(Expression> whereExpression) + public Task FirstOrDefaultAsync(Expression> whereExpression) { return base.FirstOrDefault(whereExpression); } - public ValueTask FirstAsync() + public Task FirstAsync() { return base.First(); } - public ValueTask FirstAsync(Expression> whereExpression) + public Task FirstAsync(Expression> whereExpression) { return base.First(whereExpression); } - public ValueTask SingleOrDefaultAsync() + public Task SingleOrDefaultAsync() { return base.SingleOrDefault(); } - public ValueTask SingleOrDefaultAsync(Expression> whereExpression) + public Task SingleOrDefaultAsync(Expression> whereExpression) { return base.SingleOrDefault(whereExpression); } - public ValueTask SingleAsync() + public Task SingleAsync() { return base.Single(); } - public ValueTask SingleAsync(Expression> whereExpression) + public Task SingleAsync(Expression> whereExpression) { return base.Single(whereExpression); } - public ValueTask CountAsync() + public Task CountAsync() { return base.Count(); } - public ValueTask CountAsync(Expression> whereExpression) + public Task CountAsync(Expression> whereExpression) { return base.Count(whereExpression); } - public ValueTask AnyAsync() + public Task AnyAsync() { return base.Any(); } - public ValueTask AnyAsync(Expression> whereExpression) + public Task AnyAsync(Expression> whereExpression) { return base.Any(whereExpression); } - public ValueTask> ToPageAsync(int page, int pageSize) + public Task> ToPageAsync(int page, int pageSize) { return base.ToPage(page, pageSize); } - public ValueTask> ProjectToAsync(Expression> projectionExpression) + public Task> ProjectToAsync(Expression> projectionExpression) { return base.ProjectTo(projectionExpression); } - public ValueTask> DistinctAsync(Expression> projectionExpression) + public Task> DistinctAsync(Expression> projectionExpression) { return base.Distinct(projectionExpression); } - public ValueTask> DistinctAsync() + public Task> DistinctAsync() { return base.Distinct(); } diff --git a/test/NPoco.Tests/Async/QueryAsyncTests.cs b/test/NPoco.Tests/Async/QueryAsyncTests.cs index 5494f840..9d075709 100644 --- a/test/NPoco.Tests/Async/QueryAsyncTests.cs +++ b/test/NPoco.Tests/Async/QueryAsyncTests.cs @@ -50,5 +50,16 @@ public async Task FetchMultipleAsync() Assert.AreEqual(15, users.Count); Assert.AreEqual(6, houses.Count); } + + [Test] + public async Task QueryAsyncSql() + { + var i = 1; + var userCount = Database.QueryAsync("select * from users"); + await foreach (var item in userCount) + { + Assert.AreEqual(item.UserId, i++); + } + } } } From 99788a22a6a637528e23094a312dcbda42d31e42 Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Mon, 5 Oct 2020 17:35:37 +1100 Subject: [PATCH 25/42] Remove AsyncEnumeratorAdapter --- src/NPoco/AsyncEnumeratorAdapter.cs | 212 ---------------------------- 1 file changed, 212 deletions(-) delete mode 100644 src/NPoco/AsyncEnumeratorAdapter.cs diff --git a/src/NPoco/AsyncEnumeratorAdapter.cs b/src/NPoco/AsyncEnumeratorAdapter.cs deleted file mode 100644 index 5c43ebc4..00000000 --- a/src/NPoco/AsyncEnumeratorAdapter.cs +++ /dev/null @@ -1,212 +0,0 @@ -using System.ComponentModel; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using System.Threading.Tasks.Sources; - -namespace System.Collections.Generic -{ - /// - /// Provides extension methods that enable use of await foreach - /// with . - /// - [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] - public static class AsyncEnumeratorAdapter - { - /// - public readonly struct Dispose - { - private readonly IAsyncEnumerator m_enumerator; - - public Dispose(IAsyncEnumerator enumerator) - { - m_enumerator = enumerator; - } - - public IAsyncEnumerator GetAsyncEnumerator() => m_enumerator; - } - - /// - public readonly struct KeepAlive - { - private readonly IAsyncEnumerator m_enumerator; - - public KeepAlive(IAsyncEnumerator enumerator) - { - m_enumerator = enumerator; - } - - public KeepAlive GetAsyncEnumerator() => this; - - public T Current => m_enumerator.Current; - public ValueTask MoveNextAsync() => m_enumerator.MoveNextAsync(); - } - - /// - public readonly struct ConfiguredDispose - { - private readonly IAsyncEnumerator m_enumerator; - private readonly bool m_continueOnCapturedContext; - - public ConfiguredDispose(IAsyncEnumerator enumerator, bool continueOnCapturedContext) - { - m_enumerator = enumerator; - m_continueOnCapturedContext = continueOnCapturedContext; - } - - public ConfiguredDispose GetAsyncEnumerator() => this; - - public T Current => m_enumerator.Current; - public ConfiguredValueTaskAwaitable MoveNextAsync() => m_enumerator.MoveNextAsync().ConfigureAwait(m_continueOnCapturedContext); - public ConfiguredValueTaskAwaitable DisposeAsync() => m_enumerator.DisposeAsync().ConfigureAwait(m_continueOnCapturedContext); - } - - /// - public readonly struct ConfiguredKeepAlive - { - private readonly IAsyncEnumerator m_enumerator; - private readonly bool m_continueOnCapturedContext; - - public ConfiguredKeepAlive(IAsyncEnumerator enumerator, bool continueOnCapturedContext) - { - m_enumerator = enumerator; - m_continueOnCapturedContext = continueOnCapturedContext; - } - - public ConfiguredKeepAlive GetAsyncEnumerator() => this; - - public T Current => m_enumerator.Current; - public ConfiguredValueTaskAwaitable MoveNextAsync() => m_enumerator.MoveNextAsync().ConfigureAwait(m_continueOnCapturedContext); - } - - /// - /// Enables use of await foreach with . - /// The enumerator is disposed after enumeration completes. - /// - public static Dispose EnumerateAndDispose(this IAsyncEnumerator enumerator) => - new Dispose(enumerator); - - /// - /// Enables use of await foreach with . - /// The enumerator is NOT disposed after enumeration completes. - /// Callers are responsible for managing the enumerator's lifetime. - /// - public static KeepAlive EnumerateAndKeepAlive(this IAsyncEnumerator enumerator) => - new KeepAlive(enumerator); - - /// - /// Enables use of await foreach with . - /// The enumerator is disposed after enumeration completes. - /// - public static ConfiguredDispose ConfigureEnumerateAndDispose(this IAsyncEnumerator enumerator, bool continueOnCapturedContext) => - new ConfiguredDispose(enumerator, continueOnCapturedContext); - - /// - /// Enables use of await foreach with . - /// The enumerator is NOT disposed after enumeration completes. - /// Callers are responsible for managing the enumerator's lifetime. - /// - public static ConfiguredKeepAlive ConfigureEnumerateAndKeepAlive(this IAsyncEnumerator enumerator, bool continueOnCapturedContext) => - new ConfiguredKeepAlive(enumerator, continueOnCapturedContext); - } - - /// - /// Provides extension methods for IAsyncEnumXxx. - /// - [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] - public static class AsyncEnumeratorExtensions - { - sealed class WrappingAsyncEnumerable : IAsyncEnumerable - { - private readonly IEnumerable m_enumerable; - - public WrappingAsyncEnumerable(IEnumerable enumerable) - { - m_enumerable = enumerable; - } - - public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) - { - return new WrappingAsyncEnumerator(m_enumerable.GetEnumerator(), cancellationToken); - } - } - - sealed class WrappingAsyncEnumerator : IAsyncEnumerator, IValueTaskSource - { - private readonly IEnumerator m_enumerator; - private readonly CancellationToken m_cancellationToken; - - public WrappingAsyncEnumerator(IEnumerator enumerator, CancellationToken cancellationToken = default) - { - m_enumerator = enumerator; - m_cancellationToken = cancellationToken; - } - - public T Current => m_enumerator.Current; - - public ValueTask DisposeAsync() - { - m_enumerator.Dispose(); - return new ValueTask(); - } - - public ValueTask MoveNextAsync() - { - return m_cancellationToken.IsCancellationRequested ? - new ValueTask(this, 0) : - new ValueTask(m_enumerator.MoveNext()); - } - - public bool GetResult(short token) - { - m_cancellationToken.ThrowIfCancellationRequested(); - throw new InvalidOperationException(); - } - - public ValueTaskSourceStatus GetStatus(short token) - { - return ValueTaskSourceStatus.Canceled; - } - - public void OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) - { - throw new InvalidOperationException(); - } - } - - /// - /// Wraps a synchronous into an . - /// - public static IAsyncEnumerable ToAsyncEnumerable(this IEnumerable enumerable) => new WrappingAsyncEnumerable(enumerable); - - /// - /// Wraps a synchronous into an . - /// - public static IAsyncEnumerator ToAsyncEnumerator(this IEnumerable enumerable, CancellationToken cancellationToken = default) => - new WrappingAsyncEnumerator(enumerable.GetEnumerator(), cancellationToken); - - /// - /// Wraps a synchronous into an . - /// - public static IAsyncEnumerator ToAsyncEnumerator(this IEnumerator enumerator, CancellationToken cancellationToken = default) => - new WrappingAsyncEnumerator(enumerator, cancellationToken); - } - - /// - /// Provides an empty . - /// - public static class AsyncEnumerator - { - sealed class EmptyAsyncEnumerator : IAsyncEnumerator - { - public T Current => throw new InvalidOperationException(); - public ValueTask MoveNextAsync() => new ValueTask(false); - public ValueTask DisposeAsync() => new ValueTask(); - } - - /// - /// Gets an empty . - /// - public static readonly IAsyncEnumerator Empty = new EmptyAsyncEnumerator(); - } -} \ No newline at end of file From c6d2c10653da8a04f72fd85f5a1485597bc11018 Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Mon, 5 Oct 2020 17:40:36 +1100 Subject: [PATCH 26/42] Create netstandard2.1 targets --- src/NPoco.JsonNet/NPoco.JsonNet.csproj | 4 ++-- src/NPoco.SqlServer/NPoco.SqlServer.csproj | 2 +- src/NPoco/NPoco.csproj | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/NPoco.JsonNet/NPoco.JsonNet.csproj b/src/NPoco.JsonNet/NPoco.JsonNet.csproj index b8b4edf7..d10c21d9 100644 --- a/src/NPoco.JsonNet/NPoco.JsonNet.csproj +++ b/src/NPoco.JsonNet/NPoco.JsonNet.csproj @@ -3,7 +3,7 @@ Provides an implementation to use Json.NET as the serializer for serialized columns 5.0.0 - netstandard2.0 + netstandard2.0;netstandard2.1 NPoco.JsonNet NPoco.JsonNet orm;sql;micro-orm;database;mvc @@ -22,7 +22,7 @@ - + diff --git a/src/NPoco.SqlServer/NPoco.SqlServer.csproj b/src/NPoco.SqlServer/NPoco.SqlServer.csproj index ca9ae89d..23347a1c 100644 --- a/src/NPoco.SqlServer/NPoco.SqlServer.csproj +++ b/src/NPoco.SqlServer/NPoco.SqlServer.csproj @@ -3,7 +3,7 @@ An extremely easy to use Micro-ORM supporting Sql Server. 5.0.0 - netstandard2.0 + netstandard2.0;netstandard2.1 NPoco.SqlServer NPoco.SqlServer Adam Schröder diff --git a/src/NPoco/NPoco.csproj b/src/NPoco/NPoco.csproj index 43e42a35..8d197b81 100644 --- a/src/NPoco/NPoco.csproj +++ b/src/NPoco/NPoco.csproj @@ -2,7 +2,7 @@ An extremely easy to use Micro-ORM supporting Sql Server, MySQL, PostgreSQL, Oracle, Sqlite, SqlCE. - netstandard2.0 + netstandard2.0;netstandard2.1 NPoco NPoco 5.0.0 @@ -21,7 +21,7 @@ - + From 7f543779167c9b794b57b717b539253a1a059eae Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Mon, 5 Oct 2020 18:16:58 +1100 Subject: [PATCH 27/42] Use .AsTask instead of a possible double await --- src/NPoco/AsyncDatabase.cs | 48 +++++++++++++-------------- src/NPoco/Linq/SimpleQueryProvider.cs | 40 +++++++++++----------- 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/src/NPoco/AsyncDatabase.cs b/src/NPoco/AsyncDatabase.cs index 2c8d7b3f..8ee9b4c9 100644 --- a/src/NPoco/AsyncDatabase.cs +++ b/src/NPoco/AsyncDatabase.cs @@ -366,14 +366,14 @@ public Task> FetchAsync() return FetchAsync(""); } - public async Task> FetchAsync(string sql, params object[] args) + public Task> FetchAsync(string sql, params object[] args) { - return await QueryAsync(sql, args).ToListAsync().ConfigureAwait(false); + return QueryAsync(sql, args).ToListAsync().AsTask(); } - public async Task> FetchAsync(Sql sql) + public Task> FetchAsync(Sql sql) { - return await QueryAsync(sql).ToListAsync().ConfigureAwait(false); + return QueryAsync(sql).ToListAsync().AsTask(); } public Task FetchMultipleAsync(Func, List, TRet> cb, string sql, params object[] args) { return FetchMultipleImp(new[] { typeof(T1), typeof(T2) }, cb, new Sql(sql, args), false); } @@ -431,56 +431,56 @@ internal async IAsyncEnumerable QueryAsync(T instance, Expression SingleAsync(string sql, params object[] args) + public Task SingleAsync(string sql, params object[] args) { - return await QueryAsync(sql, args).SingleAsync().ConfigureAwait(false); + return QueryAsync(sql, args).SingleAsync().AsTask(); } - public async Task SingleAsync(Sql sql) + public Task SingleAsync(Sql sql) { - return await QueryAsync(sql).SingleAsync().ConfigureAwait(false); + return QueryAsync(sql).SingleAsync().AsTask(); } - public async Task SingleOrDefaultAsync(string sql, params object[] args) + public Task SingleOrDefaultAsync(string sql, params object[] args) { - return await QueryAsync(sql, args).SingleOrDefaultAsync().ConfigureAwait(false); + return QueryAsync(sql, args).SingleOrDefaultAsync().AsTask(); } - public async Task SingleOrDefaultAsync(Sql sql) + public Task SingleOrDefaultAsync(Sql sql) { - return await QueryAsync(sql).SingleOrDefaultAsync().ConfigureAwait(false); + return QueryAsync(sql).SingleOrDefaultAsync().AsTask(); } - public async Task SingleByIdAsync(object primaryKey) + public Task SingleByIdAsync(object primaryKey) { var sql = GenerateSingleByIdSql(primaryKey); - return await QueryAsync(sql).SingleAsync().ConfigureAwait(false); + return QueryAsync(sql).SingleAsync().AsTask(); } - public async Task SingleOrDefaultByIdAsync(object primaryKey) + public Task SingleOrDefaultByIdAsync(object primaryKey) { var sql = GenerateSingleByIdSql(primaryKey); - return await QueryAsync(sql).SingleOrDefaultAsync().ConfigureAwait(false); + return QueryAsync(sql).SingleOrDefaultAsync().AsTask(); } - public async Task FirstAsync(string sql, params object[] args) + public Task FirstAsync(string sql, params object[] args) { - return await QueryAsync(sql).FirstAsync().ConfigureAwait(false); + return QueryAsync(sql).FirstAsync().AsTask(); } - public async Task FirstAsync(Sql sql) + public Task FirstAsync(Sql sql) { - return await QueryAsync(sql).FirstAsync().ConfigureAwait(false); + return QueryAsync(sql).FirstAsync().AsTask(); } - public async Task FirstOrDefaultAsync(string sql, params object[] args) + public Task FirstOrDefaultAsync(string sql, params object[] args) { - return await QueryAsync(sql).FirstOrDefaultAsync().ConfigureAwait(false); + return QueryAsync(sql).FirstOrDefaultAsync().AsTask(); } - public async Task FirstOrDefaultAsync(Sql sql) + public Task FirstOrDefaultAsync(Sql sql) { - return await QueryAsync(sql).FirstOrDefaultAsync().ConfigureAwait(false); + return QueryAsync(sql).FirstOrDefaultAsync().AsTask(); } public Task ExecuteAsync(string sql, params object[] args) diff --git a/src/NPoco/Linq/SimpleQueryProvider.cs b/src/NPoco/Linq/SimpleQueryProvider.cs index 59104384..48246cfb 100644 --- a/src/NPoco/Linq/SimpleQueryProvider.cs +++ b/src/NPoco/Linq/SimpleQueryProvider.cs @@ -214,14 +214,14 @@ private IAsyncQueryProviderWithIncludes QueryProviderWithIncludes(Expression return this; } - public async Task> ToList() + public Task> ToList() { - return await ToEnumerable().ToListAsync().ConfigureAwait(false); + return ToEnumerable().ToListAsync().AsTask(); } - public async Task ToArray() + public Task ToArray() { - return await ToEnumerable().ToArrayAsync().ConfigureAwait(false); + return ToEnumerable().ToArrayAsync().AsTask(); } public IAsyncEnumerable ToEnumerable() @@ -239,10 +239,10 @@ public Task FirstOrDefault() return FirstOrDefault(null); } - public async Task FirstOrDefault(Expression> whereExpression) + public Task FirstOrDefault(Expression> whereExpression) { AddWhere(whereExpression); - return await ToEnumerable().FirstOrDefaultAsync().ConfigureAwait(false); + return ToEnumerable().FirstOrDefaultAsync().AsTask(); } public Task First() @@ -250,10 +250,10 @@ public Task First() return First(null); } - public async Task First(Expression> whereExpression) + public Task First(Expression> whereExpression) { AddWhere(null); - return await ToEnumerable().FirstAsync().ConfigureAwait(false); + return ToEnumerable().FirstAsync().AsTask(); } public Task SingleOrDefault() @@ -261,10 +261,10 @@ public Task SingleOrDefault() return SingleOrDefault(null); } - public async Task SingleOrDefault(Expression> whereExpression) + public Task SingleOrDefault(Expression> whereExpression) { AddWhere(whereExpression); - return await ToEnumerable().SingleOrDefaultAsync().ConfigureAwait(false); + return ToEnumerable().SingleOrDefaultAsync().AsTask(); } public Task Single() @@ -272,10 +272,10 @@ public Task Single() return Single(null); } - public async Task Single(Expression> whereExpression) + public Task Single(Expression> whereExpression) { AddWhere(whereExpression); - return await ToEnumerable().SingleAsync().ConfigureAwait(false); + return ToEnumerable().SingleAsync().AsTask(); } public Task Count() @@ -283,11 +283,11 @@ public Task Count() return Count(null); } - public async Task Count(Expression> whereExpression) + public Task Count(Expression> whereExpression) { AddWhere(whereExpression); var sql = _buildComplexSql.BuildJoin(_database, _sqlExpression, _joinSqlExpressions.Values.ToList(), null, true, false); - return await _database.ExecuteScalarAsync(sql).ConfigureAwait(false); + return _database.ExecuteScalarAsync(sql); } public Task Any() @@ -325,21 +325,21 @@ public async Task> ToPage(int page, int pageSize) return result; } - public async Task> ProjectTo(Expression> projectionExpression) + public Task> ProjectTo(Expression> projectionExpression) { var sql = _buildComplexSql.GetSqlForProjection(projectionExpression, false); - return await ExecuteQueryAsync(sql).Select(projectionExpression.Compile()).ToListAsync().ConfigureAwait(false); + return ExecuteQueryAsync(sql).Select(projectionExpression.Compile()).ToListAsync().AsTask(); } - public async Task> Distinct() + public Task> Distinct() { - return await ExecuteQueryAsync(new Sql(_sqlExpression.Context.ToSelectStatement(true, true), _sqlExpression.Context.Params)).ToListAsync().ConfigureAwait(false); + return ExecuteQueryAsync(new Sql(_sqlExpression.Context.ToSelectStatement(true, true), _sqlExpression.Context.Params)).ToListAsync().AsTask(); } - public async Task> Distinct(Expression> projectionExpression) + public Task> Distinct(Expression> projectionExpression) { var sql = _buildComplexSql.GetSqlForProjection(projectionExpression, true); - return await ExecuteQueryAsync(sql).Select(projectionExpression.Compile()).ToListAsync().ConfigureAwait(false); + return ExecuteQueryAsync(sql).Select(projectionExpression.Compile()).ToListAsync().AsTask(); } public IAsyncQueryProvider Where(Expression> whereExpression) From 5f0ba271cfd887c50b0fc4eba7ea9cf140f7ad9c Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Mon, 5 Oct 2020 18:25:04 +1100 Subject: [PATCH 28/42] Removed left over target framework --- src/NPoco.SqlServer/NPoco.SqlServer.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/NPoco.SqlServer/NPoco.SqlServer.csproj b/src/NPoco.SqlServer/NPoco.SqlServer.csproj index 23347a1c..ab6e04dd 100644 --- a/src/NPoco.SqlServer/NPoco.SqlServer.csproj +++ b/src/NPoco.SqlServer/NPoco.SqlServer.csproj @@ -9,7 +9,6 @@ Adam Schröder orm;sql;micro-orm;database;mvc https://github.com/schotime/NPoco - netstandard2.0 8.0 From 85e6b288ed0a8d03efab52252553180b29382457 Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Tue, 6 Oct 2020 09:13:07 +1100 Subject: [PATCH 29/42] Add missing where expression to First method #598 --- src/NPoco/Linq/SimpleQueryProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NPoco/Linq/SimpleQueryProvider.cs b/src/NPoco/Linq/SimpleQueryProvider.cs index 48246cfb..e2bf99fa 100644 --- a/src/NPoco/Linq/SimpleQueryProvider.cs +++ b/src/NPoco/Linq/SimpleQueryProvider.cs @@ -252,7 +252,7 @@ public Task First() public Task First(Expression> whereExpression) { - AddWhere(null); + AddWhere(whereExpression); return ToEnumerable().FirstAsync().AsTask(); } From 229211f17d0bae89a00ed0c917fa4c4e7a820a64 Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Wed, 7 Oct 2020 13:21:15 +1100 Subject: [PATCH 30/42] Add ability to have multi parameter less constructors and default values will be provided to initialise #602 --- src/NPoco/FastCreate.cs | 96 +++++++++++++++---- src/NPoco/PocoData.cs | 7 +- src/NPoco/PocoDataBuilder.cs | 10 +- src/NPoco/PocoDataFactory.cs | 6 +- test/NPoco.Tests/Common/User.cs | 8 -- .../QueryTests/ConstructorTests.cs | 65 +++++++++++++ .../SingleAndFirstQueryFluentTest.cs | 8 -- test/NPoco.Tests/NewMapper/NewMapperTests.cs | 1 + 8 files changed, 156 insertions(+), 45 deletions(-) create mode 100644 test/NPoco.Tests/DecoratedTests/QueryTests/ConstructorTests.cs diff --git a/src/NPoco/FastCreate.cs b/src/NPoco/FastCreate.cs index 1ae4d9f7..d94816fd 100644 --- a/src/NPoco/FastCreate.cs +++ b/src/NPoco/FastCreate.cs @@ -3,7 +3,6 @@ using System.Data; using System.Data.Common; using System.Linq; -using System.Linq.Expressions; using System.Reflection.Emit; using System.Reflection; @@ -13,21 +12,22 @@ public class FastCreate { private readonly Type _type; private readonly MapperCollection _mapperCollection; + private ConstructorInfo _constructorInfo; + private Func _createDelegate; public FastCreate(Type type, MapperCollection mapperCollection) { _type = type; _mapperCollection = mapperCollection; - CreateDelegate = GetCreateDelegate(); } - public Func CreateDelegate { get; set; } - public object Create(DbDataReader dataReader) { try { - return CreateDelegate(dataReader); + _constructorInfo ??= GetConstructorInfo(_type); + _createDelegate ??= GetCreateDelegate(); + return _createDelegate(dataReader); } catch (Exception exception) { @@ -40,28 +40,88 @@ private Func GetCreateDelegate() if (_mapperCollection.HasFactory(_type)) return dataReader => _mapperCollection.GetFactory(_type)(dataReader); - //if (PocoDataBuilder.IsDictionaryType(_type)) - // return _ => Activator.CreateInstance(_type, StringComparer.OrdinalIgnoreCase); - - var constructorInfo = _type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).SingleOrDefault(x => x.GetParameters().Length == 0); - if (constructorInfo == null) + if (_constructorInfo == null) return _ => null; - // var poco=new T() - var constructor = new DynamicMethod(Guid.NewGuid().ToString(), _type, new[] { typeof(DbDataReader) }, true); - var il = constructor.GetILGenerator(); - il.Emit(OpCodes.Newobj, constructorInfo); - il.Emit(OpCodes.Ret); - try { - var del = constructor.CreateDelegate(Expression.GetFuncType(typeof(DbDataReader), typeof(object))); - return del as Func; + var create = CreateObjectFactoryMethodWithCtorParams(_constructorInfo); + var parameters = _constructorInfo.GetParameters().Select(x => MappingHelper.GetDefault(x.ParameterType)).ToArray(); + return x => create(parameters); } catch (Exception exception) { throw new Exception("Error trying to create type " + _type, exception); } } + + private static Func CreateObjectFactoryMethodWithCtorParams(ConstructorInfo ctor) + { + var dm = new DynamicMethod(string.Format("_ObjectFactory_{0}", Guid.NewGuid()), typeof(object), new Type[] { typeof(object[]) }, true); + var il = dm.GetILGenerator(); + + var parameters = ctor.GetParameters(); + for (int i = 0; i < parameters.Length; i++) + { + il.Emit(OpCodes.Ldarg_0); // [args] + EmitInt32(il, i); // [args][index] + il.Emit(OpCodes.Ldelem_Ref); // [item-in-args-at-index] + + var paramType = parameters[i].ParameterType; + if (paramType != typeof(object)) + { + il.Emit(OpCodes.Unbox_Any, paramType); // same as a cast if ref-type + } + } + il.Emit(OpCodes.Newobj, ctor); // [new-object] + il.Emit(OpCodes.Ret); // nothing + return (Func)dm.CreateDelegate(typeof(Func)); + } + + private static void EmitInt32(ILGenerator il, int value) + { + switch (value) + { + case -1: il.Emit(OpCodes.Ldc_I4_M1); break; + case 0: il.Emit(OpCodes.Ldc_I4_0); break; + case 1: il.Emit(OpCodes.Ldc_I4_1); break; + case 2: il.Emit(OpCodes.Ldc_I4_2); break; + case 3: il.Emit(OpCodes.Ldc_I4_3); break; + case 4: il.Emit(OpCodes.Ldc_I4_4); break; + case 5: il.Emit(OpCodes.Ldc_I4_5); break; + case 6: il.Emit(OpCodes.Ldc_I4_6); break; + case 7: il.Emit(OpCodes.Ldc_I4_7); break; + case 8: il.Emit(OpCodes.Ldc_I4_8); break; + default: + if (value >= -128 && value <= 127) + { + il.Emit(OpCodes.Ldc_I4_S, (sbyte)value); + } + else + { + il.Emit(OpCodes.Ldc_I4, value); + } + break; + } + } + + private static ConstructorInfo GetConstructorInfo(Type type) + { + var constructorParameters = type + .GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .Select(x => new + { + Constructor = x, + Parameters = x.GetParameters() + }) + .OrderByDescending(x => x.Parameters.Length) + .ToList(); + + var getParameterLess = constructorParameters.SingleOrDefault(x => x.Parameters.Length == 0); + + return getParameterLess != null + ? getParameterLess.Constructor + : constructorParameters.FirstOrDefault()?.Constructor; + } } } \ No newline at end of file diff --git a/src/NPoco/PocoData.cs b/src/NPoco/PocoData.cs index 2d26c2ab..2e54ff5d 100644 --- a/src/NPoco/PocoData.cs +++ b/src/NPoco/PocoData.cs @@ -26,8 +26,9 @@ public PocoData() { } - public PocoData(Type type, MapperCollection mapper) : this() + public PocoData(Type type, MapperCollection mapper, FastCreate creator) : this() { + CreateDelegate = creator; Type = type; Mapper = mapper; } @@ -73,15 +74,11 @@ private Func PrimaryKeyValues } } - public object CreateObject(DbDataReader dataReader) { - if (CreateDelegate == null) - CreateDelegate = new FastCreate(Type, Mapper); return CreateDelegate.Create(dataReader); } private FastCreate CreateDelegate { get; set; } - } } diff --git a/src/NPoco/PocoDataBuilder.cs b/src/NPoco/PocoDataBuilder.cs index 7e3ef233..01d85055 100644 --- a/src/NPoco/PocoDataBuilder.cs +++ b/src/NPoco/PocoDataBuilder.cs @@ -17,6 +17,7 @@ public interface InitializedPocoDataBuilder public class PocoDataBuilder : InitializedPocoDataBuilder { private readonly Cache _aliasToType = Cache.CreateStaticCache(); + private FastCreate _generator; protected Type Type { get; set; } private MapperCollection Mapper { get; set; } @@ -38,6 +39,9 @@ public InitializedPocoDataBuilder Init() var memberInfos = new List(); var columnInfos = GetColumnInfos(Type); + // init the generator + _generator = new FastCreate(Type, Mapper); + // Get table info plan _tableInfoPlan = GetTableInfo(Type, columnInfos, memberInfos); @@ -72,7 +76,7 @@ TableInfo InitializedPocoDataBuilder.BuildTableInfo() PocoData InitializedPocoDataBuilder.Build() { - var pocoData = new PocoData(Type, Mapper); + var pocoData = new PocoData(Type, Mapper, _generator); pocoData.TableInfo = _tableInfoPlan(); @@ -138,7 +142,7 @@ public IEnumerable GetPocoMembers(ColumnInfo[] columnInfos, List : memberInfoType.GetTypeWithGenericTypeDefinitionOf(typeof(IList<>)).GetGenericArguments().First(); } - var childrenPlans = new PocoMemberPlan[0]; + var childrenPlans = new List(); TableInfoPlan childTableInfoPlan = null; var members = new List(capturedMembers) { columnInfo.MemberInfo }; @@ -158,7 +162,7 @@ public IEnumerable GetPocoMembers(ColumnInfo[] columnInfos, List var newPrefix = JoinStrings(capturedPrefix, columnInfo.ReferenceType != ReferenceType.None ? "" : (columnInfo.ComplexPrefix ?? columnInfo.MemberInfo.Name)); - childrenPlans = GetPocoMembers(childColumnInfos, members, newPrefix).ToArray(); + childrenPlans.AddRange(GetPocoMembers(childColumnInfos, members, newPrefix)); } MemberInfo capturedMemberInfo = columnInfo.MemberInfo; diff --git a/src/NPoco/PocoDataFactory.cs b/src/NPoco/PocoDataFactory.cs index 31e917b5..64e39d1d 100644 --- a/src/NPoco/PocoDataFactory.cs +++ b/src/NPoco/PocoDataFactory.cs @@ -65,14 +65,14 @@ public PocoDataFactory(MapperCollection mapper) public PocoData ForType(Type type) { Guard(type); - var pocoDataBuilder = _pocoDatas.Get(type, () => BaseClassFalbackPocoDataBuilder(type)); + var pocoDataBuilder = _pocoDatas.Get(type, () => BaseClassFallbackPocoDataBuilder(type)); return pocoDataBuilder.Build(); } public TableInfo TableInfoForType(Type type) { Guard(type); - var pocoDataBuilder = _pocoDatas.Get(type, () => BaseClassFalbackPocoDataBuilder(type)); + var pocoDataBuilder = _pocoDatas.Get(type, () => BaseClassFallbackPocoDataBuilder(type)); return pocoDataBuilder.BuildTableInfo(); } @@ -81,7 +81,7 @@ public PocoData ForObject(object o, string primaryKeyName, bool autoIncrement) return ForObjectStatic(o, primaryKeyName, autoIncrement, ForType); } - private InitializedPocoDataBuilder BaseClassFalbackPocoDataBuilder(Type type) + private InitializedPocoDataBuilder BaseClassFallbackPocoDataBuilder(Type type) { var builder = new PocoDataBuilder(type, _mapper).Init(); var persistedType = builder.BuildTableInfo().PersistedType; diff --git a/test/NPoco.Tests/Common/User.cs b/test/NPoco.Tests/Common/User.cs index a474732d..eb719b73 100644 --- a/test/NPoco.Tests/Common/User.cs +++ b/test/NPoco.Tests/Common/User.cs @@ -77,14 +77,6 @@ public class UserWithExtraInfo : User public new ExtraUserInfo ExtraUserInfo { get; set; } } - public class UserWithNoParamConstructor : User - { - public UserWithNoParamConstructor(int userId) - { - UserId = userId; - } - } - public class UserWithPrivateParamLessConstructor : User { private UserWithPrivateParamLessConstructor() diff --git a/test/NPoco.Tests/DecoratedTests/QueryTests/ConstructorTests.cs b/test/NPoco.Tests/DecoratedTests/QueryTests/ConstructorTests.cs new file mode 100644 index 00000000..d7b83ba6 --- /dev/null +++ b/test/NPoco.Tests/DecoratedTests/QueryTests/ConstructorTests.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Text; +using NPoco.Tests.Common; +using NUnit.Framework; + +namespace NPoco.Tests.DecoratedTests.QueryTests +{ + [TestFixture] + public class ConstructorTests : BaseDBDecoratedTest + { + [Test] + public void TestPublicParameter() + { + var result = Database.Single("select 'Name' Name, 23 Age"); + Assert.True(result.Multiple); + } + + [Test] + public void TestParameterLess() + { + var result = Database.Single("select 'Name' Name, 23 Age"); + Assert.True(result.Single); + Assert.False(result.Multiple); + } + + public class MultipleParameterConstructor + { + public string Name { get; } + public int Age { get; } + + public bool Multiple { get; } + + public MultipleParameterConstructor(string Name, int Age) + { + this.Name = Name; + this.Age = Age; + Multiple = true; + } + } + + public class SingleParameterConstructor + { + public string Name { get; } + public int Age { get; } + + public bool Multiple { get; } + public bool Single { get; } + + private SingleParameterConstructor() + { + Single = true; + } + + public SingleParameterConstructor(string Name, int Age) + { + this.Name = Name; + this.Age = Age; + Multiple = true; + } + } + } + + +} diff --git a/test/NPoco.Tests/FluentTests/QueryTests/SingleAndFirstQueryFluentTest.cs b/test/NPoco.Tests/FluentTests/QueryTests/SingleAndFirstQueryFluentTest.cs index 46a0e827..58c6c64b 100644 --- a/test/NPoco.Tests/FluentTests/QueryTests/SingleAndFirstQueryFluentTest.cs +++ b/test/NPoco.Tests/FluentTests/QueryTests/SingleAndFirstQueryFluentTest.cs @@ -151,13 +151,5 @@ public void SingleWithAdHocObjectUsingUnderscores() var data = Database.Single("select name \"Na_me\" from users where userid = 1"); Assert.AreEqual("Name1", data.Name); } - - [Test] - public void NoParameterLessConstructor() - { - Assert.Throws(() => - Database.Single("select * from users where userid = 1"), "Poco '{0}' has not parameterless constructor", - typeof(UserWithNoParamConstructor).FullName); - } } } \ No newline at end of file diff --git a/test/NPoco.Tests/NewMapper/NewMapperTests.cs b/test/NPoco.Tests/NewMapper/NewMapperTests.cs index d6353db8..b5df2767 100644 --- a/test/NPoco.Tests/NewMapper/NewMapperTests.cs +++ b/test/NPoco.Tests/NewMapper/NewMapperTests.cs @@ -670,6 +670,7 @@ public void Test31() Assert.Throws(() => { var fastCreate = new FastCreate(typeof(ContentBase), new MapperCollection()); + fastCreate.Create(new FakeReader()); }); } From ad5f3413614b1d072cf1e272fd7cf287e7738fa9 Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Wed, 7 Oct 2020 14:10:03 +1100 Subject: [PATCH 31/42] Add ConstructAttribute for being specific about the constructor used --- src/NPoco/ConstructAttribute.cs | 10 ++++++++++ src/NPoco/FastCreate.cs | 8 +++++++- .../DecoratedTests/QueryTests/ConstructorTests.cs | 1 + 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 src/NPoco/ConstructAttribute.cs diff --git a/src/NPoco/ConstructAttribute.cs b/src/NPoco/ConstructAttribute.cs new file mode 100644 index 00000000..9a614502 --- /dev/null +++ b/src/NPoco/ConstructAttribute.cs @@ -0,0 +1,10 @@ +using System; + +namespace NPoco +{ + [AttributeUsage(AttributeTargets.Constructor)] + public class ConstructAttribute : Attribute + { + public ConstructAttribute() { } + } +} \ No newline at end of file diff --git a/src/NPoco/FastCreate.cs b/src/NPoco/FastCreate.cs index d94816fd..f7b80d37 100644 --- a/src/NPoco/FastCreate.cs +++ b/src/NPoco/FastCreate.cs @@ -116,7 +116,13 @@ private static ConstructorInfo GetConstructorInfo(Type type) }) .OrderByDescending(x => x.Parameters.Length) .ToList(); - + + var attributeConstructor = constructorParameters.FirstOrDefault(x => x.Constructor.GetCustomAttribute(typeof(ConstructAttribute)) != null); + if (attributeConstructor != null) + { + return attributeConstructor.Constructor; + } + var getParameterLess = constructorParameters.SingleOrDefault(x => x.Parameters.Length == 0); return getParameterLess != null diff --git a/test/NPoco.Tests/DecoratedTests/QueryTests/ConstructorTests.cs b/test/NPoco.Tests/DecoratedTests/QueryTests/ConstructorTests.cs index d7b83ba6..2226675b 100644 --- a/test/NPoco.Tests/DecoratedTests/QueryTests/ConstructorTests.cs +++ b/test/NPoco.Tests/DecoratedTests/QueryTests/ConstructorTests.cs @@ -47,6 +47,7 @@ public class SingleParameterConstructor public bool Multiple { get; } public bool Single { get; } + [Construct] private SingleParameterConstructor() { Single = true; From 2738997177db9266554437e2d6812b8c5d51218b Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Mon, 12 Oct 2020 22:22:26 +1100 Subject: [PATCH 32/42] Add more constructor tests --- .../QueryTests/ConstructorTests.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/NPoco.Tests/DecoratedTests/QueryTests/ConstructorTests.cs b/test/NPoco.Tests/DecoratedTests/QueryTests/ConstructorTests.cs index 2226675b..1dd85185 100644 --- a/test/NPoco.Tests/DecoratedTests/QueryTests/ConstructorTests.cs +++ b/test/NPoco.Tests/DecoratedTests/QueryTests/ConstructorTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Net.NetworkInformation; using System.Text; using NPoco.Tests.Common; using NUnit.Framework; @@ -24,6 +25,23 @@ public void TestParameterLess() Assert.False(result.Multiple); } + [Test] + public void TestParameterLess2() + { + var result = Database.Single($"select '{Guid.NewGuid()}' Id"); + Assert.AreNotEqual(Guid.Empty, result.Id); + } + + public class GuidConstructor + { + public Guid Id { get; } + + public GuidConstructor(Guid id) + { + Id = id; + } + } + public class MultipleParameterConstructor { public string Name { get; } From fd95d7056bc02bffd27277545cc3ed4937570b72 Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Mon, 12 Oct 2020 22:23:55 +1100 Subject: [PATCH 33/42] Tidy up error message when creating an object --- src/NPoco/RowMappers/PropertyMapper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NPoco/RowMappers/PropertyMapper.cs b/src/NPoco/RowMappers/PropertyMapper.cs index 07e6e285..3f34d4ca 100644 --- a/src/NPoco/RowMappers/PropertyMapper.cs +++ b/src/NPoco/RowMappers/PropertyMapper.cs @@ -36,7 +36,7 @@ public override object Map(DbDataReader dataReader, RowMapperContext context) { context.Instance = context.PocoData.CreateObject(dataReader); if (context.Instance == null) - throw new Exception(string.Format("Cannot create POCO '{0}'. It may have no parameterless constructor or be an interface or abstract class without a Mapper factory.", context.Type.FullName)); + throw new Exception(string.Format("Cannot create POCO '{0}'. It may be an interface or abstract class without a Mapper factory.", context.Type.FullName)); } else { @@ -75,7 +75,7 @@ private IEnumerable BuildMapPlans(GroupResult groupedName, DbD { // find pocomember by property name var pocoMember = pocoMembers.FirstOrDefault(x => IsEqual(groupedName.Item, x.Name) - || IsEqual(groupedName.Item, x.PocoColumn?.ColumnAlias)); + || string.Equals(groupedName.Item, x.PocoColumn?.ColumnAlias, StringComparison.OrdinalIgnoreCase)); if (pocoMember == null) { From d79c076fc103567d1271d37342b8d20f3c18418f Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Mon, 12 Oct 2020 22:26:38 +1100 Subject: [PATCH 34/42] Remove extra try catch --- src/NPoco/FastCreate.cs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/NPoco/FastCreate.cs b/src/NPoco/FastCreate.cs index f7b80d37..a65cf015 100644 --- a/src/NPoco/FastCreate.cs +++ b/src/NPoco/FastCreate.cs @@ -43,16 +43,11 @@ private Func GetCreateDelegate() if (_constructorInfo == null) return _ => null; - try - { - var create = CreateObjectFactoryMethodWithCtorParams(_constructorInfo); - var parameters = _constructorInfo.GetParameters().Select(x => MappingHelper.GetDefault(x.ParameterType)).ToArray(); - return x => create(parameters); - } - catch (Exception exception) - { - throw new Exception("Error trying to create type " + _type, exception); - } + var create = CreateObjectFactoryMethodWithCtorParams(_constructorInfo); + var parameters = _constructorInfo.GetParameters() + .Select(x => MappingHelper.GetDefault(x.ParameterType)) + .ToArray(); + return x => create(parameters); } private static Func CreateObjectFactoryMethodWithCtorParams(ConstructorInfo ctor) From 25eafd9af0aaf53f12001d4ae78377841e001a7d Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Thu, 15 Oct 2020 19:07:01 +1100 Subject: [PATCH 35/42] Add some nullable reference type annotations --- .../SqlServer2012DatabaseType.cs | 4 +- .../DatabaseTypes/SqlServerDatabaseType.cs | 2 +- src/NPoco.SqlServer/NPoco.SqlServer.csproj | 1 + src/NPoco.SqlServer/SqlBulkCopyHelper.cs | 9 +- src/NPoco.SqlServer/SqlServerDatabase.cs | 6 +- src/NPoco/Database.cs | 223 +++++++++--------- src/NPoco/IDatabase.cs | 15 +- 7 files changed, 129 insertions(+), 131 deletions(-) diff --git a/src/NPoco.SqlServer/DatabaseTypes/SqlServer2012DatabaseType.cs b/src/NPoco.SqlServer/DatabaseTypes/SqlServer2012DatabaseType.cs index 7a866be7..3839127b 100644 --- a/src/NPoco.SqlServer/DatabaseTypes/SqlServer2012DatabaseType.cs +++ b/src/NPoco.SqlServer/DatabaseTypes/SqlServer2012DatabaseType.cs @@ -21,10 +21,10 @@ public override string BuildPageQuery(long skip, long take, PagingHelper.SQLPart return sqlPage; } - public override string GetAutoIncrementExpression(TableInfo ti) + public override string? GetAutoIncrementExpression(TableInfo ti) { if (!string.IsNullOrEmpty(ti.SequenceName)) - return string.Format("NEXT VALUE FOR {0}", ti.SequenceName); + return $"NEXT VALUE FOR {ti.SequenceName}"; return null; } diff --git a/src/NPoco.SqlServer/DatabaseTypes/SqlServerDatabaseType.cs b/src/NPoco.SqlServer/DatabaseTypes/SqlServerDatabaseType.cs index 80f179c2..688695c0 100644 --- a/src/NPoco.SqlServer/DatabaseTypes/SqlServerDatabaseType.cs +++ b/src/NPoco.SqlServer/DatabaseTypes/SqlServerDatabaseType.cs @@ -74,7 +74,7 @@ public override IsolationLevel GetDefaultTransactionIsolationLevel() return base.LookupDbType(type, name); } - public override void InsertBulk(IDatabase db, IEnumerable pocos, InsertBulkOptions options) + public override void InsertBulk(IDatabase db, IEnumerable pocos, InsertBulkOptions? options) { SqlBulkCopyHelper.BulkInsert(db, pocos, options); } diff --git a/src/NPoco.SqlServer/NPoco.SqlServer.csproj b/src/NPoco.SqlServer/NPoco.SqlServer.csproj index ab6e04dd..b194eef5 100644 --- a/src/NPoco.SqlServer/NPoco.SqlServer.csproj +++ b/src/NPoco.SqlServer/NPoco.SqlServer.csproj @@ -10,6 +10,7 @@ orm;sql;micro-orm;database;mvc https://github.com/schotime/NPoco 8.0 + enable diff --git a/src/NPoco.SqlServer/SqlBulkCopyHelper.cs b/src/NPoco.SqlServer/SqlBulkCopyHelper.cs index 24ae7d5e..3f55fa1a 100644 --- a/src/NPoco.SqlServer/SqlBulkCopyHelper.cs +++ b/src/NPoco.SqlServer/SqlBulkCopyHelper.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Data; using System.Data.Common; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.Data.SqlClient; @@ -13,12 +14,12 @@ public class SqlBulkCopyHelper public static Func SqlConnectionResolver = dbConn => (SqlConnection)dbConn; public static Func SqlTransactionResolver = dbTran => (SqlTransaction)dbTran; - public static void BulkInsert(IDatabase db, IEnumerable list, InsertBulkOptions insertBulkOptions) + public static void BulkInsert(IDatabase db, IEnumerable list, InsertBulkOptions? insertBulkOptions) { BulkInsert(db, list, SqlBulkCopyOptions.Default, insertBulkOptions); } - public static void BulkInsert(IDatabase db, IEnumerable list, SqlBulkCopyOptions sqlBulkCopyOptions, InsertBulkOptions insertBulkOptions) + public static void BulkInsert(IDatabase db, IEnumerable list, SqlBulkCopyOptions sqlBulkCopyOptions, InsertBulkOptions? insertBulkOptions) { using (var bulkCopy = new SqlBulkCopy(SqlConnectionResolver(db.Connection), sqlBulkCopyOptions, SqlTransactionResolver(db.Transaction))) { @@ -42,7 +43,7 @@ public static async Task BulkInsertAsync(IDatabase db, IEnumerable list, S } - private static DataTable BuildBulkInsertDataTable(IDatabase db, IEnumerable list, SqlBulkCopy bulkCopy, SqlBulkCopyOptions sqlBulkCopyOptions, InsertBulkOptions insertBulkOptions) + private static DataTable BuildBulkInsertDataTable(IDatabase db, IEnumerable list, SqlBulkCopy bulkCopy, SqlBulkCopyOptions sqlBulkCopyOptions, InsertBulkOptions? insertBulkOptions) { var pocoData = db.PocoDataFactory.ForType(typeof (T)); @@ -78,7 +79,7 @@ private static DataTable BuildBulkInsertDataTable(IDatabase db, IEnumerable x.GetToDbConverter(cols[i].Value.ColumnType, cols[i].Value.MemberInfoData.MemberInfo), value); diff --git a/src/NPoco.SqlServer/SqlServerDatabase.cs b/src/NPoco.SqlServer/SqlServerDatabase.cs index 8e7ee9df..dce70151 100644 --- a/src/NPoco.SqlServer/SqlServerDatabase.cs +++ b/src/NPoco.SqlServer/SqlServerDatabase.cs @@ -8,14 +8,14 @@ namespace NPoco.SqlServer { public class SqlServerDatabase : Database { - private readonly IPollyPolicy _pollyPolicy; + private readonly IPollyPolicy? _pollyPolicy; - public SqlServerDatabase(string connectionString, IPollyPolicy pollyPolicy = null) + public SqlServerDatabase(string connectionString, IPollyPolicy? pollyPolicy = null) : this(connectionString, Singleton.Instance, pollyPolicy) { } - public SqlServerDatabase(string connectionString, SqlServerDatabaseType databaseType, IPollyPolicy pollyPolicy) + public SqlServerDatabase(string connectionString, SqlServerDatabaseType databaseType, IPollyPolicy? pollyPolicy) : base(connectionString, databaseType, SqlClientFactory.Instance) { _pollyPolicy = pollyPolicy; diff --git a/src/NPoco/Database.cs b/src/NPoco/Database.cs index b32b389b..419c6d5e 100644 --- a/src/NPoco/Database.cs +++ b/src/NPoco/Database.cs @@ -6,11 +6,14 @@ * Originally created by Brad Robinson (@toptensoftware) */ +#nullable enable using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Data.Common; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -30,15 +33,15 @@ public Database(DbConnection connection) : this(connection, null, null, DefaultEnableAutoSelect) { } - public Database(DbConnection connection, DatabaseType dbType) + public Database(DbConnection connection, DatabaseType? dbType) : this(connection, dbType, null, DefaultEnableAutoSelect) { } - public Database(DbConnection connection, DatabaseType dbType, IsolationLevel? isolationLevel) + public Database(DbConnection connection, DatabaseType? dbType, IsolationLevel? isolationLevel) : this(connection, dbType, isolationLevel, DefaultEnableAutoSelect) { } - public Database(DbConnection connection, DatabaseType dbType, IsolationLevel? isolationLevel, bool enableAutoSelect) + public Database(DbConnection connection, DatabaseType? dbType, IsolationLevel? isolationLevel, bool enableAutoSelect) { EnableAutoSelect = enableAutoSelect; KeepConnectionAlive = true; @@ -48,7 +51,7 @@ public Database(DbConnection connection, DatabaseType dbType, IsolationLevel? is _connectionString = connection.ConnectionString; _dbType = dbType ?? DatabaseType.Resolve(_sharedConnection.GetType().Name, null); _providerName = _dbType.GetProviderName(); - _isolationLevel = isolationLevel.HasValue ? isolationLevel.Value : _dbType.GetDefaultTransactionIsolationLevel(); + _isolationLevel = isolationLevel ?? _dbType.GetDefaultTransactionIsolationLevel(); _paramPrefix = _dbType.GetParameterPrefix(_connectionString); // Cause it is an external connection ensure that the isolation level matches ours @@ -61,7 +64,7 @@ public Database(DbConnection connection, DatabaseType dbType, IsolationLevel? is } public Database(string connectionString, DatabaseType databaseType, DbProviderFactory provider) - : this(connectionString, databaseType, provider, null, DefaultEnableAutoSelect) + : this(connectionString, databaseType, provider, null) { } public Database(string connectionString, DatabaseType databaseType, DbProviderFactory provider, IsolationLevel? isolationLevel = null, bool enableAutoSelect = DefaultEnableAutoSelect) @@ -69,20 +72,21 @@ public Database(string connectionString, DatabaseType databaseType, DbProviderFa EnableAutoSelect = enableAutoSelect; KeepConnectionAlive = false; + _sharedConnection = default!; _connectionString = connectionString; _factory = provider; _dbType = databaseType ?? DatabaseType.Resolve(_factory.GetType().Name, null); _providerName = _dbType.GetProviderName(); - _isolationLevel = isolationLevel.HasValue ? isolationLevel.Value : _dbType.GetDefaultTransactionIsolationLevel(); + _isolationLevel = isolationLevel ?? _dbType.GetDefaultTransactionIsolationLevel(); _paramPrefix = _dbType.GetParameterPrefix(_connectionString); } private readonly DatabaseType _dbType; - public DatabaseType DatabaseType { get { return _dbType; } } - public IsolationLevel IsolationLevel { get { return _isolationLevel; } } + public DatabaseType DatabaseType => _dbType; + public IsolationLevel IsolationLevel => _isolationLevel; - private IDictionary _data; - public IDictionary Data => _data ?? (_data = new Dictionary()); + private IDictionary? _data; + public IDictionary Data => _data ??= new Dictionary(); // Automatically close connection public void Dispose() @@ -118,7 +122,7 @@ private void OpenSharedConnectionImp(bool isInternal) ShouldCloseConnectionAutomatically = isInternal; - _sharedConnection = _factory.CreateConnection(); + _sharedConnection = _factory?.CreateConnection()!; if (_sharedConnection == null) throw new Exception("SQL Connection failed to configure."); _sharedConnection.ConnectionString = _connectionString; @@ -165,29 +169,19 @@ public void CloseSharedConnection() _sharedConnection.Close(); _sharedConnection.Dispose(); - _sharedConnection = null; + _sharedConnection = null!; } - public VersionExceptionHandling VersionException - { - get { return _versionException; } - set { _versionException = value; } - } + public VersionExceptionHandling VersionException { get; set; } = VersionExceptionHandling.Exception; // Access to our shared connection - public DbConnection Connection - { - get { return _sharedConnection; } - } + public DbConnection? Connection => _sharedConnection; - public DbTransaction Transaction - { - get { return _transaction; } - } + public DbTransaction? Transaction => _transaction; public DbParameter CreateParameter() { - using (var conn = _sharedConnection ?? _factory.CreateConnection()) + using (var conn = _sharedConnection ?? _factory?.CreateConnection()) { if (conn == null) throw new Exception("DB Connection no longer active and failed to reset."); using (var comm = conn.CreateCommand()) @@ -216,7 +210,7 @@ public void SetTransaction(DbTransaction tran) private void OnBeginTransactionInternal() { #if DEBUG - System.Diagnostics.Debug.WriteLine("Created new transaction using isolation level of " + _transaction.IsolationLevel + "."); + System.Diagnostics.Debug.WriteLine("Created new transaction using isolation level of " + _transaction?.IsolationLevel + "."); #endif OnBeginTransaction(); foreach (var interceptor in Interceptors.OfType()) @@ -349,9 +343,7 @@ public void CompleteTransaction() if (TransactionIsOk()) _transaction.Commit(); - if (_transaction != null) - _transaction.Dispose(); - + _transaction?.Dispose(); _transaction = null; OnCompleteTransactionInternal(); @@ -370,7 +362,7 @@ private bool TransactionIsOk() } // Add a parameter to a DB command - public virtual void AddParameter(DbCommand cmd, object value) + public virtual void AddParameter(DbCommand cmd, object? value) { // Convert value to from poco type to db type if (Mappers != null && value != null) @@ -395,7 +387,7 @@ public virtual void AddParameter(DbCommand cmd, object value) cmd.Parameters.Add(p); } - private void SetParameterValue(DbParameter p, object value) + private void SetParameterValue(DbParameter p, object? value) { if (value == null) { @@ -724,7 +716,7 @@ public T ExecuteScalar(string sql, CommandType commandType, params object[] a object val = ExecuteScalarHelper(cmd); if (val == null || val == DBNull.Value) - return default(T); + return default!; Type t = typeof(T); Type u = Nullable.GetUnderlyingType(t); @@ -817,7 +809,7 @@ public Dictionary Dictionary(string sql, params obje foreach (var line in Query>(sql, args)) { object key = line.ElementAt(0).Value; - object value = line.ElementAt(1).Value; + object? value = line.ElementAt(1).Value; if (isConverterSet == false) { @@ -829,12 +821,12 @@ public Dictionary Dictionary(string sql, params obje var keyConverted = (TKey)Convert.ChangeType(converter1(key), typeof(TKey)); var valueType = Nullable.GetUnderlyingType(typeof(TValue)) ?? typeof(TValue); - var valConv = converter2(value); - var valConverted = valConv != null ? (TValue)Convert.ChangeType(valConv, valueType) : default(TValue); + var valConv = converter2(value!); + var valConverted = valConv != null ? (TValue)Convert.ChangeType(valConv, valueType) : default; if (keyConverted != null) { - newDict.Add(keyConverted, valConverted); + newDict.Add(keyConverted, valConverted!); } } return newDict; @@ -848,7 +840,7 @@ public IEnumerable Query(string sql, params object[] args) public IEnumerable Query(Sql Sql) { - return Query(default(T), Sql); + return Query(default(T)!, Sql); } private async IAsyncEnumerable ReadAsync(object instance, DbDataReader r, PocoData pd) @@ -874,21 +866,21 @@ private async IAsyncEnumerable ReadAsync(object instance, DbDataReader r, private async IAsyncEnumerable ReadOneToManyAsync(T instance, DbDataReader r, Expression> listExpression, Func idFunc, PocoData pocoData) { - Func listFunc = null; - PocoMember pocoMember = null; - PocoMember foreignMember = null; + Func? listFunc = null; + PocoMember? pocoMember = null; + PocoMember? foreignMember = null; if (listExpression != null) { - idFunc = idFunc ?? (x => pocoData.GetPrimaryKeyValues(x)); + idFunc ??= (x => pocoData.GetPrimaryKeyValues(x)); listFunc = listExpression.Compile(); var key = PocoColumn.GenerateKey(MemberChainHelper.GetMembers(listExpression)); pocoMember = pocoData.Members.FirstOrDefault(x => x.Name == key); - foreignMember = pocoMember != null ? pocoMember.PocoMemberChildren.FirstOrDefault(x => x.Name == pocoMember.ReferenceMemberName && x.ReferenceType == ReferenceType.Foreign) : null; + foreignMember = pocoMember?.PocoMemberChildren.FirstOrDefault(x => x.Name == pocoMember.ReferenceMemberName && x.ReferenceType == ReferenceType.Foreign); } var factory = new MappingFactory(pocoData, r); - object prevPoco = null; + object? prevPoco = null; while (true) { @@ -928,7 +920,7 @@ private async IAsyncEnumerable ReadOneToManyAsync(T instance, DbDataReader } } - private IEnumerable Read(object instance, DbDataReader r, PocoData pd) + private IEnumerable Read(object? instance, DbDataReader r, PocoData pd) { var factory = new MappingFactory(pd, r); while (true) @@ -949,23 +941,23 @@ private IEnumerable Read(object instance, DbDataReader r, PocoData pd) } } - private IEnumerable ReadOneToMany(T instance, DbDataReader r, Expression> listExpression, Func idFunc, PocoData pocoData) + private IEnumerable ReadOneToMany(T instance, DbDataReader r, Expression> listExpression, Func? idFunc, PocoData pocoData) { - Func listFunc = null; - PocoMember pocoMember = null; - PocoMember foreignMember = null; + Func? listFunc = null; + PocoMember? pocoMember = null; + PocoMember? foreignMember = null; if (listExpression != null) { - idFunc = idFunc ?? (x => pocoData.GetPrimaryKeyValues(x)); + idFunc ??= (x => pocoData.GetPrimaryKeyValues(x)); listFunc = listExpression.Compile(); var key = PocoColumn.GenerateKey(MemberChainHelper.GetMembers(listExpression)); pocoMember = pocoData.Members.FirstOrDefault(x => x.Name == key); - foreignMember = pocoMember != null ? pocoMember.PocoMemberChildren.FirstOrDefault(x => x.Name == pocoMember.ReferenceMemberName && x.ReferenceType == ReferenceType.Foreign) : null; + foreignMember = pocoMember?.PocoMemberChildren.FirstOrDefault(x => x.Name == pocoMember.ReferenceMemberName && x.ReferenceType == ReferenceType.Foreign); } var factory = new MappingFactory(pocoData, r); - object prevPoco = null; + object? prevPoco = null; while (true) { @@ -983,7 +975,8 @@ private IEnumerable ReadOneToMany(T instance, DbDataReader r, Expression Query(Type type, Sql Sql) } } - internal IEnumerable QueryImp(T instance, Expression> listExpression, Func idFunc, Sql Sql, PocoData pocoData = null) + internal IEnumerable QueryImp(T instance, Expression>? listExpression, Func? idFunc, Sql Sql, PocoData? pocoData = null) { pocoData ??= PocoDataFactory.ForType(typeof(T)); @@ -1097,7 +1090,7 @@ private async Task ExecuteDataReader(DbCommand cmd, bool sync) public List FetchOneToMany(Expression> many, Sql sql) { - return QueryImp(default(T), many, null, sql).ToList(); + return QueryImp(default!, many, null, sql).ToList(); } public List FetchOneToMany(Expression> many, string sql, params object[] args) @@ -1107,7 +1100,7 @@ public List FetchOneToMany(Expression> many, string sql, pa public List FetchOneToMany(Expression> many, Func idFunc, Sql sql) { - return QueryImp(default(T), many, x => new[] { idFunc(x) }, sql).ToList(); + return QueryImp(default!, many, x => new[] { idFunc(x) }, sql).ToList(); } public List FetchOneToMany(Expression> many, Func idFunc, string sql, params object[] args) @@ -1210,13 +1203,13 @@ private async Task FetchMultipleImp(Type[] types, ob list1.Add((T1) factory.Map(r, default(T1))); break; case 2: - list2.Add((T2) factory.Map(r, default(T2))); + list2!.Add((T2) factory.Map(r, default(T2))); break; case 3: - list3.Add((T3) factory.Map(r, default(T3))); + list3!.Add((T3) factory.Map(r, default(T3))); break; case 4: - list4.Add((T4) factory.Map(r, default(T4))); + list4!.Add((T4) factory.Map(r, default(T4))); break; } } @@ -1233,14 +1226,14 @@ private async Task FetchMultipleImp(Type[] types, ob switch (types.Length) { case 2: - return ((Func, List, TRet>) cb)(list1, list2); + return ((Func, List, TRet>) cb)(list1, list2!); case 3: - return ((Func, List, List, TRet>) cb)(list1, list2, list3); + return ((Func, List, List, TRet>) cb)(list1, list2!, list3!); case 4: - return ((Func, List, List, List, TRet>) cb)(list1, list2, list3, list4); + return ((Func, List, List, List, TRet>) cb)(list1, list2!, list3!, list4!); } - return default(TRet); + return default(TRet)!; } finally { @@ -1343,6 +1336,7 @@ public T FirstOrDefaultInto(T instance, Sql sql) // Insert an annotated poco object public object Insert(T poco) { + if (poco == null) throw new ArgumentNullException(nameof(poco)); var tableInfo = PocoDataFactory.TableInfoForType(poco.GetType()); return Insert(tableInfo.TableName, tableInfo.PrimaryKey, tableInfo.AutoIncrement, poco); } @@ -1361,12 +1355,12 @@ public virtual object Insert(string tableName, string primaryKeyName, bool au return InsertAsyncImp(pd, tableName, primaryKeyName, autoIncrement, poco, true).RunSync(); } - public int InsertBatch(IEnumerable pocos, BatchOptions options = null) + public int InsertBatch(IEnumerable pocos, BatchOptions? options = null) { return InsertBatchAsyncImp(pocos, options, true).RunSync(); } - public void InsertBulk(IEnumerable pocos, InsertBulkOptions options = null) + public void InsertBulk(IEnumerable pocos, InsertBulkOptions? options = null) { try { @@ -1389,18 +1383,18 @@ public int Update(string tableName, string primaryKeyName, object poco, object p return Update(tableName, primaryKeyName, poco, primaryKeyValue, null); } - public virtual int Update(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns) + public virtual int Update(string tableName, string primaryKeyName, object poco, object? primaryKeyValue, IEnumerable? columns) { return UpdateImpAsync(tableName, primaryKeyName, poco, primaryKeyValue, columns, true).RunSync(); } - public int UpdateBatch(IEnumerable> pocos, BatchOptions options = null) + public int UpdateBatch(IEnumerable> pocos, BatchOptions? options = null) { return UpdateBatchAsyncImp(pocos, options, true).RunSync(); } // Update a record with values from a poco. primary key value can be either supplied or read from the poco - private async Task UpdateImpAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns, bool sync) + private async Task UpdateImpAsync(string tableName, string primaryKeyName, object poco, object? primaryKeyValue, IEnumerable? columns, bool sync) { if (!OnUpdatingInternal(new UpdateContext(poco, tableName, primaryKeyName, primaryKeyValue, columns))) return 0; @@ -1444,7 +1438,7 @@ internal static string BuildPrimaryKeySql(Database database, Dictionary x.Value == null || x.Value == DBNull.Value ? string.Format("{0} IS NULL", database.DatabaseType.EscapeSqlIdentifier(x.Key)) : string.Format("{0} = @{1}", database.DatabaseType.EscapeSqlIdentifier(x.Key), tempIndex + i)).ToArray()); } - internal static Dictionary GetPrimaryKeyValues(Database database, PocoData pocoData, string primaryKeyName, object primaryKeyValueOrPoco, bool isPoco) + internal static Dictionary GetPrimaryKeyValues(Database database, PocoData? pocoData, string primaryKeyName, object primaryKeyValueOrPoco, bool isPoco) { Dictionary primaryKeyValues; @@ -1463,7 +1457,7 @@ internal static Dictionary GetPrimaryKeyValues(Database database } else { - primaryKeyValues = ProcessMapper(database, pocoData, multiplePrimaryKeysNames.ToDictionary(x => x, x => pocoData.Columns[x].GetValue(primaryKeyValueOrPoco), StringComparer.OrdinalIgnoreCase)); + primaryKeyValues = ProcessMapper(database, pocoData!, multiplePrimaryKeysNames.ToDictionary(x => x, x => pocoData!.Columns[x].GetValue(primaryKeyValueOrPoco), StringComparer.OrdinalIgnoreCase)); } return primaryKeyValues; @@ -1490,7 +1484,7 @@ public int Update(string tableName, string primaryKeyName, object poco) return Update(tableName, primaryKeyName, poco, null); } - public int Update(string tableName, string primaryKeyName, object poco, IEnumerable columns) + public int Update(string tableName, string primaryKeyName, object poco, IEnumerable? columns) { return Update(tableName, primaryKeyName, poco, null, columns); } @@ -1502,6 +1496,7 @@ public int Update(object poco, IEnumerable columns) public int Update(T poco, Expression> fields) { + if (poco == null) throw new ArgumentNullException(nameof(poco)); var expression = DatabaseType.ExpressionVisitor(this, PocoDataFactory.ForType(typeof(T))); expression = expression.Select(fields); var columnNames = ((ISqlExpression) expression).SelectMembers.Select(x => x.PocoColumn.ColumnName); @@ -1519,7 +1514,7 @@ public int Update(object poco, object primaryKeyValue) return Update(poco, primaryKeyValue, null); } - public int Update(object poco, object primaryKeyValue, IEnumerable columns) + public int Update(object poco, object? primaryKeyValue, IEnumerable? columns) { var tableInfo = PocoDataFactory.TableInfoForType(poco.GetType()); return Update(tableInfo.TableName, tableInfo.PrimaryKey, poco, primaryKeyValue, columns); @@ -1528,13 +1523,13 @@ public int Update(object poco, object primaryKeyValue, IEnumerable colum public int Update(string sql, params object[] args) { var tableInfo = PocoDataFactory.TableInfoForType(typeof(T)); - return Execute(string.Format("UPDATE {0} {1}", _dbType.EscapeTableName(tableInfo.TableName), sql), args); + return Execute($"UPDATE {_dbType.EscapeTableName(tableInfo.TableName)} {sql}", args); } public int Update(Sql sql) { var tableInfo = PocoDataFactory.TableInfoForType(typeof(T)); - return Execute(new Sql(string.Format("UPDATE {0}", _dbType.EscapeTableName(tableInfo.TableName))).Append(sql)); + return Execute(new Sql($"UPDATE {_dbType.EscapeTableName(tableInfo.TableName)}").Append(sql)); } public IDeleteQueryProvider DeleteMany() @@ -1547,27 +1542,27 @@ public int Delete(string tableName, string primaryKeyName, object poco) return Delete(tableName, primaryKeyName, poco, null); } - public virtual int Delete(string tableName, string primaryKeyName, object poco, object primaryKeyValue) + public virtual int Delete(string tableName, string primaryKeyName, object? poco, object? primaryKeyValue) { return DeleteImpAsync(tableName, primaryKeyName, poco, primaryKeyValue, true).RunSync(); } - private async Task DeleteImpAsync(string tableName, string primaryKeyName, object poco, object primaryKeyValue, bool sync) + private async Task DeleteImpAsync(string tableName, string primaryKeyName, object? poco, object? primaryKeyValue, bool sync) { if (!OnDeletingInternal(new DeleteContext(poco, tableName, primaryKeyName, primaryKeyValue))) return 0; var pd = poco != null ? PocoDataFactory.ForObject(poco, primaryKeyName, true) : null; - var primaryKeyValuePairs = GetPrimaryKeyValues(this, pd, primaryKeyName, primaryKeyValue ?? poco, primaryKeyValue == null); + var primaryKeyValuePairs = GetPrimaryKeyValues(this, pd, primaryKeyName, primaryKeyValue ?? poco!, primaryKeyValue == null); // Do it var index = 0; - var sql = string.Format("DELETE FROM {0} WHERE {1}", _dbType.EscapeTableName(tableName), BuildPrimaryKeySql(this, primaryKeyValuePairs, ref index)); + var sql = $"DELETE FROM {_dbType.EscapeTableName(tableName)} WHERE {BuildPrimaryKeySql(this, primaryKeyValuePairs, ref index)}"; var rawValues = primaryKeyValuePairs.Select(x => x.Value).ToList(); var versionColumn = pd?.AllColumns.SingleOrDefault(x => x.VersionColumn); - string versionName = null; - object versionValue = null; + string? versionName = null; + object? versionValue = null; if (versionColumn != null) { versionName = versionColumn.ColumnName; @@ -1587,7 +1582,7 @@ private async Task DeleteImpAsync(string tableName, string primaryKeyName, if (result == 0 && !string.IsNullOrEmpty(versionName) && VersionException == VersionExceptionHandling.Exception) { throw new DBConcurrencyException(string.Format("A Concurrency update occurred in table '{0}' for primary key value(s) = '{1}' and version = '{2}'", tableName, - string.Join(",", primaryKeyValuePairs.Values.Select(x => x.ToString()).ToArray()), versionValue)); + string.Join(",", primaryKeyValuePairs.Values.Select(x => x?.ToString()).ToArray()), versionValue)); } return result; @@ -1610,13 +1605,13 @@ public int Delete(object pocoOrPrimaryKey) public int Delete(string sql, params object[] args) { var tableInfo = PocoDataFactory.TableInfoForType(typeof(T)); - return Execute(string.Format("DELETE FROM {0} {1}", _dbType.EscapeTableName(tableInfo.TableName), sql), args); + return Execute($"DELETE FROM {_dbType.EscapeTableName(tableInfo.TableName)} {sql}", args); } public int Delete(Sql sql) { var tableInfo = PocoDataFactory.TableInfoForType(typeof(T)); - return Execute(new Sql(string.Format("DELETE FROM {0}", _dbType.EscapeTableName(tableInfo.TableName))).Append(sql)); + return Execute(new Sql($"DELETE FROM {_dbType.EscapeTableName(tableInfo.TableName)}").Append(sql)); } /// Checks if a poco represents a new record. @@ -1627,6 +1622,7 @@ public bool IsNew(T poco) private async Task IsNewAsync(T poco, bool sync) { + if (poco == null) throw new ArgumentNullException(nameof(poco)); if (poco is System.Dynamic.ExpandoObject || poco is PocoExpando) { return true; @@ -1684,6 +1680,7 @@ private async Task IsNewAsync(T poco, bool sync) // Insert new record or Update existing record public void Save(T poco) { + if (poco == null) throw new ArgumentNullException(nameof(poco)); var tableInfo = PocoDataFactory.TableInfoForType(poco.GetType()); if (IsNew(poco)) { @@ -1719,15 +1716,20 @@ void DoPreExecute(DbCommand cmd) _lastArgs = (from DbParameter parameter in cmd.Parameters select parameter.Value).ToArray(); } - public string LastSQL { get { return _lastSql; } } - public object[] LastArgs { get { return _lastArgs; } } - public string LastCommand - { - get { return FormatCommand(_lastSql, _lastArgs); } - } + public string? LastSQL => _lastSql; + public object[]? LastArgs => _lastArgs; + + public string LastCommand => FormatCommand(_lastSql, _lastArgs); private class FormattedParameter { + public FormattedParameter(Type type, object value, DbParameter parameter) + { + Type = type; + Value = value; + Parameter = parameter; + } + public Type Type { get; set; } public object Value { get; set; } public DbParameter Parameter { get; set; } @@ -1735,16 +1737,14 @@ private class FormattedParameter public string FormatCommand(DbCommand cmd) { - var parameters = cmd.Parameters.Cast().Select(parameter => new FormattedParameter() + var parameters = cmd.Parameters.Cast().Select(parameter => { - Type = parameter.Value.GetTheType(), - Value = parameter.Value, - Parameter = parameter + return new FormattedParameter(parameter.Value.GetTheType(), parameter.Value, parameter); }); return FormatCommand(cmd.CommandText, parameters.Cast().ToArray()); } - public string FormatCommand(string sql, object[] args) + public string FormatCommand(string? sql, object[]? args) { var sb = new StringBuilder(); if (sql == null) @@ -1757,8 +1757,7 @@ public string FormatCommand(string sql, object[] args) { var type = args[i] != null ? args[i].GetType().Name : string.Empty; var value = args[i]; - var formatted = args[i] as FormattedParameter; - if (formatted != null) + if (args[i] is FormattedParameter formatted) { type = formatted.Type != null ? formatted.Type.Name : string.Format("{0}, {1}", formatted.Parameter.GetType().Name, formatted.Parameter.DbType); value = formatted.Value; @@ -1770,24 +1769,21 @@ public string FormatCommand(string sql, object[] args) return sb.ToString(); } - private List _interceptors; - public List Interceptors - { - get { return _interceptors ?? (_interceptors = new List()); } - } + private List? _interceptors; + public List Interceptors => _interceptors ??= new List(); - private MapperCollection _mappers; + private MapperCollection? _mappers; public MapperCollection Mappers { - get { return _mappers ?? (_mappers = new MapperCollection()); } - set { _mappers = value; } + get => _mappers ??= new MapperCollection(); + set => _mappers = value; } - private IPocoDataFactory _pocoDataFactory; + private IPocoDataFactory? _pocoDataFactory; public IPocoDataFactory PocoDataFactory { - get { return _pocoDataFactory ?? (_pocoDataFactory = new PocoDataFactory(Mappers)); } - set { _pocoDataFactory = value; } + get => _pocoDataFactory ??= new PocoDataFactory(Mappers); + set => _pocoDataFactory = value; } public string ConnectionString { get { return _connectionString; } } @@ -1795,14 +1791,13 @@ public IPocoDataFactory PocoDataFactory // Member variables private readonly string _connectionString; private readonly string _providerName; - private DbProviderFactory _factory; + private DbProviderFactory? _factory; private DbConnection _sharedConnection; - private DbTransaction _transaction; + private DbTransaction? _transaction; private IsolationLevel _isolationLevel; - private string _lastSql; - private object[] _lastArgs; + private string? _lastSql; + private object[]? _lastArgs; private string _paramPrefix = "@"; - private VersionExceptionHandling _versionException = VersionExceptionHandling.Exception; private readonly bool _connectionPassedIn; internal int ExecuteNonQueryHelper(DbCommand cmd) @@ -1860,13 +1855,13 @@ public static bool IsEnum(MemberInfoData memberInfo) internal static class ProcessMapperExtensions { - internal static object ProcessMapper(this IDatabase database, PocoColumn pc, object value) + internal static object ProcessMapper(this IDatabase database, PocoColumn pc, object? value) { var converter = database.Mappers.Find(x => x.GetToDbConverter(pc.ColumnType, pc.MemberInfoData.MemberInfo)); return converter != null ? converter(value) : ProcessDefaultMappings(database, pc, value); } - internal static object ProcessDefaultMappings(IDatabase database, PocoColumn pocoColumn, object value) + internal static object ProcessDefaultMappings(IDatabase database, PocoColumn pocoColumn, object? value) { if (pocoColumn.SerializedColumn) { diff --git a/src/NPoco/IDatabase.cs b/src/NPoco/IDatabase.cs index b72069e2..aadda215 100644 --- a/src/NPoco/IDatabase.cs +++ b/src/NPoco/IDatabase.cs @@ -1,3 +1,4 @@ +#nullable enable using System; using System.Collections.Generic; using System.Linq.Expressions; @@ -26,12 +27,12 @@ public interface IDatabase : IAsyncDatabase, IDatabaseQuery, IDatabaseConfig /// /// Insert POCO's into database using SqlBulkCopy for SqlServer (other DB's currently fall back to looping each row) /// - void InsertBulk(IEnumerable pocos, InsertBulkOptions options = null); + void InsertBulk(IEnumerable pocos, InsertBulkOptions? options = null); /// /// Insert POCO's into database by concatenating sql using the provided batch options /// - int InsertBatch(IEnumerable pocos, BatchOptions options = null); + int InsertBatch(IEnumerable pocos, BatchOptions? options = null); /// /// Update POCO in the specified table, primary key and primarkey value @@ -46,12 +47,12 @@ public interface IDatabase : IAsyncDatabase, IDatabaseQuery, IDatabaseConfig /// /// Update POCO in the specified table, primary key, primarkey value for only the columns specified /// - int Update(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns); + int Update(string tableName, string primaryKeyName, object poco, object? primaryKeyValue, IEnumerable? columns); /// /// Update POCO in the specified table, primary key for only the columns specified /// - int Update(string tableName, string primaryKeyName, object poco, IEnumerable columns); + int Update(string tableName, string primaryKeyName, object poco, IEnumerable? columns); /// /// Update POCO by convention or configuration for only the columns specified @@ -61,7 +62,7 @@ public interface IDatabase : IAsyncDatabase, IDatabaseQuery, IDatabaseConfig /// /// Update POCO by primary key for only the columns specified /// - int Update(object poco, object primaryKeyValue, IEnumerable columns); + int Update(object poco, object primaryKeyValue, IEnumerable? columns); /// /// Update POCO by convention or configuration @@ -97,7 +98,7 @@ public interface IDatabase : IAsyncDatabase, IDatabaseQuery, IDatabaseConfig /// /// Update POCO's into database by concatenating sql using the provided batch options /// - int UpdateBatch(IEnumerable> pocos, BatchOptions options = null); + int UpdateBatch(IEnumerable> pocos, BatchOptions? options = null); /// /// Generate an update statement using a Fluent syntax. Remember to call Execute. @@ -112,7 +113,7 @@ public interface IDatabase : IAsyncDatabase, IDatabaseQuery, IDatabaseConfig /// /// Delete POCO specifying the table name, primary key name and primary key value /// - int Delete(string tableName, string primaryKeyName, object poco, object primaryKeyValue); + int Delete(string tableName, string primaryKeyName, object? poco, object? primaryKeyValue); /// /// Delete POCO using convention or configuration From a28cb5a0d53d2ccff5804a26bcbde270637fe90a Mon Sep 17 00:00:00 2001 From: Bryan Boettcher Date: Mon, 19 Oct 2020 09:47:48 -0500 Subject: [PATCH 36/42] adding missing ConfigureAwaits --- src/NPoco/AsyncDatabase.cs | 4 ++-- src/NPoco/Database.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/NPoco/AsyncDatabase.cs b/src/NPoco/AsyncDatabase.cs index 8ee9b4c9..3809d328 100644 --- a/src/NPoco/AsyncDatabase.cs +++ b/src/NPoco/AsyncDatabase.cs @@ -323,7 +323,7 @@ public Task IsNewAsync(T poco) public async Task SaveAsync(T poco) { var tableInfo = PocoDataFactory.TableInfoForType(poco.GetType()); - if (await IsNewAsync(poco)) + if (await IsNewAsync(poco).ConfigureAwait(false)) { await InsertAsync(tableInfo.TableName, tableInfo.PrimaryKey, tableInfo.AutoIncrement, poco).ConfigureAwait(false); } @@ -338,7 +338,7 @@ private async Task PocoExistsAsync(T poco, bool sync) var sql = GetExistsSql(poco, true); var result = sync ? ExecuteScalar(sql) - : await ExecuteScalarAsync(sql); + : await ExecuteScalarAsync(sql).ConfigureAwait(false); return result > 0; } diff --git a/src/NPoco/Database.cs b/src/NPoco/Database.cs index 419c6d5e..b9c40999 100644 --- a/src/NPoco/Database.cs +++ b/src/NPoco/Database.cs @@ -1144,7 +1144,7 @@ private async Task> PageImpAsync(long page, long itemsPerPage, string // Get the records result.Items = sync ? Fetch(new Sql(sqlPage, args)) - : await FetchAsync(new Sql(sqlPage, args)); + : await FetchAsync(new Sql(sqlPage, args)).ConfigureAwait(false); return result; } @@ -1577,7 +1577,7 @@ private async Task DeleteImpAsync(string tableName, string primaryKeyName, var result = sync ? Execute(sql, rawValues.ToArray()) - : await ExecuteAsync(sql, rawValues.ToArray()); + : await ExecuteAsync(sql, rawValues.ToArray()).ConfigureAwait(false); if (result == 0 && !string.IsNullOrEmpty(versionName) && VersionException == VersionExceptionHandling.Exception) { From cd5de3677ca37c069f3e4d032b8f7ae44df00b18 Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Wed, 21 Oct 2020 20:12:17 +1100 Subject: [PATCH 37/42] Use MemberInfoKey in BulkInsert to support complex properties #609 --- src/NPoco.SqlServer/SqlBulkCopyHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NPoco.SqlServer/SqlBulkCopyHelper.cs b/src/NPoco.SqlServer/SqlBulkCopyHelper.cs index 3f55fa1a..d489c75a 100644 --- a/src/NPoco.SqlServer/SqlBulkCopyHelper.cs +++ b/src/NPoco.SqlServer/SqlBulkCopyHelper.cs @@ -70,8 +70,8 @@ private static DataTable BuildBulkInsertDataTable(IDatabase db, IEnumerable Date: Wed, 21 Oct 2020 21:54:27 +1100 Subject: [PATCH 38/42] Escape table name for sql bulk copy #451 --- src/NPoco.SqlServer/SqlBulkCopyHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NPoco.SqlServer/SqlBulkCopyHelper.cs b/src/NPoco.SqlServer/SqlBulkCopyHelper.cs index d489c75a..0f2e58d2 100644 --- a/src/NPoco.SqlServer/SqlBulkCopyHelper.cs +++ b/src/NPoco.SqlServer/SqlBulkCopyHelper.cs @@ -48,7 +48,7 @@ private static DataTable BuildBulkInsertDataTable(IDatabase db, IEnumerable Date: Thu, 22 Oct 2020 09:50:09 +1100 Subject: [PATCH 39/42] Adds SQL Server command formatting for easy copy & paste #429 --- .../DatabaseTypes/SqlServerDatabaseType.cs | 46 +++++ src/NPoco.SqlServer/SqlServerDatabase.cs | 4 +- src/NPoco/Database.cs | 195 +++--------------- src/NPoco/DatabaseType.cs | 45 ++++ src/NPoco/ParameterHelper.cs | 91 ++++++++ test/NPoco.Tests/FormatCommandTest.cs | 3 +- .../NPoco.Tests/FormatSqlServerCommandTest.cs | 84 ++++++++ 7 files changed, 304 insertions(+), 164 deletions(-) create mode 100644 test/NPoco.Tests/FormatSqlServerCommandTest.cs diff --git a/src/NPoco.SqlServer/DatabaseTypes/SqlServerDatabaseType.cs b/src/NPoco.SqlServer/DatabaseTypes/SqlServerDatabaseType.cs index 688695c0..68659430 100644 --- a/src/NPoco.SqlServer/DatabaseTypes/SqlServerDatabaseType.cs +++ b/src/NPoco.SqlServer/DatabaseTypes/SqlServerDatabaseType.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Data; using System.Data.Common; +using System.Linq; +using System.Text; using System.Threading.Tasks; using NPoco.SqlServer; @@ -97,5 +99,49 @@ public override object ProcessDefaultMappings(PocoColumn pocoColumn, object valu } return base.ProcessDefaultMappings(pocoColumn, value); } + + + public override string FormatCommand(string sql, object[] args) + { + if (sql == null) + return ""; + + var sb = new StringBuilder(); + if (args != null && args.Length > 0) + { + for (int i = 0; i < args.Length; i++) + { + var value = args[i]; + var formatted = args[i] as FormattedParameter; + if (formatted != null) + { + value = formatted.Value; + } + + var p = new SqlParameter(); + ParameterHelper.SetParameterValue(this, p, args[i]); + if (p.Size == 0 || p.SqlDbType == SqlDbType.UniqueIdentifier) + { + if (value == null && (p.SqlDbType == SqlDbType.NVarChar || p.SqlDbType == SqlDbType.VarChar)) + { + sb.AppendFormat("DECLARE {0}{1} {2} = null\n", GetParameterPrefix(string.Empty), i, p.SqlDbType); + } + else + { + sb.AppendFormat("DECLARE {0}{1} {2} = '{3}'\n", GetParameterPrefix(string.Empty), i, p.SqlDbType, value); + } + } + else + { + sb.AppendFormat("DECLARE {0}{1} {2}[{3}] = '{4}'\n", GetParameterPrefix(string.Empty), i, p.SqlDbType, p.Size, value); + } + } + } + + sb.AppendFormat("\n{0}", sql); + + return sb.ToString(); + } + } } \ No newline at end of file diff --git a/src/NPoco.SqlServer/SqlServerDatabase.cs b/src/NPoco.SqlServer/SqlServerDatabase.cs index dce70151..1e9e2778 100644 --- a/src/NPoco.SqlServer/SqlServerDatabase.cs +++ b/src/NPoco.SqlServer/SqlServerDatabase.cs @@ -2,7 +2,9 @@ using NPoco.SqlServer; using System; using System.Threading.Tasks; +using System.Text; using NPoco.DatabaseTypes; +using System.Data; namespace NPoco.SqlServer { @@ -39,6 +41,6 @@ protected override async Task ExecutionHookAsync(Func> action) } return await base.ExecutionHookAsync(action).ConfigureAwait(false); - } + } } } diff --git a/src/NPoco/Database.cs b/src/NPoco/Database.cs index b9c40999..394b4f9e 100644 --- a/src/NPoco/Database.cs +++ b/src/NPoco/Database.cs @@ -36,7 +36,7 @@ public Database(DbConnection connection) public Database(DbConnection connection, DatabaseType? dbType) : this(connection, dbType, null, DefaultEnableAutoSelect) { } - + public Database(DbConnection connection, DatabaseType? dbType, IsolationLevel? isolationLevel) : this(connection, dbType, isolationLevel, DefaultEnableAutoSelect) { } @@ -382,106 +382,17 @@ public virtual void AddParameter(DbCommand cmd, object? value) var p = cmd.CreateParameter(); p.ParameterName = string.Format("{0}{1}", _paramPrefix, cmd.Parameters.Count); - SetParameterValue(p, value); - - cmd.Parameters.Add(p); - } - - private void SetParameterValue(DbParameter p, object? value) - { - if (value == null) - { - p.Value = DBNull.Value; - return; - } - - // Give the database type first crack at converting to DB required type - value = _dbType.MapParameterValue(value); - - var dbtypeSet = false; - var t = value.GetType(); - var underlyingT = Nullable.GetUnderlyingType(t); - if (t.GetTypeInfo().IsEnum || (underlyingT != null && underlyingT.GetTypeInfo().IsEnum)) // PostgreSQL .NET driver wont cast enum to int - { - p.Value = (int)value; - } - else if (t == typeof(Guid)) - { - p.Value = value; - p.DbType = DbType.Guid; - p.Size = 40; - dbtypeSet = true; - } - else if (t == typeof(string)) - { - var strValue = value as string; - if (strValue == null) - { - p.Size = 0; - p.Value = DBNull.Value; - } - else - { - // out of memory exception occurs if trying to save more than 4000 characters to SQL Server CE NText column. Set before attempting to set Size, or Size will always max out at 4000 - if (strValue.Length + 1 > 4000 && p.GetType().Name == "SqlCeParameter") - { - p.GetType().GetProperty("SqlDbType").SetValue(p, SqlDbType.NText, null); - } - - p.Size = Math.Max(strValue.Length + 1, 4000); // Help query plan caching by using common size - p.Value = value; - } - } - else if (t == typeof(AnsiString)) - { - var ansistrValue = value as AnsiString; - if (ansistrValue?.Value == null) - { - p.Size = 0; - p.Value = DBNull.Value; - p.DbType = DbType.AnsiString; - } - else - { - // Thanks @DataChomp for pointing out the SQL Server indexing performance hit of using wrong string type on varchar - p.Size = Math.Max(ansistrValue.Value.Length + 1, 4000); - p.Value = ansistrValue.Value; - p.DbType = DbType.AnsiString; - } - dbtypeSet = true; - } - else if (value.GetType().Name == "SqlGeography") //SqlGeography is a CLR Type - { - p.GetType().GetProperty("UdtTypeName").SetValue(p, "geography", null); //geography is the equivalent SQL Server Type - p.Value = value; - } - - else if (value.GetType().Name == "SqlGeometry") //SqlGeometry is a CLR Type - { - p.GetType().GetProperty("UdtTypeName").SetValue(p, "geometry", null); //geography is the equivalent SQL Server Type - p.Value = value; - } - else - { - p.Value = value; - } + ParameterHelper.SetParameterValue(_dbType, p, value); - if (!dbtypeSet) - { - var dbType = _dbType.LookupDbType(p.Value.GetTheType(), p.ParameterName); - if (dbType.HasValue) - { - p.DbType = dbType.Value; - } - } + cmd.Parameters.Add(p); } - + // Create a command private DbCommand CreateCommand(DbConnection connection, string sql, params object[] args) { return CreateCommand(connection, CommandType.Text, sql, args); } - + public virtual DbCommand CreateCommand(DbConnection connection, CommandType commandType, string sql, params object[] args) { if (commandType == CommandType.StoredProcedure) @@ -497,7 +408,7 @@ public virtual DbCommand CreateCommand(DbConnection connection, CommandType comm // Create the command and add parameters DbCommand cmd = connection.CreateCommand(); cmd.Connection = connection; - cmd.CommandText = sql; + cmd.CommandText = sql; cmd.Transaction = _transaction; foreach (var item in args) @@ -621,7 +532,7 @@ private bool OnDeletingInternal(DeleteContext deleteContext) var result = OnDeleting(deleteContext); return result && Interceptors.OfType().All(x => x.OnDeleting(this, deleteContext)); } - + public DbCommand CreateStoredProcedureCommand(DbConnection connection, string name, params object[] args) { DbCommand cmd = connection.CreateCommand(); @@ -640,13 +551,13 @@ public DbCommand CreateStoredProcedureCommand(DbConnection connection, string na else { var props = args[0].GetType().GetProperties().Select(x => new { x.Name, Value = x.GetValue(args[0], null) }).ToList(); - foreach(var item in props) + foreach (var item in props) { DbParameter param = cmd.CreateParameter(); param.ParameterName = item.Name; - SetParameterValue(param, item.Value); - + ParameterHelper.SetParameterValue(_dbType, param, item.Value); + cmd.Parameters.Add(param); } } @@ -700,7 +611,7 @@ public T ExecuteScalar(string sql, params object[] args) { return ExecuteScalar(new Sql(sql, args)); } - + public T ExecuteScalar(Sql Sql) { return ExecuteScalar(Sql.SQL, CommandType.Text, Sql.Arguments); @@ -975,8 +886,8 @@ private IEnumerable ReadOneToMany(T instance, DbDataReader r, Expression QueryImp(T instance, Expression>? list var sql = Sql.SQL; var args = Sql.Arguments; - if (EnableAutoSelect) sql = AutoSelectHelper.AddSelectClause(this, typeof (T), sql); + if (EnableAutoSelect) sql = AutoSelectHelper.AddSelectClause(this, typeof(T), sql); try { @@ -1176,7 +1087,7 @@ private async Task FetchMultipleImp(Type[] types, ob OpenSharedConnectionInternal(); using var cmd = CreateCommand(_sharedConnection, sql, args); using var r = sync ? ExecuteDataReader(cmd, true).RunSync() : await ExecuteDataReader(cmd, false).ConfigureAwait(false); - + var typeIndex = 1; var list1 = new List(); var list2 = types.Length > 1 ? new List() : null; @@ -1200,16 +1111,16 @@ private async Task FetchMultipleImp(Type[] types, ob switch (typeIndex) { case 1: - list1.Add((T1) factory.Map(r, default(T1))); + list1.Add((T1)factory.Map(r, default(T1))); break; case 2: - list2!.Add((T2) factory.Map(r, default(T2))); + list2!.Add((T2)factory.Map(r, default(T2))); break; case 3: - list3!.Add((T3) factory.Map(r, default(T3))); + list3!.Add((T3)factory.Map(r, default(T3))); break; case 4: - list4!.Add((T4) factory.Map(r, default(T4))); + list4!.Add((T4)factory.Map(r, default(T4))); break; } } @@ -1226,11 +1137,11 @@ private async Task FetchMultipleImp(Type[] types, ob switch (types.Length) { case 2: - return ((Func, List, TRet>) cb)(list1, list2!); + return ((Func, List, TRet>)cb)(list1, list2!); case 3: - return ((Func, List, List, TRet>) cb)(list1, list2!, list3!); + return ((Func, List, List, TRet>)cb)(list1, list2!, list3!); case 4: - return ((Func, List, List, List, TRet>) cb)(list1, list2!, list3!, list4!); + return ((Func, List, List, List, TRet>)cb)(list1, list2!, list3!, list4!); } return default(TRet)!; @@ -1261,7 +1172,7 @@ public T SingleOrDefaultById(object primaryKey) private Sql GenerateSingleByIdSql(object primaryKey) { var index = 0; - var pd = PocoDataFactory.ForType(typeof (T)); + var pd = PocoDataFactory.ForType(typeof(T)); var primaryKeyValuePairs = GetPrimaryKeyValues(this, pd, pd.TableInfo.PrimaryKey, primaryKey, primaryKey is T); var sql = AutoSelectHelper.AddSelectClause(this, typeof(T), string.Format("WHERE {0}", BuildPrimaryKeySql(this, primaryKeyValuePairs, ref index))); var args = primaryKeyValuePairs.Select(x => x.Value).ToArray(); @@ -1430,7 +1341,7 @@ private async Task UpdateImpAsync(string tableName, string primaryKeyName, return result; } - + internal static string BuildPrimaryKeySql(Database database, Dictionary primaryKeyValuePair, ref int index) { var tempIndex = index; @@ -1499,8 +1410,8 @@ public int Update(T poco, Expression> fields) if (poco == null) throw new ArgumentNullException(nameof(poco)); var expression = DatabaseType.ExpressionVisitor(this, PocoDataFactory.ForType(typeof(T))); expression = expression.Select(fields); - var columnNames = ((ISqlExpression) expression).SelectMembers.Select(x => x.PocoColumn.ColumnName); - var otherNames = ((ISqlExpression) expression).GeneralMembers.Select(x => x.PocoColumn.ColumnName); + var columnNames = ((ISqlExpression)expression).SelectMembers.Select(x => x.PocoColumn.ColumnName); + var otherNames = ((ISqlExpression)expression).GeneralMembers.Select(x => x.PocoColumn.ColumnName); return Update(poco, columnNames.Union(otherNames)); } @@ -1620,7 +1531,7 @@ public bool IsNew(T poco) return IsNewAsync(poco, true).RunSync(); } - private async Task IsNewAsync(T poco, bool sync) + private async Task IsNewAsync(T poco, bool sync) { if (poco == null) throw new ArgumentNullException(nameof(poco)); if (poco is System.Dynamic.ExpandoObject || poco is PocoExpando) @@ -1638,8 +1549,8 @@ private async Task IsNewAsync(T poco, bool sync) } else if (pd.TableInfo.PrimaryKey.Contains(",")) { - return sync - ? !PocoExistsAsync(poco, true).RunSync() + return sync + ? !PocoExistsAsync(poco, true).RunSync() : !await PocoExistsAsync(poco, false).ConfigureAwait(false); } else @@ -1721,52 +1632,14 @@ void DoPreExecute(DbCommand cmd) public string LastCommand => FormatCommand(_lastSql, _lastArgs); - private class FormattedParameter + public virtual string FormatCommand(DbCommand cmd) { - public FormattedParameter(Type type, object value, DbParameter parameter) - { - Type = type; - Value = value; - Parameter = parameter; - } - - public Type Type { get; set; } - public object Value { get; set; } - public DbParameter Parameter { get; set; } - } - - public string FormatCommand(DbCommand cmd) - { - var parameters = cmd.Parameters.Cast().Select(parameter => - { - return new FormattedParameter(parameter.Value.GetTheType(), parameter.Value, parameter); - }); - return FormatCommand(cmd.CommandText, parameters.Cast().ToArray()); + return _dbType.FormatCommand(cmd); } public string FormatCommand(string? sql, object[]? args) { - var sb = new StringBuilder(); - if (sql == null) - return ""; - sb.Append(sql); - if (args != null && args.Length > 0) - { - sb.Append("\n"); - for (int i = 0; i < args.Length; i++) - { - var type = args[i] != null ? args[i].GetType().Name : string.Empty; - var value = args[i]; - if (args[i] is FormattedParameter formatted) - { - type = formatted.Type != null ? formatted.Type.Name : string.Format("{0}, {1}", formatted.Parameter.GetType().Name, formatted.Parameter.DbType); - value = formatted.Value; - } - sb.AppendFormat("\t -> {0}{1} [{2}] = \"{3}\"\n", _paramPrefix, i, type, value); - } - sb.Remove(sb.Length - 1, 1); - } - return sb.ToString(); + return _dbType.FormatCommand(sql, args); } private List? _interceptors; @@ -1786,7 +1659,7 @@ public IPocoDataFactory PocoDataFactory set => _pocoDataFactory = value; } - public string ConnectionString { get { return _connectionString; } } + public string ConnectionString => _connectionString; // Member variables private readonly string _connectionString; @@ -1860,7 +1733,7 @@ internal static object ProcessMapper(this IDatabase database, PocoColumn pc, obj var converter = database.Mappers.Find(x => x.GetToDbConverter(pc.ColumnType, pc.MemberInfoData.MemberInfo)); return converter != null ? converter(value) : ProcessDefaultMappings(database, pc, value); } - + internal static object ProcessDefaultMappings(IDatabase database, PocoColumn pocoColumn, object? value) { if (pocoColumn.SerializedColumn) diff --git a/src/NPoco/DatabaseType.cs b/src/NPoco/DatabaseType.cs index 4bb222f5..26a9d75b 100644 --- a/src/NPoco/DatabaseType.cs +++ b/src/NPoco/DatabaseType.cs @@ -8,6 +8,7 @@ using NPoco.Expressions; using System.Reflection; using System.Threading.Tasks; +using System.Text; namespace NPoco { @@ -364,5 +365,49 @@ public virtual object ProcessDefaultMappings(PocoColumn pocoColumn, object value { return value; } + + internal class FormattedParameter + { + public Type Type { get; set; } + public object Value { get; set; } + public DbParameter Parameter { get; set; } + } + + public virtual string FormatCommand(DbCommand cmd) + { + var parameters = cmd.Parameters.Cast().Select(parameter => new FormattedParameter() + { + Type = parameter.Value.GetTheType(), + Value = parameter.Value, + Parameter = parameter + }); + return FormatCommand(cmd.CommandText, parameters.Cast().ToArray()); + } + + public virtual string FormatCommand(string sql, object[] args) + { + if (sql == null) + return ""; + var sb = new StringBuilder(); + sb.Append(sql); + if (args != null && args.Length > 0) + { + sb.Append("\n"); + for (int i = 0; i < args.Length; i++) + { + var type = args[i] != null ? args[i].GetType().Name : string.Empty; + var value = args[i]; + if (args[i] is FormattedParameter formatted) + { + type = formatted.Type != null ? formatted.Type.Name : string.Format("{0}, {1}", formatted.Parameter.GetType().Name, formatted.Parameter.DbType); + value = formatted.Value; + } + sb.AppendFormat("\t -> {0}{1} [{2}] = \"{3}\"\n", GetParameterPrefix(string.Empty), i, type, value); + } + sb.Remove(sb.Length - 1, 1); + } + return sb.ToString(); + } + } } diff --git a/src/NPoco/ParameterHelper.cs b/src/NPoco/ParameterHelper.cs index bf2fd6e9..1dea50dc 100644 --- a/src/NPoco/ParameterHelper.cs +++ b/src/NPoco/ParameterHelper.cs @@ -1,6 +1,8 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Data; +using System.Data.Common; using System.Linq; using System.Reflection; using System.Text; @@ -133,5 +135,94 @@ private static string ProcessParam(ref string sql, string rawParam, object[] arg return "@" + (args_dest.Count - 1).ToString(); } } + + public static void SetParameterValue(DatabaseType dbType, DbParameter p, object value) + { + if (value == null) + { + p.Value = DBNull.Value; + return; + } + + // Give the database type first crack at converting to DB required type + value = dbType.MapParameterValue(value); + + var dbtypeSet = false; + var t = value.GetType(); + var underlyingT = Nullable.GetUnderlyingType(t); + if (t.GetTypeInfo().IsEnum || (underlyingT != null && underlyingT.GetTypeInfo().IsEnum)) // PostgreSQL .NET driver wont cast enum to int + { + p.Value = (int)value; + } + else if (t == typeof(Guid)) + { + p.Value = value; + p.DbType = DbType.Guid; + p.Size = 40; + dbtypeSet = true; + } + else if (t == typeof(string)) + { + var strValue = value as string; + if (strValue == null) + { + p.Size = 0; + p.Value = DBNull.Value; + } + else + { + // out of memory exception occurs if trying to save more than 4000 characters to SQL Server CE NText column. Set before attempting to set Size, or Size will always max out at 4000 + if (strValue.Length + 1 > 4000 && p.GetType().Name == "SqlCeParameter") + { + p.GetType().GetProperty("SqlDbType").SetValue(p, SqlDbType.NText, null); + } + + p.Size = Math.Max(strValue.Length + 1, 4000); // Help query plan caching by using common size + p.Value = value; + } + } + else if (t == typeof(AnsiString)) + { + var ansistrValue = value as AnsiString; + if (ansistrValue?.Value == null) + { + p.Size = 0; + p.Value = DBNull.Value; + p.DbType = DbType.AnsiString; + } + else + { + // Thanks @DataChomp for pointing out the SQL Server indexing performance hit of using wrong string type on varchar + p.Size = Math.Max(ansistrValue.Value.Length + 1, 4000); + p.Value = ansistrValue.Value; + p.DbType = DbType.AnsiString; + } + dbtypeSet = true; + } + else if (value.GetType().Name == "SqlGeography") //SqlGeography is a CLR Type + { + p.GetType().GetProperty("UdtTypeName").SetValue(p, "geography", null); //geography is the equivalent SQL Server Type + p.Value = value; + } + + else if (value.GetType().Name == "SqlGeometry") //SqlGeometry is a CLR Type + { + p.GetType().GetProperty("UdtTypeName").SetValue(p, "geometry", null); //geography is the equivalent SQL Server Type + p.Value = value; + } + else + { + p.Value = value; + } + + if (!dbtypeSet) + { + var dbTypeLookup = dbType.LookupDbType(p.Value.GetTheType(), p.ParameterName); + if (dbTypeLookup.HasValue) + { + p.DbType = dbTypeLookup.Value; + } + } + } } } diff --git a/test/NPoco.Tests/FormatCommandTest.cs b/test/NPoco.Tests/FormatCommandTest.cs index dc13c410..6d88a28a 100644 --- a/test/NPoco.Tests/FormatCommandTest.cs +++ b/test/NPoco.Tests/FormatCommandTest.cs @@ -1,6 +1,5 @@ using System.Data; using Microsoft.Data.SqlClient; -using NPoco.DatabaseTypes; using NUnit.Framework; namespace NPoco.Tests @@ -33,7 +32,7 @@ public void FormattingWithStringValue() public class MyDb : Database { public MyDb() - : base("test", new SqlServerDatabaseType(), SqlClientFactory.Instance) + : base("test", DatabaseType.MySQL, SqlClientFactory.Instance) { } } diff --git a/test/NPoco.Tests/FormatSqlServerCommandTest.cs b/test/NPoco.Tests/FormatSqlServerCommandTest.cs new file mode 100644 index 00000000..859616b6 --- /dev/null +++ b/test/NPoco.Tests/FormatSqlServerCommandTest.cs @@ -0,0 +1,84 @@ +using System; +using System.Data; +using Microsoft.Data.SqlClient; +using NPoco.DatabaseTypes; +using NUnit.Framework; + +namespace NPoco.Tests +{ + public class FormatSqlServerCommandTest + { + [Test] + public void FormattingWithSqlParameterThatIsNotNull() + { + var cmd = new SqlCommand(); + cmd.Parameters.Add(new SqlParameter("Test", SqlDbType.NVarChar)); + var db = new MyDb(); + Assert.AreEqual("DECLARE @0 NVarChar[4000] = 'value'\n\nsql @0", db.FormatCommand("sql @0", new object[] { "value" })); + Assert.AreEqual("DECLARE @0 Int = '32'\n\nsql @0", db.FormatCommand("sql @0", new object[] { 32 })); + Assert.AreEqual("DECLARE @0 DateTime = '" + DateTime.Today + "'\n\nsql @0", db.FormatCommand("sql @0", new object[] { DateTime.Today })); + + var guid = Guid.NewGuid(); + Assert.AreEqual("DECLARE @0 UniqueIdentifier = '" + guid + "'\n\nsql @0", db.FormatCommand("sql @0", new object[] { guid })); + } + [Test] + public void FormattingWithSqlParameterThatIsNotNullSqlServerDatabase() + { + var cmd = new SqlCommand(); + cmd.Parameters.Add(new SqlParameter("Test", SqlDbType.NVarChar)); + var db = new MyDb2(); + Assert.AreEqual("DECLARE @0 NVarChar[4000] = 'value'\n\nsql @0", db.FormatCommand("sql @0", new object[] { "value" })); + Assert.AreEqual("DECLARE @0 Int = '32'\n\nsql @0", db.FormatCommand("sql @0", new object[] { 32 })); + Assert.AreEqual("DECLARE @0 DateTime = '" + DateTime.Today + "'\n\nsql @0", db.FormatCommand("sql @0", new object[] { DateTime.Today })); + + var guid = Guid.NewGuid(); + Assert.AreEqual("DECLARE @0 UniqueIdentifier = '" + guid + "'\n\nsql @0", db.FormatCommand("sql @0", new object[] { guid })); + } + + + + [Test] + public void FormattingWithNullValue() + { + var db = new MyDb(); + Assert.AreEqual("DECLARE @0 NVarChar = null\n\nsql @0", db.FormatCommand("sql @0", new object[] { null })); + } + [Test] + public void FormattingWithNullValueSqlServerDatabase() + { + var db = new MyDb2(); + Assert.AreEqual("DECLARE @0 NVarChar = null\n\nsql @0", db.FormatCommand("sql @0", new object[] { null })); + } + + [Test] + public void FormattingWithStringValue() + { + var db = new MyDb(); + Assert.AreEqual("DECLARE @0 NVarChar[4000] = 'value'\n\nsql @0", db.FormatCommand("sql @0", new object[] { "value" })); + } + [Test] + public void FormattingWithStringValueSqlServerDatabase() + { + var db = new MyDb2(); + Assert.AreEqual("DECLARE @0 NVarChar[4000] = 'value'\n\nsql @0", db.FormatCommand("sql @0", new object[] { "value" })); + } + + public class MyDb2 : NPoco.SqlServer.SqlServerDatabase + { + public MyDb2() + : base("test") + { + } + } + + public class MyDb : Database + { + public MyDb() + : base("test", new SqlServerDatabaseType(), SqlClientFactory.Instance) + { + } + + + } + } +} From 33be5b84428c8d9b46f399c3638e7f9474a9e119 Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Sat, 24 Oct 2020 00:12:31 +1100 Subject: [PATCH 40/42] Add exact column name matching which doesn't remove underscores to match column names #351 --- src/NPoco/ColumnAttribute.cs | 1 + src/NPoco/ColumnInfo.cs | 2 ++ .../ColumnConfigurationBuilder.cs | 8 ++++++++ src/NPoco/FluentMappings/ColumnDefinition.cs | 1 + .../FluentMappingConfiguration.cs | 1 + .../FluentMappingsPocoDataBuilder.cs | 1 + src/NPoco/PocoColumn.cs | 1 + src/NPoco/PocoDataBuilder.cs | 1 + src/NPoco/RowMappers/PropertyMapper.cs | 6 +++--- .../RowMappers/PropertyMapperNameConvention.cs | 6 +++--- test/NPoco.Tests/NewMapper/NewMapperTests.cs | 17 +++++++++++++++++ 11 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/NPoco/ColumnAttribute.cs b/src/NPoco/ColumnAttribute.cs index dabf73e0..3e6d3852 100644 --- a/src/NPoco/ColumnAttribute.cs +++ b/src/NPoco/ColumnAttribute.cs @@ -9,5 +9,6 @@ public ColumnAttribute() { } public ColumnAttribute(string name) { Name = name; } public string Name { get; set; } public bool ForceToUtc { get; set; } = true; + public bool ExactNameMatch { get; set; } } } \ No newline at end of file diff --git a/src/NPoco/ColumnInfo.cs b/src/NPoco/ColumnInfo.cs index b9801eee..6432d270 100644 --- a/src/NPoco/ColumnInfo.cs +++ b/src/NPoco/ColumnInfo.cs @@ -25,6 +25,7 @@ public class ColumnInfo public ReferenceType ReferenceType { get; set; } public string ReferenceMemberName { get; set; } public MemberInfo MemberInfo { get; internal set; } + public bool ExactColumnNameMatch { get; set; } public static ColumnInfo FromMemberInfo(MemberInfo mi) { @@ -84,6 +85,7 @@ public static ColumnInfo FromMemberInfo(MemberInfo mi) { ci.ColumnName = colAttrs.FirstOrDefault(x => !string.IsNullOrEmpty(x.Name))?.Name ?? mi.Name; ci.ForceToUtc = colAttrs.All(x => x.ForceToUtc); + ci.ExactColumnNameMatch = colAttrs.All(x => x.ExactNameMatch); var resultAttr = colAttrs.OfType().FirstOrDefault(); ci.ResultColumn = resultAttr != null; diff --git a/src/NPoco/FluentMappings/ColumnConfigurationBuilder.cs b/src/NPoco/FluentMappings/ColumnConfigurationBuilder.cs index 436adcd0..efc5a60e 100644 --- a/src/NPoco/FluentMappings/ColumnConfigurationBuilder.cs +++ b/src/NPoco/FluentMappings/ColumnConfigurationBuilder.cs @@ -101,6 +101,7 @@ public IManyColumnBuilder Reference(Expression> mem public interface IColumnBuilder { IColumnBuilder WithName(string name); + IColumnBuilder WithName(string name, bool exactMatch); IColumnBuilder WithAlias(string alias); IColumnBuilder WithDbType(Type type); IColumnBuilder WithDbType(); @@ -134,6 +135,13 @@ public IColumnBuilder WithName(string name) return this; } + public IColumnBuilder WithName(string name, bool exactMatch) + { + _columnDefinition.DbColumnName = name; + _columnDefinition.ExactColumnNameMatch = exactMatch; + return this; + } + public IColumnBuilder WithAlias(string alias) { _columnDefinition.DbColumnAlias = alias; diff --git a/src/NPoco/FluentMappings/ColumnDefinition.cs b/src/NPoco/FluentMappings/ColumnDefinition.cs index 72295b2b..3a9b064e 100644 --- a/src/NPoco/FluentMappings/ColumnDefinition.cs +++ b/src/NPoco/FluentMappings/ColumnDefinition.cs @@ -25,5 +25,6 @@ public class ColumnDefinition public string ComplexPrefix { get; set; } public bool? ValueObjectColumn { get; set; } public string ValueObjectColumnName { get; set; } + public bool? ExactColumnNameMatch { get; set; } } } \ No newline at end of file diff --git a/src/NPoco/FluentMappings/FluentMappingConfiguration.cs b/src/NPoco/FluentMappings/FluentMappingConfiguration.cs index bff825f5..7e5e3fc0 100644 --- a/src/NPoco/FluentMappings/FluentMappingConfiguration.cs +++ b/src/NPoco/FluentMappings/FluentMappingConfiguration.cs @@ -277,6 +277,7 @@ private static void MergeOverrides(Dictionary config, Mapp convColDefinition.IsComplexMapping = overrideColumnDefinition.Value.IsComplexMapping ?? convColDefinition.IsComplexMapping; convColDefinition.ValueObjectColumn = overrideColumnDefinition.Value.ValueObjectColumn ?? convColDefinition.ValueObjectColumn; convColDefinition.ValueObjectColumnName = overrideColumnDefinition.Value.ValueObjectColumnName ?? convColDefinition.ValueObjectColumnName; + convColDefinition.ExactColumnNameMatch = overrideColumnDefinition.Value.ExactColumnNameMatch ?? convColDefinition.ExactColumnNameMatch; } } } diff --git a/src/NPoco/FluentMappings/FluentMappingsPocoDataBuilder.cs b/src/NPoco/FluentMappings/FluentMappingsPocoDataBuilder.cs index 4689b9b9..cf4b7bc4 100644 --- a/src/NPoco/FluentMappings/FluentMappingsPocoDataBuilder.cs +++ b/src/NPoco/FluentMappings/FluentMappingsPocoDataBuilder.cs @@ -121,6 +121,7 @@ protected override ColumnInfo GetColumnInfo(MemberInfo mi, Type type) { var colattr = typeConfig.ColumnConfiguration[key]; columnInfo.ColumnName = colattr.DbColumnName; + columnInfo.ExactColumnNameMatch = colattr.ExactColumnNameMatch ?? false; columnInfo.ColumnAlias = colattr.DbColumnAlias; if (colattr.ResultColumn.HasValue && colattr.ResultColumn.Value) { diff --git a/src/NPoco/PocoColumn.cs b/src/NPoco/PocoColumn.cs index 5f2e0180..92985585 100644 --- a/src/NPoco/PocoColumn.cs +++ b/src/NPoco/PocoColumn.cs @@ -53,6 +53,7 @@ public Type ColumnType public bool SerializedColumn { get; set; } public bool ValueObjectColumn { get; set; } public string ValueObjectColumnName { get; set; } + public bool ExactColumnNameMatch { get; set; } internal void SetMemberAccessors(List memberAccessors) { diff --git a/src/NPoco/PocoDataBuilder.cs b/src/NPoco/PocoDataBuilder.cs index 01d85055..738a97e1 100644 --- a/src/NPoco/PocoDataBuilder.cs +++ b/src/NPoco/PocoDataBuilder.cs @@ -187,6 +187,7 @@ public IEnumerable GetPocoMembers(ColumnInfo[] columnInfos, List MemberInfoChain = members, ColumnName = columnName, ResultColumn = capturedColumnInfo.ResultColumn, + ExactColumnNameMatch = capturedColumnInfo.ExactColumnNameMatch, ForceToUtc = capturedColumnInfo.ForceToUtc, ComputedColumn = capturedColumnInfo.ComputedColumn, ComputedColumnType = capturedColumnInfo.ComputedColumnType, diff --git a/src/NPoco/RowMappers/PropertyMapper.cs b/src/NPoco/RowMappers/PropertyMapper.cs index 3f34d4ca..a50fe5ce 100644 --- a/src/NPoco/RowMappers/PropertyMapper.cs +++ b/src/NPoco/RowMappers/PropertyMapper.cs @@ -74,7 +74,7 @@ private MapPlan BuildMapPlan(DbDataReader dataReader, PocoData pocoData) private IEnumerable BuildMapPlans(GroupResult groupedName, DbDataReader dataReader, PocoData pocoData, List pocoMembers) { // find pocomember by property name - var pocoMember = pocoMembers.FirstOrDefault(x => IsEqual(groupedName.Item, x.Name) + var pocoMember = pocoMembers.FirstOrDefault(x => IsEqual(groupedName.Item, x.Name, x.PocoColumn?.ExactColumnNameMatch ?? false) || string.Equals(groupedName.Item, x.PocoColumn?.ColumnAlias, StringComparison.OrdinalIgnoreCase)); if (pocoMember == null) @@ -128,13 +128,13 @@ private IEnumerable BuildMapPlans(GroupResult groupedName, DbD } } - public static bool IsEqual(string name, string value) + public static bool IsEqual(string name, string value, bool exactMatch) { if (value is null) return false; return string.Equals(value, name, StringComparison.OrdinalIgnoreCase) - || string.Equals(value, name.Replace("_", ""), StringComparison.OrdinalIgnoreCase); + || (!exactMatch && string.Equals(value, name.Replace("_", ""), StringComparison.OrdinalIgnoreCase)); } private bool MapValue(GroupResult posName, object[] values, Func converter, object instance, PocoColumn pocoColumn, object defaultValue) diff --git a/src/NPoco/RowMappers/PropertyMapperNameConvention.cs b/src/NPoco/RowMappers/PropertyMapperNameConvention.cs index 89165cf4..cfd37196 100644 --- a/src/NPoco/RowMappers/PropertyMapperNameConvention.cs +++ b/src/NPoco/RowMappers/PropertyMapperNameConvention.cs @@ -89,15 +89,15 @@ internal static PocoMember FindMember(IEnumerable pocoMembers, strin private static bool IsPocoMemberEqual(PocoMember pocoMember, string name) { if (pocoMember.PocoColumn == null) - return PropertyMapper.IsEqual(name, pocoMember.Name); + return PropertyMapper.IsEqual(name, pocoMember.Name, false); if (pocoMember.PocoColumn.MemberInfoKey == name) return true; - if (PropertyMapper.IsEqual(name, pocoMember.PocoColumn.ColumnAlias ?? pocoMember.PocoColumn.ColumnName)) + if (PropertyMapper.IsEqual(name, pocoMember.PocoColumn.ColumnAlias ?? pocoMember.PocoColumn.ColumnName, pocoMember.PocoColumn.ExactColumnNameMatch)) return true; - return PropertyMapper.IsEqual(name, pocoMember.Name); + return PropertyMapper.IsEqual(name, pocoMember.Name, pocoMember.PocoColumn.ExactColumnNameMatch); } private static IEnumerable FlattenPocoMembers(List pocoMembers, int levelMonitor = 1) diff --git a/test/NPoco.Tests/NewMapper/NewMapperTests.cs b/test/NPoco.Tests/NewMapper/NewMapperTests.cs index b5df2767..29523c38 100644 --- a/test/NPoco.Tests/NewMapper/NewMapperTests.cs +++ b/test/NPoco.Tests/NewMapper/NewMapperTests.cs @@ -745,6 +745,23 @@ public class Result36 public string GetData() => Data; public string GetData1() => Data1; } + + [Test] + public void ExactMatchTests() + { + var result = Database.Single("select 'Test' MyColumn, 'Test2' My_Column /*poco_dual*/"); + Assert.AreEqual("Test", result.MyColumn); + Assert.AreEqual("Test2", result.My_Column); + } + + public class ExactMatchTestsDto + { + [Column(ExactNameMatch = true)] + public string MyColumn { get; set; } + + [Column(ExactNameMatch = true)] + public string My_Column { get; set; } + } } public class NoPrimaryKey From cf50d3126fc23ac8ffde33fe22b9ed486d17267d Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Thu, 12 Nov 2020 22:25:15 +1100 Subject: [PATCH 41/42] Add helpers for concatenating Sql --- src/NPoco/Database.cs | 22 ++++++++++++++++++++++ src/NPoco/Linq/SimpleQueryProvider.cs | 12 +++++++++++- src/NPoco/Sql.cs | 7 +++++++ test/NPoco.Tests/SqlBuilderTests.cs | 11 +++++++++++ 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/NPoco/Database.cs b/src/NPoco/Database.cs index 394b4f9e..a00fbe7e 100644 --- a/src/NPoco/Database.cs +++ b/src/NPoco/Database.cs @@ -914,6 +914,28 @@ public IQueryProviderWithIncludes Query() return new QueryProvider(this); } + public (List, List, List, List) QueryMultiple( + Func, IQueryProvider> query1, + Func, IQueryProvider> query2, + Func, IQueryProvider> query3, + Func, IQueryProvider> query4 + ) + { + var qp1 = new QueryProvider(this); + var qp2 = new QueryProvider(this); + var qp3 = new QueryProvider(this); + var qp4 = new QueryProvider(this); + query1.Invoke(qp1); + query2.Invoke(qp2); + query3.Invoke(qp3); + query4.Invoke(qp4); + var sql1 = ((INeedSql)qp1).GetSql(); + var sql2 = ((INeedSql)qp2).GetSql(); + var sql3 = ((INeedSql)qp3).GetSql(); + var sql4 = ((INeedSql)qp4).GetSql(); + return FetchMultiple(sql1.Concat(sql2, ";").Concat(sql3, ";").Concat(sql4, ";")); + } + private IEnumerable Query(T instance, Sql Sql) { return QueryImp(instance, null, null, Sql); diff --git a/src/NPoco/Linq/SimpleQueryProvider.cs b/src/NPoco/Linq/SimpleQueryProvider.cs index e2bf99fa..1628e4d9 100644 --- a/src/NPoco/Linq/SimpleQueryProvider.cs +++ b/src/NPoco/Linq/SimpleQueryProvider.cs @@ -123,7 +123,7 @@ public interface IQueryProviderWithIncludes : IQueryProvider IQueryProviderWithIncludes UsingAlias(string empty); } - public class AsyncQueryProvider : IAsyncQueryProviderWithIncludes, ISimpleQueryProviderExpression, INeedDatabase + public class AsyncQueryProvider : IAsyncQueryProviderWithIncludes, ISimpleQueryProviderExpression, INeedDatabase, INeedSql { protected readonly Database _database; protected SqlExpression _sqlExpression; @@ -473,6 +473,16 @@ IDatabase INeedDatabase.GetDatabase() { return _database; } + + Sql INeedSql.GetSql() + { + return BuildSql(); + } + } + + public interface INeedSql + { + Sql GetSql(); } public interface INeedDatabase diff --git a/src/NPoco/Sql.cs b/src/NPoco/Sql.cs index b4bb8e1c..3253c5ed 100644 --- a/src/NPoco/Sql.cs +++ b/src/NPoco/Sql.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; namespace NPoco @@ -90,6 +91,12 @@ public Sql Append(Sql sql) return this; } + public Sql Concat(Sql sql, string delimiter) + { + sql._sql = delimiter + sql._sql; + return Append(sql); + } + public Sql Append(string sql, params object[] args) { Append(new Sql(sql, args)); diff --git a/test/NPoco.Tests/SqlBuilderTests.cs b/test/NPoco.Tests/SqlBuilderTests.cs index 6655f5ab..a9d9cfeb 100644 --- a/test/NPoco.Tests/SqlBuilderTests.cs +++ b/test/NPoco.Tests/SqlBuilderTests.cs @@ -191,5 +191,16 @@ public void Test14() Assert.AreEqual("SELECT 1 , COUNT(*) FROM test ", template.RawSql); } + + [Test] + public void Test15() + { + var sql1 = new Sql("select a from test1 where a = @0", 1); + var sql2 = new Sql("select b from test2 where b = @0", 2); + var sql = sql1.Concat(sql2, ";"); + Assert.AreEqual("select a from test1 where a = @0\n;select b from test2 where b = @1", sql.SQL); + Assert.AreEqual(1, sql.Arguments[0]); + Assert.AreEqual(2, sql.Arguments[1]); + } } } From 01b9f8aaa1d9d248e7c2878c8cacbafd52ea46a7 Mon Sep 17 00:00:00 2001 From: Adam Schroder Date: Fri, 13 Nov 2020 09:13:28 +1100 Subject: [PATCH 42/42] Update to net5.0 and all packages --- src/NPoco.JsonNet/NPoco.JsonNet.csproj | 2 +- src/NPoco.SqlServer/NPoco.SqlServer.csproj | 2 +- src/NPoco/NPoco.csproj | 7 ++++--- .../FluentTests/QueryTests/QueryProviderTests.cs | 4 ++-- test/NPoco.Tests/NPoco.Tests.csproj | 10 +++++----- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/NPoco.JsonNet/NPoco.JsonNet.csproj b/src/NPoco.JsonNet/NPoco.JsonNet.csproj index d10c21d9..429714ea 100644 --- a/src/NPoco.JsonNet/NPoco.JsonNet.csproj +++ b/src/NPoco.JsonNet/NPoco.JsonNet.csproj @@ -3,7 +3,7 @@ Provides an implementation to use Json.NET as the serializer for serialized columns 5.0.0 - netstandard2.0;netstandard2.1 + net461;netstandard2.0;netstandard2.1 NPoco.JsonNet NPoco.JsonNet orm;sql;micro-orm;database;mvc diff --git a/src/NPoco.SqlServer/NPoco.SqlServer.csproj b/src/NPoco.SqlServer/NPoco.SqlServer.csproj index b194eef5..1df466e1 100644 --- a/src/NPoco.SqlServer/NPoco.SqlServer.csproj +++ b/src/NPoco.SqlServer/NPoco.SqlServer.csproj @@ -3,7 +3,7 @@ An extremely easy to use Micro-ORM supporting Sql Server. 5.0.0 - netstandard2.0;netstandard2.1 + net461;netstandard2.0;netstandard2.1 NPoco.SqlServer NPoco.SqlServer Adam Schröder diff --git a/src/NPoco/NPoco.csproj b/src/NPoco/NPoco.csproj index 8d197b81..4b5caf72 100644 --- a/src/NPoco/NPoco.csproj +++ b/src/NPoco/NPoco.csproj @@ -2,7 +2,7 @@ An extremely easy to use Micro-ORM supporting Sql Server, MySQL, PostgreSQL, Oracle, Sqlite, SqlCE. - netstandard2.0;netstandard2.1 + net461;netstandard2.0;netstandard2.1 NPoco NPoco 5.0.0 @@ -21,8 +21,9 @@ - - + + + diff --git a/test/NPoco.Tests/FluentTests/QueryTests/QueryProviderTests.cs b/test/NPoco.Tests/FluentTests/QueryTests/QueryProviderTests.cs index 1334faa3..82851c68 100644 --- a/test/NPoco.Tests/FluentTests/QueryTests/QueryProviderTests.cs +++ b/test/NPoco.Tests/FluentTests/QueryTests/QueryProviderTests.cs @@ -357,7 +357,7 @@ public void QueryWithProjectionAndEnclosedMethod() SetCurrentCulture(new CultureInfo("en-US")); var users = Database.Query() - .ProjectTo(x => new ProjectUser2 { FormattedAge = string.Format("{0:n}", x.Age) }); + .ProjectTo(x => new ProjectUser2 { FormattedAge = x.Age.ToString("n2") }); Assert.AreEqual("21.00", users[0].FormattedAge); Assert.AreEqual(15, users.Count); @@ -384,7 +384,7 @@ public void QueryWithProjectionAndEnclosedMethod1() // these arguments are properly supported (ProcessMethodSearchRecursively supports // NewArrayExpression). var users = Database.Query() - .ProjectTo(x => new ProjectUser2 { FormattedAge = string.Format("{0:n} {1:n} {2:n} {3:n} {4:n} {5:n} {6:n}", + .ProjectTo(x => new ProjectUser2 { FormattedAge = string.Format("{0:n2} {1:n2} {2:n2} {3:n2} {4:n2} {5:n2} {6:n2}", x.Age, x.Age, x.Age, x.Age, x.Age, x.Age, x.Age) }); Assert.AreEqual("21.00 21.00 21.00 21.00 21.00 21.00 21.00", users[0].FormattedAge); diff --git a/test/NPoco.Tests/NPoco.Tests.csproj b/test/NPoco.Tests/NPoco.Tests.csproj index 7a77c6dc..ecda6821 100644 --- a/test/NPoco.Tests/NPoco.Tests.csproj +++ b/test/NPoco.Tests/NPoco.Tests.csproj @@ -3,7 +3,7 @@ NPoco Tests 5.0.0 - netcoreapp3.1 + net5.0 NPoco.Tests Library NPoco.Tests @@ -22,12 +22,12 @@ - + - - - + + +