diff --git a/NPoco.sln b/NPoco.sln index 13e39bd5..334dee97 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("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "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..429714ea 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 + net461;netstandard2.0;netstandard2.1 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 @@ -23,36 +22,7 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 82% rename from src/NPoco/DatabaseTypes/SqlServer2012DatabaseType.cs rename to src/NPoco.SqlServer/DatabaseTypes/SqlServer2012DatabaseType.cs index 47c441c4..3839127b 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 @@ -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/DatabaseTypes/SqlServerCEDatabaseType.cs b/src/NPoco.SqlServer/DatabaseTypes/SqlServerCEDatabaseType.cs similarity index 76% rename from src/NPoco/DatabaseTypes/SqlServerCEDatabaseType.cs rename to src/NPoco.SqlServer/DatabaseTypes/SqlServerCEDatabaseType.cs index 822824a4..071ae8fe 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;"); + await db.ExecuteNonQueryHelperAsync(cmd).ConfigureAwait(false); + return await db.ExecuteScalarAsync("SELECT @@@IDENTITY AS NewID;").ConfigureAwait(false); } -#endif public override IsolationLevel GetDefaultTransactionIsolationLevel() { diff --git a/src/NPoco/DatabaseTypes/SqlServerDatabaseType.cs b/src/NPoco.SqlServer/DatabaseTypes/SqlServerDatabaseType.cs similarity index 54% rename from src/NPoco/DatabaseTypes/SqlServerDatabaseType.cs rename to src/NPoco.SqlServer/DatabaseTypes/SqlServerDatabaseType.cs index e8191822..68659430 100644 --- a/src/NPoco/DatabaseTypes/SqlServerDatabaseType.cs +++ b/src/NPoco.SqlServer/DatabaseTypes/SqlServerDatabaseType.cs @@ -1,10 +1,12 @@ +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.Text; +using System.Threading.Tasks; +using NPoco.SqlServer; namespace NPoco.DatabaseTypes { @@ -12,8 +14,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 +21,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 +52,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; @@ -90,9 +76,19 @@ public override IsolationLevel GetDefaultTransactionIsolationLevel() return base.LookupDbType(type, name); } + public override void InsertBulk(IDatabase db, IEnumerable pocos, InsertBulkOptions? options) + { + SqlBulkCopyHelper.BulkInsert(db, pocos, options); + } + + public override Task InsertBulkAsync(IDatabase db, IEnumerable pocos, InsertBulkOptions options) + { + return SqlBulkCopyHelper.BulkInsertAsync(db, pocos, options); + } + public override string GetProviderName() { - return "System.Data.SqlClient"; + return "Microsoft.Data.SqlClient"; } public override object ProcessDefaultMappings(PocoColumn pocoColumn, object value) @@ -103,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/DefaultPollyPolicy.cs b/src/NPoco.SqlServer/DefaultPollyPolicy.cs new file mode 100644 index 00000000..24ea699c --- /dev/null +++ b/src/NPoco.SqlServer/DefaultPollyPolicy.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using Microsoft.Data.SqlClient; +using Polly; +using Polly.Retry; + +namespace NPoco.SqlServer +{ + 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(3), + TimeSpan.FromSeconds(5) + }; + + 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 new file mode 100644 index 00000000..1df466e1 --- /dev/null +++ b/src/NPoco.SqlServer/NPoco.SqlServer.csproj @@ -0,0 +1,25 @@ + + + + An extremely easy to use Micro-ORM supporting Sql Server. + 5.0.0 + net461;netstandard2.0;netstandard2.1 + NPoco.SqlServer + NPoco.SqlServer + Adam Schröder + orm;sql;micro-orm;database;mvc + https://github.com/schotime/NPoco + 8.0 + enable + + + + + + + + + + + + diff --git a/src/NPoco/SqlBulkCopyHelper.cs b/src/NPoco.SqlServer/SqlBulkCopyHelper.cs similarity index 68% rename from src/NPoco/SqlBulkCopyHelper.cs rename to src/NPoco.SqlServer/SqlBulkCopyHelper.cs index 0a0480ca..0f2e58d2 100644 --- a/src/NPoco/SqlBulkCopyHelper.cs +++ b/src/NPoco.SqlServer/SqlBulkCopyHelper.cs @@ -2,47 +2,56 @@ using System.Collections.Generic; using System.Data; using System.Data.Common; -using System.Data.SqlClient; +using System.Diagnostics; using System.Linq; +using System.Threading.Tasks; +using Microsoft.Data.SqlClient; -namespace NPoco +namespace NPoco.SqlServer { -#if !DNXCORE50 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); } } -#if NET45 || NETSTANDARD20 - public static async System.Threading.Tasks.Task BulkInsertAsync(IDatabase db, IEnumerable list, SqlBulkCopyOptions sqlBulkCopyOptions) + + public static Task BulkInsertAsync(IDatabase db, IEnumerable list, InsertBulkOptions sqlBulkCopyOptions) + { + return BulkInsertAsync(db, list, SqlBulkCopyOptions.Default, 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); } } -#endif - 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; + bulkCopy.DestinationTableName = db.DatabaseType.EscapeTableName(pocoData.TableInfo.TableName); + + if (insertBulkOptions?.BulkCopyTimeout != null) + bulkCopy.BulkCopyTimeout = insertBulkOptions.BulkCopyTimeout.Value; var table = new DataTable(); var cols = pocoData.Columns.Where(x => @@ -61,8 +70,8 @@ private static DataTable BuildBulkInsertDataTable(IDatabase db, IEnumerable(IDatabase db, IEnumerable x.GetToDbConverter(cols[i].Value.ColumnType, cols[i].Value.MemberInfoData.MemberInfo), value); @@ -97,5 +106,4 @@ private static DataTable BuildBulkInsertDataTable(IDatabase db, IEnumerable.Instance, pollyPolicy) + { + } + + 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 Task ExecutionHookAsync(Func> action) + { + if (_pollyPolicy?.AsyncRetryPolicy != null) + { + return await _pollyPolicy.AsyncRetryPolicy.ExecuteAsync(action).ConfigureAwait(false); + } + + return await base.ExecutionHookAsync(action).ConfigureAwait(false); + } + } +} 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 8d34f092..3809d328 100644 --- a/src/NPoco/AsyncDatabase.cs +++ b/src/NPoco/AsyncDatabase.cs @@ -1,5 +1,4 @@ -#if !NET35 && !NET40 -using System.Collections; +using System.Collections; using System.Linq.Expressions; using NPoco.Expressions; using System; @@ -55,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; @@ -77,12 +76,18 @@ private async Task InsertAsyncImp(PocoData pocoData, string tableName object id; if (!autoIncrement) { - await ExecuteNonQueryHelperAsync(cmd); + _ = 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()); + 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); } @@ -100,7 +105,30 @@ private async Task InsertAsyncImp(PocoData pocoData, string tableName } } - public async Task InsertBatchAsync(IEnumerable pocos, BatchOptions options = null) + public async Task InsertBulkAsync(IEnumerable pocos, InsertBulkOptions options = null) + { + try + { + OpenSharedConnectionInternal(); + await _dbType.InsertBulkAsync(this, pocos, options).ConfigureAwait(false); + } + catch (Exception x) + { + OnExceptionInternal(x); + throw; + } + finally + { + CloseSharedConnectionInternal(); + } + } + + 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; @@ -126,7 +154,9 @@ public async Task InsertBatchAsync(IEnumerable pocos, BatchOptions op using (var cmd = CreateCommand(_sharedConnection, sql.SQL, sql.Arguments)) { - result += await ExecuteNonQueryHelperAsync(cmd); + result += sync + ? ExecuteNonQueryHelper(cmd) + : await ExecuteNonQueryHelperAsync(cmd).ConfigureAwait(false); } } } @@ -170,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)), TaskAsyncHelper.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; @@ -188,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(); @@ -203,7 +237,9 @@ public async Task UpdateBatchAsync(IEnumerable> pocos, Ba using (var cmd = CreateCommand(_sharedConnection, sql.SQL, sql.Arguments)) { - result += await ExecuteNonQueryHelperAsync(cmd); + result += sync + ? ExecuteNonQueryHelper(cmd) + : await ExecuteNonQueryHelperAsync(cmd).ConfigureAwait(false); } } } @@ -238,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, ExecuteAsync, TaskAsyncHelper.FromResult(0)); + return DeleteImpAsync(tableName, primaryKeyName, poco, primaryKeyValue, false); } public IAsyncDeleteQueryProvider DeleteManyAsync() @@ -253,12 +289,7 @@ public Task> PageAsync(long page, long itemsPerPage, Sql sql) public Task> PageAsync(long page, long itemsPerPage, string sql, params object[] args) { - return PageImp>>(page, itemsPerPage, sql, args, - async (paged, thesql) => - { - paged.Items = (await QueryAsync(thesql).ConfigureAwait(false)).ToList(); - return paged; - }); + return PageImpAsync(page, itemsPerPage, sql, args, false); } public Task> FetchAsync(long page, long itemsPerPage, string sql, params object[] args) @@ -283,38 +314,101 @@ public Task> 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).ConfigureAwait(false)) + { + 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 sql = GetExistsSql(poco, true); + var result = sync + ? ExecuteScalar(sql) + : await ExecuteScalarAsync(sql).ConfigureAwait(false); + 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, 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(); + return new Sql(sql, args); + } + 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).ConfigureAwait(false)).ToList(); + return QueryAsync(sql, args).ToListAsync().AsTask(); } - public async Task> FetchAsync(Sql sql) + public Task> FetchAsync(Sql sql) { - return (await QueryAsync(sql).ConfigureAwait(false)).ToList(); + 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); } + 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); } - 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; @@ -323,78 +417,70 @@ internal async Task> QueryAsync(T instance, Expression(instance, reader, pocoData)); + await foreach (var item in read) { - r = await ExecuteReaderHelperAsync(cmd).ConfigureAwait(false); + yield return item; } - catch (Exception x) - { - cmd.Dispose(); - OnExceptionInternal(x); - throw; - } - - return listExpression != null ? ReadOneToMany(instance, r, cmd, listExpression, idFunc) : Read(typeof(T), instance, r, cmd); } - catch + finally { CloseSharedConnectionInternal(); - throw; } } - public async Task SingleAsync(string sql, params object[] args) + public Task SingleAsync(string sql, params object[] args) { - return (await QueryAsync(sql, args).ConfigureAwait(false)).Single(); + return QueryAsync(sql, args).SingleAsync().AsTask(); } - public async Task SingleAsync(Sql sql) + public Task SingleAsync(Sql sql) { - return (await QueryAsync(sql).ConfigureAwait(false)).Single(); + 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).ConfigureAwait(false)).SingleOrDefault(); + return QueryAsync(sql, args).SingleOrDefaultAsync().AsTask(); } - public async Task SingleOrDefaultAsync(Sql sql) + public Task SingleOrDefaultAsync(Sql sql) { - return (await QueryAsync(sql).ConfigureAwait(false)).SingleOrDefault(); + return QueryAsync(sql).SingleOrDefaultAsync().AsTask(); } - public async Task SingleByIdAsync(object primaryKey) + public Task SingleByIdAsync(object primaryKey) { var sql = GenerateSingleByIdSql(primaryKey); - return (await QueryAsync(sql).ConfigureAwait(false)).Single(); + return QueryAsync(sql).SingleAsync().AsTask(); } - public async Task SingleOrDefaultByIdAsync(object primaryKey) + public Task SingleOrDefaultByIdAsync(object primaryKey) { var sql = GenerateSingleByIdSql(primaryKey); - return (await QueryAsync(sql).ConfigureAwait(false)).SingleOrDefault(); + 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, args).ConfigureAwait(false)).First(); + return QueryAsync(sql).FirstAsync().AsTask(); } - public async Task FirstAsync(Sql sql) + public Task FirstAsync(Sql sql) { - return (await QueryAsync(sql).ConfigureAwait(false)).First(); + 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, args).ConfigureAwait(false)).FirstOrDefault(); + return QueryAsync(sql).FirstOrDefaultAsync().AsTask(); } - public async Task FirstOrDefaultAsync(Sql sql) + public Task FirstOrDefaultAsync(Sql sql) { - return (await QueryAsync(sql).ConfigureAwait(false)).FirstOrDefault(); + return QueryAsync(sql).FirstOrDefaultAsync().AsTask(); } public Task ExecuteAsync(string sql, params object[] args) @@ -445,7 +531,7 @@ public async Task ExecuteScalarAsync(Sql Sql) object val = await ExecuteScalarHelperAsync(cmd).ConfigureAwait(false); if (val == null || val == DBNull.Value) - return await TaskAsyncHelper.FromResult(default(T)).ConfigureAwait(false); + return default(T); Type t = typeof(T); Type u = Nullable.GetUnderlyingType(t); @@ -467,7 +553,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 ExecutionHookAsync(() => _dbType.ExecuteNonQueryAsync(this, cmd)).ConfigureAwait(false); OnExecutedCommandInternal(cmd); return result; } @@ -475,7 +561,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 ExecutionHookAsync(() => _dbType.ExecuteScalarAsync(this, cmd)).ConfigureAwait(false); OnExecutedCommandInternal(cmd); return result; } @@ -483,21 +569,9 @@ 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 ExecutionHookAsync(() => _dbType.ExecuteReaderAsync(this, cmd)).ConfigureAwait(false); OnExecutedCommandInternal(cmd); return reader; } } - - public class TaskAsyncHelper - { - public static Task FromResult(T value) - { - var tcs = new TaskCompletionSource(); - tcs.SetResult(value); - return tcs.Task; - } - } } - -#endif 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/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/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/Database.cs b/src/NPoco/Database.cs index 0be25245..a00fbe7e 100644 --- a/src/NPoco/Database.cs +++ b/src/NPoco/Database.cs @@ -1,30 +1,27 @@ -/* 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 */ +#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; using System.Text; +using System.Threading.Tasks; using NPoco.Expressions; using NPoco.Extensions; using NPoco.Linq; -#if !DNXCORE50 -using System.Configuration; -#endif namespace NPoco { @@ -36,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; @@ -54,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 @@ -66,57 +63,8 @@ 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) + : this(connectionString, databaseType, provider, null) { } public Database(string connectionString, DatabaseType databaseType, DbProviderFactory provider, IsolationLevel? isolationLevel = null, bool enableAutoSelect = DefaultEnableAutoSelect) @@ -124,66 +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); } -#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; } } + 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() @@ -219,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; @@ -266,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()) @@ -317,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()) @@ -450,9 +343,7 @@ public void CompleteTransaction() if (TransactionIsOk()) _transaction.Commit(); - if (_transaction != null) - _transaction.Dispose(); - + _transaction?.Dispose(); _transaction = null; OnCompleteTransactionInternal(); @@ -471,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) @@ -491,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) @@ -606,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) @@ -730,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(); @@ -749,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); } } @@ -809,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); @@ -825,7 +627,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); @@ -918,7 +720,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) { @@ -930,12 +732,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; @@ -949,111 +751,161 @@ public IEnumerable Query(string sql, params object[] args) public IEnumerable Query(Sql Sql) { - return Query(default(T), Sql); + return Query(default(T)!, Sql); } - private IEnumerable Read(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 (!r.Read()) yield break; - poco = (T)factory.Map(r, instance); - } - catch (Exception x) - { - OnExceptionInternal(x); - throw; - } + if (!await r.ReadAsync().ConfigureAwait(false)) yield break; + poco = (T)factory.Map(r, instance); + } + catch (Exception x) + { + OnExceptionInternal(x); + throw; + } - yield return poco; - } + yield return poco; + } + } + + private async IAsyncEnumerable ReadOneToManyAsync(T instance, DbDataReader r, Expression> listExpression, Func idFunc, PocoData pocoData) + { + Func? listFunc = null; + PocoMember? pocoMember = null; + PocoMember? foreignMember = null; + + if (listExpression != null) + { + 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?.PocoMemberChildren.FirstOrDefault(x => x.Name == pocoMember.ReferenceMemberName && x.ReferenceType == ReferenceType.Foreign); + } + + 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; + } + + 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 ReadOneToMany(T instance, DbDataReader r, DbCommand cmd, Expression> listExpression, Func idFunc) + private IEnumerable Read(object? instance, DbDataReader r, PocoData pd) { - Func listFunc = null; - PocoMember pocoMember = null; - PocoMember foreignMember = null; - - try + var factory = new MappingFactory(pd, r); + while (true) { - using (cmd) + T poco; + try { - 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; - } + if (!r.Read()) yield break; + poco = (T)factory.Map(r, instance); + } + catch (Exception x) + { + OnExceptionInternal(x); + throw; + } - var factory = new MappingFactory(pocoData, r); - object prevPoco = null; + yield return poco; + } + } - while (true) - { - T poco; - try - { - if (!r.Read()) break; - poco = (T)factory.Map(r, instance); - } - catch (Exception x) - { - OnExceptionInternal(x); - throw; - } + private IEnumerable ReadOneToMany(T instance, DbDataReader r, Expression> listExpression, Func? idFunc, PocoData pocoData) + { + Func? listFunc = null; + PocoMember? pocoMember = null; + PocoMember? foreignMember = 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; - } + if (listExpression != null) + { + 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?.PocoMemberChildren.FirstOrDefault(x => x.Name == pocoMember.ReferenceMemberName && x.ReferenceType == ReferenceType.Foreign); + } - prevPoco = poco; - } + var factory = new MappingFactory(pocoData, r); + object? prevPoco = null; - if (prevPoco != null) - { - OneToManyHelper.SetForeignList(listFunc, foreignMember, prevPoco); - yield return (T)prevPoco; - } + while (true) + { + T poco; + try + { + if (!r.Read()) break; + poco = (T)factory.Map(r, instance); + } + catch (Exception x) + { + OnExceptionInternal(x); + throw; + } + + if (prevPoco != null) + { + if (idFunc != null + && 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; } } @@ -1062,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); @@ -1092,19 +966,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); + using var reader = ExecuteDataReader(cmd, true).RunSync(); + var read = Read(null, reader, PocoDataFactory.ForType(type)); foreach (var item in read) { yield return item; @@ -1116,34 +980,25 @@ 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; - if (EnableAutoSelect) sql = AutoSelectHelper.AddSelectClause(this, typeof (T), sql); + if (EnableAutoSelect) sql = AutoSelectHelper.AddSelectClause(this, typeof(T), sql); 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); + 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; } - } finally { @@ -1151,12 +1006,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) { @@ -1168,7 +1023,7 @@ private DbDataReader ExecuteDataReader(DbCommand cmd) 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) @@ -1178,7 +1033,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) @@ -1188,15 +1043,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) { @@ -1224,27 +1075,31 @@ 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)).ConfigureAwait(false); + + 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; @@ -1252,70 +1107,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 { @@ -1323,20 +1174,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) @@ -1354,7 +1194,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(); @@ -1429,6 +1269,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); } @@ -1444,100 +1285,20 @@ 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); + return InsertAsyncImp(pd, tableName, primaryKeyName, autoIncrement, poco, true).RunSync(); } - private object InsertImp(PocoData pocoData, string tableName, string primaryKeyName, bool autoIncrement, T poco) + public int InsertBatch(IEnumerable pocos, BatchOptions? options = null) { - 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 InsertBatchAsyncImp(pocos, options, true).RunSync(); } - public int InsertBatch(IEnumerable pocos, BatchOptions options = null) + public void InsertBulk(IEnumerable pocos, InsertBulkOptions? 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; - } - - public void InsertBulk(IEnumerable pocos) - { - try - { - OpenSharedConnectionInternal(); - _dbType.InsertBulk(this, pocos); + _dbType.InsertBulk(this, pocos, options); } catch (Exception x) { @@ -1555,99 +1316,54 @@ 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 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) + 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 = executeFunc(preparedStatement.Sql, preparedStatement.Rawvalues.ToArray(), (id) => + var result = sync + ? Execute(preparedStatement.Sql, preparedStatement.Rawvalues.ToArray()) + : await ExecuteAsync(preparedStatement.Sql, preparedStatement.Rawvalues.ToArray()).ConfigureAwait(false); + + if (result == 0 && !string.IsNullOrEmpty(preparedStatement.VersionName) && VersionException == VersionExceptionHandling.Exception) { - 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 - } + 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; } - + internal static string BuildPrimaryKeySql(Database database, Dictionary primaryKeyValuePair, ref int index) { var tempIndex = index; @@ -1655,7 +1371,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; @@ -1674,7 +1390,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; @@ -1701,7 +1417,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); } @@ -1713,10 +1429,11 @@ 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); - 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)); } @@ -1730,7 +1447,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); @@ -1739,13 +1456,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() @@ -1758,23 +1475,50 @@ 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 DeleteImp(tableName, primaryKeyName, poco, primaryKeyValue, Execute, 0); + return DeleteImpAsync(tableName, primaryKeyName, poco, primaryKeyValue, true).RunSync(); } - private TRet DeleteImp(string tableName, string primaryKeyName, object poco, object primaryKeyValue, Func 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); + 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)); - return executeFunc(sql, primaryKeyValuePairs.Select(x => x.Value).ToArray()); + 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; + 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 = sync + ? Execute(sql, rawValues.ToArray()) + : await ExecuteAsync(sql, rawValues.ToArray()).ConfigureAwait(false); + + 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; } public int Delete(object poco) @@ -1794,24 +1538,29 @@ 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. public bool IsNew(T poco) { -#if !NET35 + return IsNewAsync(poco, true).RunSync(); + } + + 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; } -#endif + var pd = PocoDataFactory.ForType(poco.GetType()); object pk; PocoColumn pc; @@ -1822,7 +1571,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 { @@ -1835,7 +1586,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(); @@ -1858,6 +1613,7 @@ public bool IsNew(T poco) // 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)) { @@ -1893,96 +1649,56 @@ 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; - private class FormattedParameter - { - public Type Type { get; set; } - public object Value { get; set; } - public DbParameter Parameter { get; set; } - } + public string LastCommand => FormatCommand(_lastSql, _lastArgs); - public string FormatCommand(DbCommand cmd) + 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()); + return _dbType.FormatCommand(cmd); } - public string FormatCommand(string sql, object[] args) + 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]; - var formatted = args[i] as FormattedParameter; - if (formatted != null) - { - 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; - 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; } } + public string ConnectionString => _connectionString; // 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) { DoPreExecute(cmd); - var result = cmd.ExecuteNonQuery(); + var result = ExecutionHook(() => cmd.ExecuteNonQuery()); OnExecutedCommandInternal(cmd); return result; } @@ -1990,17 +1706,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 Task ExecutionHookAsync(Func> action) + { + return await action().ConfigureAwait(false); } int IDatabaseHelpers.ExecuteNonQueryHelper(DbCommand cmd) => ExecuteNonQueryHelper(cmd); @@ -2009,13 +1735,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) { @@ -2026,13 +1750,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/DatabaseType.cs b/src/NPoco/DatabaseType.cs index dfd05a20..26a9d75b 100644 --- a/src/NPoco/DatabaseType.cs +++ b/src/NPoco/DatabaseType.cs @@ -7,24 +7,26 @@ using NPoco.DatabaseTypes; using NPoco.Expressions; using System.Reflection; +using System.Threading.Tasks; +using System.Text; 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,15 +225,14 @@ 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); + return await db.ExecuteScalarHelperAsync(cmd).ConfigureAwait(false); } -#endif - public virtual void InsertBulk(IDatabase db, IEnumerable pocos) + public virtual void InsertBulk(IDatabase db, IEnumerable pocos, InsertBulkOptions options) { foreach (var poco in pocos) { @@ -239,6 +240,14 @@ public virtual void InsertBulk(IDatabase db, IEnumerable pocos) } } + public virtual async Task InsertBulkAsync(IDatabase db, IEnumerable pocos, InsertBulkOptions options) + { + foreach (var poco in pocos) + { + await db.InsertAsync(poco).ConfigureAwait(false); + } + } + /// /// Look at the type and provider name being used and instantiate a suitable DatabaseType instance. /// @@ -251,7 +260,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 +270,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 +280,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 +294,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,29 +343,71 @@ 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) { 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/DatabaseTypes/FirebirdDatabaseType.cs b/src/NPoco/DatabaseTypes/FirebirdDatabaseType.cs index 4c0ecb17..20e749d7 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,20 +76,18 @@ 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) { var param = AdjustSqlInsertCommandText(cmd, primaryKeyName); - await db.ExecuteNonQueryHelperAsync(cmd); + await db.ExecuteNonQueryHelperAsync(cmd).ConfigureAwait(false); return param.Value; } - await db.ExecuteNonQueryHelperAsync(cmd); - return TaskAsyncHelper.FromResult(-1); + await db.ExecuteNonQueryHelperAsync(cmd).ConfigureAwait(false); + return -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..b3d5b8c6 100644 --- a/src/NPoco/DatabaseTypes/OracleDatabaseType.cs +++ b/src/NPoco/DatabaseTypes/OracleDatabaseType.cs @@ -1,12 +1,19 @@ -using System; +using NPoco.Expressions; +using System; using System.Data; using System.Data.Common; using System.Reflection; +using System.Threading.Tasks; 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 ":"; @@ -24,7 +31,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,21 +72,18 @@ 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) { var param = AdjustSqlInsertCommandText(cmd, primaryKeyName); - await db.ExecuteNonQueryHelperAsync(cmd); + await db.ExecuteNonQueryHelperAsync(cmd).ConfigureAwait(false); return param.Value; } - await db.ExecuteNonQueryHelperAsync(cmd); - return TaskAsyncHelper.FromResult(-1); + await db.ExecuteNonQueryHelperAsync(cmd).ConfigureAwait(false); + return -1; } -#endif - public override string GetProviderName() { diff --git a/src/NPoco/DatabaseTypes/PostgreSQLDatabaseType.cs b/src/NPoco/DatabaseTypes/PostgreSQLDatabaseType.cs index 99cb1a8d..8847cfbd 100644 --- a/src/NPoco/DatabaseTypes/PostgreSQLDatabaseType.cs +++ b/src/NPoco/DatabaseTypes/PostgreSQLDatabaseType.cs @@ -1,10 +1,17 @@ +using NPoco.Expressions; using System.Data; using System.Data.Common; +using System.Threading.Tasks; 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 @@ -35,19 +42,17 @@ 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) { AdjustSqlInsertCommandText(cmd, primaryKeyName); - return await db.ExecuteScalarHelperAsync(cmd); + return await db.ExecuteScalarHelperAsync(cmd).ConfigureAwait(false); } - await db.ExecuteNonQueryHelperAsync(cmd); - return TaskAsyncHelper.FromResult(-1); + await db.ExecuteNonQueryHelperAsync(cmd).ConfigureAwait(false); + return -1; } -#endif public override string GetParameterPrefix(string connectionString) { diff --git a/src/NPoco/DatabaseTypes/SQLiteDatabaseType.cs b/src/NPoco/DatabaseTypes/SQLiteDatabaseType.cs index 2e63cd2d..869df7af 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,19 +32,17 @@ 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) { AdjustSqlInsertCommandText(cmd); - return await db.ExecuteScalarHelperAsync(cmd); + return await db.ExecuteScalarHelperAsync(cmd).ConfigureAwait(false); } - await db.ExecuteNonQueryHelperAsync(cmd); - return TaskAsyncHelper.FromResult(-1); + await db.ExecuteNonQueryHelperAsync(cmd).ConfigureAwait(false); + return -1; } -#endif public override string GetExistsSql() { 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 b7c79607..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 @@ -16,9 +18,26 @@ protected override string EscapeParam(object par) { var param = par.ToString().ToUpper(); param = param - .Replace("\\", EscapeChar + EscapeChar) - .Replace("_", EscapeChar + "_"); + .Replace("\\", EscapeChar) + .Replace("_", "\\_") + .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..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) @@ -919,6 +916,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 @@ -1399,9 +1422,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)); @@ -1580,6 +1611,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/src/NPoco/FastCreate.cs b/src/NPoco/FastCreate.cs index 1ae4d9f7..a65cf015 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,89 @@ 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); + var create = CreateObjectFactoryMethodWithCtorParams(_constructorInfo); + var parameters = _constructorInfo.GetParameters() + .Select(x => MappingHelper.GetDefault(x.ParameterType)) + .ToArray(); + return x => create(parameters); + } - try + 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++) { - var del = constructor.CreateDelegate(Expression.GetFuncType(typeof(DbDataReader), typeof(object))); - return del as Func; + 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 + } } - catch (Exception exception) + 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) { - throw new Exception("Error trying to create type " + _type, exception); + 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 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 + ? getParameterLess.Constructor + : constructorParameters.FirstOrDefault()?.Constructor; } } } \ No newline at end of file diff --git a/src/NPoco/FluentMappings/ColumnConfigurationBuilder.cs b/src/NPoco/FluentMappings/ColumnConfigurationBuilder.cs index d2a69306..efc5a60e 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); @@ -83,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(); @@ -116,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/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..7e5e3fc0 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()) @@ -279,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; } } } @@ -307,7 +306,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..94ad06df 100644 --- a/src/NPoco/IAsyncDatabase.cs +++ b/src/NPoco/IAsyncDatabase.cs @@ -2,16 +2,102 @@ using System.Collections.Generic; using System.Data; using System.Linq.Expressions; -using NPoco.Linq; -#if !NET35 && !NET40 using System.Threading.Tasks; -#endif +using NPoco.Linq; 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); + + /// + /// 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 + /// + 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, InsertBulkOptions options = null); + + /// + /// 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(); + + /// + /// 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 { -#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 +153,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); + 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 @@ -136,69 +222,69 @@ public interface IAsyncDatabase : IBaseDatabase Task> SkipTakeAsync(long skip, long take, Sql sql); /// - /// Executes the provided sql and parameters and casts the result to T + /// Fetches multiple result sets into the one object. + /// In this method you must provide how you will take the results and combine them /// - Task ExecuteScalarAsync(string sql, params object[] args); + Task FetchMultipleAsync(Func, List, TRet> cb, string sql, params object[] args); /// - /// Executes the provided sql and parameters and casts the result to T + /// Fetches multiple result sets into the one object. + /// In this method you must provide how you will take the results and combine them /// - Task ExecuteScalarAsync(Sql sql); + Task FetchMultipleAsync(Func, List, List, TRet> cb, string sql, params object[] args); /// - /// Executes the provided sql and parameters + /// Fetches multiple result sets into the one object. + /// In this method you must provide how you will take the results and combine them /// - Task ExecuteAsync(string sql, params object[] args); + Task FetchMultipleAsync(Func, List, List, List, TRet> cb, string sql, params object[] args); /// - /// Executes the provided sql and parameters + /// Fetches multiple result sets into the one object. + /// In this method you must provide how you will take the results and combine them /// - Task ExecuteAsync(Sql sql); + Task FetchMultipleAsync(Func, List, TRet> cb, 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); + /// 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); /// - /// Update POCO in the table by convention or configuration - /// - Task UpdateAsync(object poco); + /// 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); /// - /// Update POCO in the table by convention or configuration specifying which columns to update - /// - Task UpdateAsync(object poco, IEnumerable columns); + /// Fetches multiple result sets into the one Tuple. + /// + Task<(List, List)> FetchMultipleAsync(string sql, params object[] args); /// - /// Update POCO in the table by convention or configuration specifying which columns to update - /// - Task UpdateAsync(T poco, Expression> fields); + /// Fetches multiple result sets into the one Tuple. + /// + Task<(List, List, List)> FetchMultipleAsync(string sql, params object[] args); /// - /// Update POCO's into database by concatenating sql using the provided batch options - /// - Task UpdateBatchAsync(IEnumerable> pocos, BatchOptions options = null); + /// Fetches multiple result sets into the one Tuple. + /// + Task<(List, List, List, List)> FetchMultipleAsync(string sql, params object[] args); /// - /// Delete POCO from table by convention or configuration - /// - Task DeleteAsync(object poco); + /// Fetches multiple result sets into the one Tuple. + /// + Task<(List, List)> FetchMultipleAsync(Sql sql); /// - /// Generate an update statement using a Fluent syntax. Remember to call Execute. + /// Fetches multiple result sets into the one Tuple. /// - IAsyncUpdateQueryProvider UpdateManyAsync(); + Task<(List, List, List)> FetchMultipleAsync(Sql sql); /// - /// Generate a delete statement using a Fluent syntax. Remember to call Execute. + /// Fetches multiple result sets into the one Tuple. /// - IAsyncDeleteQueryProvider DeleteManyAsync(); -#endif + Task<(List, List, List, List)> FetchMultipleAsync(Sql sql); } } 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 59e0cd85..aadda215 100644 --- a/src/NPoco/IDatabase.cs +++ b/src/NPoco/IDatabase.cs @@ -1,11 +1,13 @@ +#nullable enable using System; 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 : IAsyncDatabase, IDatabaseQuery, IDatabaseConfig { /// /// Insert POCO into the table, primary key and autoincrement specified @@ -25,12 +27,12 @@ public interface IDatabase : IDisposable, 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 /// - 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 @@ -45,12 +47,12 @@ public interface IDatabase : IDisposable, 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 @@ -60,7 +62,7 @@ public interface IDatabase : IDisposable, 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 @@ -96,7 +98,7 @@ public interface IDatabase : IDisposable, 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. @@ -111,7 +113,7 @@ public interface IDatabase : IDisposable, 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 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..07fa1e60 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 : IAsyncQueryDatabase { /// /// Builds a paged query from a non-paged query @@ -319,31 +317,31 @@ public interface IDatabaseQuery : IAsyncDatabase /// /// 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/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; } + } +} 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 132e0c8a..1628e4d9 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(); + IAsyncEnumerable 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(); + IAsyncEnumerable 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 @@ -116,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); @@ -124,12 +117,13 @@ 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); } - public class AsyncQueryProvider : IAsyncQueryProviderWithIncludes, ISimpleQueryProviderExpression, INeedDatabase + public class AsyncQueryProvider : IAsyncQueryProviderWithIncludes, ISimpleQueryProviderExpression, INeedDatabase, INeedSql { protected readonly Database _database; protected SqlExpression _sqlExpression; @@ -142,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); @@ -174,6 +169,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 { @@ -203,20 +214,24 @@ private IAsyncQueryProviderWithIncludes QueryProviderWithIncludes(Expression return this; } -#if !NET35 && !NET40 - public async Task> ToList() + public Task> ToList() + { + return ToEnumerable().ToListAsync().AsTask(); + } + + public Task ToArray() { - return (await ToEnumerable().ConfigureAwait(false)).ToList(); + return ToEnumerable().ToArrayAsync().AsTask(); } - public async Task ToArray() + public IAsyncEnumerable ToEnumerable() { - return (await ToEnumerable().ConfigureAwait(false)).ToArray(); + return ExecuteQueryAsync(BuildSql()); } - public Task> ToEnumerable() + private IAsyncEnumerable ExecuteQueryAsync(Sql sql) { - return _database.QueryAsync(default(T), _listExpression, null, BuildSql()); + return _database.QueryAsync(default, _listExpression, null, sql, _pocoData); } public Task FirstOrDefault() @@ -224,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().ConfigureAwait(false)).FirstOrDefault(); + return ToEnumerable().FirstOrDefaultAsync().AsTask(); } public Task First() @@ -235,10 +250,10 @@ public Task First() return First(null); } - public async Task First(Expression> whereExpression) + public Task First(Expression> whereExpression) { AddWhere(whereExpression); - return (await ToEnumerable().ConfigureAwait(false)).First(); + return ToEnumerable().FirstAsync().AsTask(); } public Task SingleOrDefault() @@ -246,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().ConfigureAwait(false)).SingleOrDefault(); + return ToEnumerable().SingleOrDefaultAsync().AsTask(); } public Task Single() @@ -257,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().ConfigureAwait(false)).Single(); + return ToEnumerable().SingleAsync().AsTask(); } public Task Count() @@ -268,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() @@ -296,7 +311,7 @@ public async Task> 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++; @@ -305,28 +320,27 @@ public async Task> ToPage(int page, int pageSize) _sqlExpression = _sqlExpression.Limit(offset, pageSize); - result.Items = await ToList(); + result.Items = await ToList().ConfigureAwait(false); return result; } - public async Task> ProjectTo(Expression> projectionExpression) + public Task> ProjectTo(Expression> projectionExpression) { var sql = _buildComplexSql.GetSqlForProjection(projectionExpression, false); - return (await _database.QueryAsync(sql).ConfigureAwait(false)).Select(projectionExpression.Compile()).ToList(); + return ExecuteQueryAsync(sql).Select(projectionExpression.Compile()).ToListAsync().AsTask(); } - public async Task> Distinct() + public Task> Distinct() { - return (await _database.QueryAsync(new Sql(_sqlExpression.Context.ToSelectStatement(true, true), _sqlExpression.Context.Params))).ToList(); + 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 _database.QueryAsync(sql).ConfigureAwait(false)).Select(projectionExpression.Compile()).ToList(); + return ExecuteQueryAsync(sql).Select(projectionExpression.Compile()).ToListAsync().AsTask(); } -#endif public IAsyncQueryProvider Where(Expression> whereExpression) { @@ -444,7 +458,6 @@ public IAsyncQueryProvider From(QueryBuilder builder) return this; } -#if !NET35 public List ToDynamicList() { return ToDynamicEnumerable().ToList(); @@ -455,11 +468,21 @@ public IEnumerable ToDynamicEnumerable() var sql = BuildSql(); return _database.QueryImp(null, null, null, sql); } -#endif + IDatabase INeedDatabase.GetDatabase() { return _database; } + + Sql INeedSql.GetSql() + { + return BuildSql(); + } + } + + public interface INeedSql + { + Sql GetSql(); } public interface INeedDatabase @@ -532,7 +555,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); } @@ -599,17 +621,15 @@ 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 -#if !NET35 && !NET40 public Task> ToListAsync() { return base.ToList(); @@ -620,7 +640,7 @@ public Task ToArrayAsync() return base.ToArray(); } - public Task> ToEnumerableAsync() + public IAsyncEnumerable ToEnumerableAsync() { return base.ToEnumerable(); } @@ -704,13 +724,17 @@ public Task> DistinctAsync() { return base.Distinct(); } -#endif public new IQueryProvider IncludeMany(Expression> expression, JoinType joinType = JoinType.Left) { 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/src/NPoco/Linq/UpdateQueryProvider.cs b/src/NPoco/Linq/UpdateQueryProvider.cs index c0d26157..0b29b8ed 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); + return await _database.ExecuteAsync(updateStatement, _sqlExpression.Context.Params).ConfigureAwait(false); } -#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/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/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/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/NPoco.csproj b/src/NPoco/NPoco.csproj index 01eb87ab..4b5caf72 100644 --- a/src/NPoco/NPoco.csproj +++ b/src/NPoco/NPoco.csproj @@ -2,91 +2,33 @@ An extremely easy to use Micro-ORM supporting Sql Server, MySQL, PostgreSQL, Oracle, Sqlite, SqlCE. - net35;net45;net40;netstandard1.3;netstandard2.0 + net461;netstandard2.0;netstandard2.1 NPoco NPoco - 4.0.3 + 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/ParameterHelper.cs b/src/NPoco/ParameterHelper.cs index 9e09821e..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; @@ -65,13 +67,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) @@ -124,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/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/PocoData.cs b/src/NPoco/PocoData.cs index fbbf1681..2e54ff5d 100644 --- a/src/NPoco/PocoData.cs +++ b/src/NPoco/PocoData.cs @@ -19,12 +19,16 @@ 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() { } - public PocoData(Type type, MapperCollection mapper) : this() + public PocoData(Type type, MapperCollection mapper, FastCreate creator) : this() { + CreateDelegate = creator; Type = type; Mapper = mapper; } @@ -70,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 cddfc497..738a97e1 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); @@ -47,11 +51,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) @@ -67,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(); @@ -133,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 }; @@ -153,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; @@ -178,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/PocoDataFactory.cs b/src/NPoco/PocoDataFactory.cs index 48285c0c..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; @@ -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..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; @@ -54,14 +62,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 +153,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/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 dc813d8b..a50fe5ce 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 { @@ -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)); + 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) { @@ -127,10 +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 2a172735..cfd37196 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, false); + + if (pocoMember.PocoColumn.MemberInfoKey == name) + return true; + + if (PropertyMapper.IsEqual(name, pocoMember.PocoColumn.ColumnAlias ?? pocoMember.PocoColumn.ColumnName, pocoMember.PocoColumn.ExactColumnNameMatch)) + return true; - return PropertyMapper.IsEqual(name, x.Name); - }); + return PropertyMapper.IsEqual(name, pocoMember.Name, pocoMember.PocoColumn.ExactColumnNameMatch); } - 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/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/src/NPoco/Singleton.cs b/src/NPoco/Singleton.cs index 26bb21fc..fd227254 100644 --- a/src/NPoco/Singleton.cs +++ b/src/NPoco/Singleton.cs @@ -9,4 +9,26 @@ namespace NPoco { 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..3253c5ed 100644 --- a/src/NPoco/Sql.cs +++ b/src/NPoco/Sql.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; -using System.Text; -#if NET35 using System.Linq; -#endif +using System.Text; namespace NPoco { @@ -93,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)); @@ -134,41 +138,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/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/Async/QueryAsyncTests.cs b/test/NPoco.Tests/Async/QueryAsyncTests.cs index 785fc3c2..9d075709 100644 --- a/test/NPoco.Tests/Async/QueryAsyncTests.cs +++ b/test/NPoco.Tests/Async/QueryAsyncTests.cs @@ -42,5 +42,24 @@ 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); + } + + [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++); + } + } } } 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..ac415aeb 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() @@ -221,14 +219,18 @@ 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); - //#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/Common/User.cs b/test/NPoco.Tests/Common/User.cs index ddf858e8..eb719b73 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; } @@ -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/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/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/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 diff --git a/test/NPoco.Tests/DecoratedTests/CRUDTests/UpdateTests.cs b/test/NPoco.Tests/DecoratedTests/CRUDTests/UpdateTests.cs index b50b6871..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; @@ -16,7 +19,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 +38,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 +84,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,7 +101,7 @@ public void UpdatePrimaryKeyVersionConcurrencyException() { var poco1 = Database.SingleOrDefaultById(InMemoryUsers[1].UserId); var poco2 = Database.SingleOrDefaultById(InMemoryUsers[1].UserId); - + poco1.Age = 100; Database.Update(poco1); @@ -111,7 +114,7 @@ public void UpdatePrimaryKeyVersionConcurrencyException() public void UpdatePrimaryKeyNoVersionConcurrencyException() { var poco1 = Database.SingleOrDefaultById(InMemoryUsers[1].UserId); - + poco1.Age = 100; Database.Update(poco1); @@ -169,7 +172,79 @@ public void UpdateBatchTest() { 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); + } + + [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 diff --git a/test/NPoco.Tests/DecoratedTests/QueryTests/ConstructorTests.cs b/test/NPoco.Tests/DecoratedTests/QueryTests/ConstructorTests.cs new file mode 100644 index 00000000..1dd85185 --- /dev/null +++ b/test/NPoco.Tests/DecoratedTests/QueryTests/ConstructorTests.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Net.NetworkInformation; +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); + } + + [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; } + 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; } + + [Construct] + private SingleParameterConstructor() + { + Single = true; + } + + public SingleParameterConstructor(string Name, int Age) + { + this.Name = Name; + this.Age = Age; + Multiple = true; + } + } + } + + +} diff --git a/test/NPoco.Tests/DecoratedTests/QueryTests/FetchAndQueryDecoratedTests.cs b/test/NPoco.Tests/DecoratedTests/QueryTests/FetchAndQueryDecoratedTests.cs index e5e341dd..d7ba816b 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 { @@ -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() { diff --git a/test/NPoco.Tests/DecoratedTests/QueryTests/ParentChildIncludeTests.cs b/test/NPoco.Tests/DecoratedTests/QueryTests/ParentChildIncludeTests.cs new file mode 100644 index 00000000..e64b4d2d --- /dev/null +++ b/test/NPoco.Tests/DecoratedTests/QueryTests/ParentChildIncludeTests.cs @@ -0,0 +1,60 @@ +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); + } + + [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; } + } + } +} diff --git a/test/NPoco.Tests/DecoratedTests/TransactionDecoratedTests.cs b/test/NPoco.Tests/DecoratedTests/TransactionDecoratedTests.cs index c02439a9..e301fbec 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; @@ -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); 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/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 c11fe170..82851c68 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; @@ -341,14 +342,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] @@ -363,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); @@ -390,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); @@ -447,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); @@ -578,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() //{ @@ -610,7 +619,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/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/FormatCommandTest.cs b/test/NPoco.Tests/FormatCommandTest.cs index 6a9ea4ca..6d88a28a 100644 --- a/test/NPoco.Tests/FormatCommandTest.cs +++ b/test/NPoco.Tests/FormatCommandTest.cs @@ -1,6 +1,5 @@ using System.Data; -using System.Data.SqlClient; -using NPoco; +using Microsoft.Data.SqlClient; using NUnit.Framework; namespace NPoco.Tests @@ -33,7 +32,7 @@ public void FormattingWithStringValue() public class MyDb : Database { public MyDb() - : base("test", DatabaseType.SqlServer2008, 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) + { + } + + + } + } +} diff --git a/test/NPoco.Tests/NPoco.Tests.csproj b/test/NPoco.Tests/NPoco.Tests.csproj index 628aa079..ecda6821 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 + net5.0 + 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/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()})"; + } +} diff --git a/test/NPoco.Tests/NewMapper/NewMapperTests.cs b/test/NPoco.Tests/NewMapper/NewMapperTests.cs index b957a7dc..29523c38 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 { @@ -453,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(Guid.Parse("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() { @@ -634,6 +670,7 @@ public void Test31() Assert.Throws(() => { var fastCreate = new FastCreate(typeof(ContentBase), new MapperCollection()); + fastCreate.Create(new FakeReader()); }); } @@ -689,6 +726,42 @@ public class ResultData public int Age { get; set; } } } + + [Test] + public void Test36() + { + var result = Database.Single("select 'Test' Data, 'Test2' Data1 /*poco_dual*/"); + 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; + } + + [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 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; + } + } + } +} 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(); 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]); + } } }