Repository: DapperLib/Dapper Branch: main Commit: 288730e69b05 Files: 221 Total size: 1.2 MB Directory structure: gitextract_2tqsalxj/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ └── bug_report.md │ └── workflows/ │ ├── cla.yml │ └── main.yml ├── .gitignore ├── Build.csproj ├── Dapper/ │ ├── CommandDefinition.cs │ ├── CommandFlags.cs │ ├── CompiledRegex.cs │ ├── CustomPropertyTypeMap.cs │ ├── Dapper.csproj │ ├── DataTableHandler.cs │ ├── DbString.cs │ ├── DefaultTypeMap.cs │ ├── DynamicParameters.CachedOutputSetters.cs │ ├── DynamicParameters.ParamInfo.cs │ ├── DynamicParameters.cs │ ├── ExplicitConstructorAttribute.cs │ ├── Extensions.cs │ ├── FeatureSupport.cs │ ├── Global.cs │ ├── NRT.cs │ ├── Properties/ │ │ └── AssemblyInfo.cs │ ├── PublicAPI/ │ │ ├── net461/ │ │ │ ├── PublicAPI.Shipped.txt │ │ │ └── PublicAPI.Unshipped.txt │ │ ├── net8.0/ │ │ │ ├── PublicAPI.Shipped.txt │ │ │ └── PublicAPI.Unshipped.txt │ │ └── netstandard2.0/ │ │ ├── PublicAPI.Shipped.txt │ │ └── PublicAPI.Unshipped.txt │ ├── PublicAPI.Shipped.txt │ ├── PublicAPI.Unshipped.txt │ ├── SimpleMemberMap.cs │ ├── SqlDataRecordHandler.cs │ ├── SqlDataRecordListTVPParameter.cs │ ├── SqlMapper.Async.cs │ ├── SqlMapper.CacheInfo.cs │ ├── SqlMapper.DapperRow.Descriptor.cs │ ├── SqlMapper.DapperRow.cs │ ├── SqlMapper.DapperRowMetaObject.cs │ ├── SqlMapper.DapperTable.cs │ ├── SqlMapper.DeserializerState.cs │ ├── SqlMapper.DontMap.cs │ ├── SqlMapper.GridReader.Async.cs │ ├── SqlMapper.GridReader.cs │ ├── SqlMapper.ICustomQueryParameter.cs │ ├── SqlMapper.IDataReader.cs │ ├── SqlMapper.IDynamicParameters.cs │ ├── SqlMapper.IMemberMap.cs │ ├── SqlMapper.IParameterCallbacks.cs │ ├── SqlMapper.IParameterLookup.cs │ ├── SqlMapper.ITypeHandler.cs │ ├── SqlMapper.ITypeMap.cs │ ├── SqlMapper.Identity.cs │ ├── SqlMapper.Link.cs │ ├── SqlMapper.LiteralToken.cs │ ├── SqlMapper.Settings.cs │ ├── SqlMapper.TypeDeserializerCache.cs │ ├── SqlMapper.TypeHandler.cs │ ├── SqlMapper.TypeHandlerCache.cs │ ├── SqlMapper.cs │ ├── TableValuedParameter.cs │ ├── TypeExtensions.cs │ ├── UdtTypeHandler.cs │ ├── WrappedDataReader.cs │ ├── WrappedReader.cs │ └── XmlHandlers.cs ├── Dapper.EntityFramework/ │ ├── Dapper.EntityFramework.csproj │ ├── DbGeographyHandler.cs │ ├── DbGeometryHandler.cs │ ├── Handlers.cs │ ├── PublicAPI.Shipped.txt │ └── PublicAPI.Unshipped.txt ├── Dapper.EntityFramework.StrongName/ │ └── Dapper.EntityFramework.StrongName.csproj ├── Dapper.ProviderTools/ │ ├── BulkCopy.cs │ ├── Dapper.ProviderTools.csproj │ ├── DbConnectionExtensions.cs │ ├── DbExceptionExtensions.cs │ ├── Internal/ │ │ └── DynamicBulkCopy.cs │ ├── PublicAPI.Shipped.txt │ └── PublicAPI.Unshipped.txt ├── Dapper.Rainbow/ │ ├── Dapper.Rainbow.csproj │ ├── Database.Async.cs │ ├── Database.cs │ ├── IgnorePropertyAttribute.cs │ ├── Snapshotter.cs │ ├── SqlCompactDatabase.cs │ └── readme.md ├── Dapper.SqlBuilder/ │ ├── Dapper.SqlBuilder.csproj │ ├── PublicAPI.Shipped.txt │ ├── PublicAPI.Unshipped.txt │ ├── Readme.md │ └── SqlBuilder.cs ├── Dapper.StrongName/ │ └── Dapper.StrongName.csproj ├── Dapper.sln ├── Dapper.sln.DotSettings ├── Dapper.snk ├── Directory.Build.props ├── Directory.Build.targets ├── Directory.Packages.props ├── License.txt ├── NonCLA.md ├── Readme.md ├── appveyor.yml ├── benchmarks/ │ ├── Dapper.Tests.Performance/ │ │ ├── Benchmarks.Belgrade.cs │ │ ├── Benchmarks.Dapper.cs │ │ ├── Benchmarks.Dashing.cs │ │ ├── Benchmarks.EntityFramework.cs │ │ ├── Benchmarks.EntityFrameworkCore.cs │ │ ├── Benchmarks.HandCoded.cs │ │ ├── Benchmarks.Linq2DB.cs │ │ ├── Benchmarks.Linq2Sql.cs │ │ ├── Benchmarks.Massive.cs │ │ ├── Benchmarks.Mighty.cs │ │ ├── Benchmarks.NHibernate.cs │ │ ├── Benchmarks.Norm.cs │ │ ├── Benchmarks.PetaPoco.cs │ │ ├── Benchmarks.RepoDB.cs │ │ ├── Benchmarks.ServiceStack.cs │ │ ├── Benchmarks.SqlMarshal.cs │ │ ├── Benchmarks.Susanoo.cs │ │ ├── Benchmarks.XPO.cs │ │ ├── Benchmarks.cs │ │ ├── Config.cs │ │ ├── Dapper.Tests.Performance.csproj │ │ ├── DapperCacheImpact.cs │ │ ├── Dashing/ │ │ │ ├── DashingConfiguration.cs │ │ │ └── Post.cs │ │ ├── EntityFramework/ │ │ │ └── EFContext.cs │ │ ├── EntityFrameworkCore/ │ │ │ └── EFCoreContext.cs │ │ ├── Helpers/ │ │ │ ├── ORMColum.cs │ │ │ └── ReturnColum.cs │ │ ├── LegacyTests.cs │ │ ├── Linq2DB/ │ │ │ ├── ConnectionStringSettings.cs │ │ │ ├── Linq2DBContext.cs │ │ │ └── Linq2DbSettings.cs │ │ ├── Linq2Sql/ │ │ │ ├── DataClasses.dbml │ │ │ ├── DataClasses.dbml.layout │ │ │ └── DataClasses.designer.cs │ │ ├── Massive/ │ │ │ └── Massive.cs │ │ ├── NHibernate/ │ │ │ ├── NHibernateHelper.cs │ │ │ ├── Post.hbm.xml │ │ │ └── hibernate.cfg.xml │ │ ├── PetaPoco/ │ │ │ └── PetaPoco.cs │ │ ├── Post.cs │ │ ├── Program.cs │ │ ├── SqlDataReaderHelper.cs │ │ ├── XPO/ │ │ │ └── Post.cs │ │ └── app.config │ └── Directory.Build.props ├── build.cmd ├── build.ps1 ├── docs/ │ ├── _config.yml │ ├── dapperplus.md │ ├── docs.csproj │ ├── index.md │ └── readme.md ├── global.json ├── nuget.config ├── signatures/ │ └── version1/ │ └── cla.json ├── tests/ │ ├── Dapper.Tests/ │ │ ├── App.config │ │ ├── AsyncTests.cs │ │ ├── ConstructorTests.cs │ │ ├── Dapper.Tests.csproj │ │ ├── DataReaderTests.cs │ │ ├── DateTimeOnlyTests.cs │ │ ├── DecimalTests.cs │ │ ├── EnumTests.cs │ │ ├── Helpers/ │ │ │ ├── Attributes.cs │ │ │ ├── Common.cs │ │ │ ├── IsExternalInit.cs │ │ │ ├── SqlServerTypesLoader.cs │ │ │ ├── TransactedConnection.cs │ │ │ └── XunitSkippable.cs │ │ ├── LiteralTests.cs │ │ ├── MiscTests.cs │ │ ├── MultiMapTests.cs │ │ ├── NullTests.cs │ │ ├── ParameterTests.cs │ │ ├── ProcedureTests.cs │ │ ├── ProviderTests.cs │ │ ├── Providers/ │ │ │ ├── DuckDBTests.cs │ │ │ ├── EntityFrameworkTests.cs │ │ │ ├── FirebirdTests.cs │ │ │ ├── Linq2SqlTests.cs │ │ │ ├── MySQLTests.cs │ │ │ ├── OLDEBTests.cs │ │ │ ├── PostgresqlTests.cs │ │ │ ├── SnowflakeTests.cs │ │ │ └── SqliteTests.cs │ │ ├── QueryMultipleTests.cs │ │ ├── SharedTypes/ │ │ │ ├── Address.cs │ │ │ ├── Bar1.cs │ │ │ ├── Category.cs │ │ │ ├── Comment.cs │ │ │ ├── Dog.cs │ │ │ ├── Enums.cs │ │ │ ├── Foo1.cs │ │ │ ├── HazNameId.cs │ │ │ ├── Index.cs │ │ │ ├── Person.cs │ │ │ ├── Post.cs │ │ │ ├── Product.cs │ │ │ ├── ReviewBoard.cs │ │ │ ├── ShortEnum.cs │ │ │ ├── SomeType.cs │ │ │ └── User.cs │ │ ├── SingleRowTests.cs │ │ ├── SqlBuilderTests.cs │ │ ├── TestBase.cs │ │ ├── TransactionTests.cs │ │ ├── TupleTests.cs │ │ ├── TypeHandlerTests.cs │ │ ├── WrappedReaderTests.cs │ │ ├── XmlTests.cs │ │ └── xunit.runner.json │ ├── Directory.Build.props │ ├── Directory.Build.targets │ └── docker-compose.yml └── version.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # EditorConfig is awesome:http://EditorConfig.org # top-most EditorConfig file root = true # Don't use tabs for indentation. [*] indent_style = space # (Please don't specify an indent_size here; that has too many unintended consequences.) # Code files [*.{cs,csx,vb,vbx}] indent_size = 4 insert_final_newline = true charset = utf-8-bom # Xml project files [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] indent_size = 2 # Xml config files [*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] indent_size = 2 # JSON files [*.json] indent_size = 2 # Dotnet code style settings: [*.{cs,vb}] # Sort using and Import directives with System.* appearing first dotnet_sort_system_directives_first = true # Avoid "this." and "Me." if not necessary dotnet_style_qualification_for_field = false:suggestion dotnet_style_qualification_for_property = false:suggestion dotnet_style_qualification_for_method = false:suggestion dotnet_style_qualification_for_event = false:suggestion # Use language keywords instead of framework type names for type references dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion dotnet_style_predefined_type_for_member_access = true:suggestion # Suggest more modern language features when available dotnet_style_object_initializer = true:suggestion dotnet_style_collection_initializer = true:suggestion dotnet_style_coalesce_expression = true:suggestion dotnet_style_null_propagation = true:suggestion dotnet_style_explicit_tuple_names = true:suggestion # CSharp code style settings: [*.cs] # Prefer "var" everywhere #csharp_style_var_for_built_in_types = true:suggestion #csharp_style_var_when_type_is_apparent = false:suggestion #csharp_style_var_elsewhere = true:suggestion # Prefer method-like constructs to have a expression-body csharp_style_expression_bodied_methods = true:none csharp_style_expression_bodied_constructors = true:none csharp_style_expression_bodied_operators = true:none # Prefer property-like constructs to have an expression-body csharp_style_expression_bodied_properties = true:none csharp_style_expression_bodied_indexers = true:none csharp_style_expression_bodied_accessors = true:none # Suggest more modern language features when available csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion csharp_style_pattern_matching_over_as_with_null_check = true:suggestion csharp_style_inlined_variable_declaration = true:suggestion csharp_style_throw_expression = true:suggestion csharp_style_conditional_delegate_call = true:suggestion # Newline settings csharp_new_line_before_open_brace = all csharp_new_line_before_else = true csharp_new_line_before_catch = true csharp_new_line_before_finally = true csharp_new_line_before_members_in_object_initializers = true csharp_new_line_before_members_in_anonymous_types = true ================================================ FILE: .gitattributes ================================================ * text=auto *.doc diff=astextplain *.DOC diff=astextplain *.docx diff=astextplain *.DOCX diff=astextplain *.dot diff=astextplain *.DOT diff=astextplain *.pdf diff=astextplain *.PDF diff=astextplain *.rtf diff=astextplain *.RTF diff=astextplain *.jpg binary *.png binary *.gif binary *.cs -text diff=csharp *.vb -text *.c -text *.cpp -text *.cxx -text *.h -text *.hxx -text *.py -text *.rb -text *.java -text *.html -text *.htm -text *.css -text *.scss -text *.sass -text *.less -text *.js -text *.lisp -text *.clj -text *.sql -text *.php -text *.lua -text *.m -text *.asm -text *.erl -text *.fs -text *.fsx -text *.hs -text *.csproj -text merge=union *.vbproj -text merge=union *.fsproj -text merge=union *.dbproj -text merge=union *.sln -text merge=union ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: [mgravell, dapperlib] custom: ["https://www.buymeacoffee.com/marcgravell"] ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: bug, needs-triage assignees: '' --- **Check your library version, and try updating** To help, we're going to need to know your library version. If it isn't the latest: *go do that* - it might fix the problem, and even if it doesn't: you're going to need to update if we find a problem and fix it, so you might as well get ready for that now. **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected and actual behavior** A clear and concise description of what you expected to happen, and what actually happens. **Additional context** Add any other context about the problem here: - what DB backend (and version) are you using, if relevant? - what ADO.NET provider (and version) are you using, if relevant? - what OS and .NET runtime (and version) are you using, if relevant? ================================================ FILE: .github/workflows/cla.yml ================================================ name: "CLA Assistant" on: issue_comment: types: [created] pull_request_target: types: [opened,closed,synchronize] # explicitly configure permissions, in case your GITHUB_TOKEN workflow permissions are set to read-only in repository settings permissions: actions: write contents: write pull-requests: write statuses: write jobs: CLAAssistant: runs-on: ubuntu-latest steps: - name: "CLA Assistant" if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target' uses: contributor-assistant/github-action@v2.3.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # the below token should have repo scope and must be manually added by you in the repository's secret # This token is required only if you have configured to store the signatures in a remote repository/organization PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} with: path-to-signatures: 'signatures/version1/cla.json' path-to-document: 'https://raw.githubusercontent.com/DapperLib/Dapper/main/NonCLA.md' # e.g. a CLA or a DCO document # branch should not be protected branch: 'main' # allowlist: user1,bot* # the followings are the optional inputs - If the optional inputs are not given, then default values will be taken #remote-organization-name: enter the remote organization name where the signatures should be stored (Default is storing the signatures in the same repository) #remote-repository-name: enter the remote repository name where the signatures should be stored (Default is storing the signatures in the same repository) #create-file-commit-message: 'For example: Creating file for storing CLA Signatures' #signed-commit-message: 'For example: $contributorName has signed the CLA in $owner/$repo#$pullRequestNo' #custom-notsigned-prcomment: 'pull request comment with Introductory message to ask new contributors to sign' #custom-pr-sign-comment: 'The signature to be committed in order to sign the CLA' #custom-allsigned-prcomment: 'pull request comment when all contributors has signed, defaults to **CLA Assistant Lite bot** All Contributors have signed the CLA.' #lock-pullrequest-aftermerge: false - if you don't want this bot to automatically lock the pull request after merging (default - true) #use-dco-flag: true - If you are using DCO instead of CLA ================================================ FILE: .github/workflows/main.yml ================================================ name: Main Build on: pull_request: push: branches: - main paths: - '*' - '!/docs/*' # Don't run workflow when files are only in the /docs directory jobs: vm-job: name: Ubuntu runs-on: ubuntu-latest services: postgres: image: postgres ports: - 5432/tcp env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: test options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 sqlserver: image: mcr.microsoft.com/mssql/server:2019-latest ports: - 1433/tcp env: ACCEPT_EULA: Y SA_PASSWORD: "Password." mysql: image: mysql ports: - 3306/tcp env: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: test steps: - name: Checkout code uses: actions/checkout@v1 - name: Setup dotnet uses: actions/setup-dotnet@v4 with: dotnet-version: '10.0.x' - name: .NET Build run: dotnet build Build.csproj -c Release /p:CI=true - name: Dapper Tests run: dotnet test tests/Dapper.Tests/Dapper.Tests.csproj -c Release --logger GitHubActions -p:CI=true -p:TestTfmsInParallel=false env: MySqlConnectionString: Server=localhost;Port=${{ job.services.mysql.ports[3306] }};Uid=root;Pwd=root;Database=test;Allow User Variables=true OLEDBConnectionString: Provider=SQLOLEDB;Server=tcp:localhost,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=Password.; PostgesConnectionString: Server=localhost;Port=${{ job.services.postgres.ports[5432] }};Database=test;User Id=postgres;Password=postgres; SqlServerConnectionString: Server=tcp:localhost,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=Password.; - name: .NET Lib Pack run: dotnet pack Build.csproj --no-build -c Release /p:PackageOutputPath=%CD%\.nupkgs /p:CI=true ================================================ FILE: .gitignore ================================================ /*.suo .vs/ .vscode/ bin/ obj/ /*.user _Resharper* .hgtags NuGet.exe *.user *.nupkg .nupkgs/ .docstats *.ide/ *.lock.json *.coverage Test.DB.* TestResults/ Dapper.Tests/*.sdf Dapper.Tests/SqlServerTypes/ .dotnet/* BenchmarkDotNet.Artifacts/ .idea/ .DS_Store ================================================ FILE: Build.csproj ================================================ ================================================ FILE: Dapper/CommandDefinition.cs ================================================ using System; using System.Data; using System.Reflection; using System.Reflection.Emit; using System.Threading; namespace Dapper { /// /// Represents the key aspects of a sql operation /// public readonly struct CommandDefinition { internal static CommandDefinition ForCallback(object? parameters, CommandFlags flags) { return new CommandDefinition(parameters is DynamicParameters ? parameters : null, flags); } internal void OnCompleted() { (Parameters as SqlMapper.IParameterCallbacks)?.OnCompleted(); } /// /// The command (sql or a stored-procedure name) to execute /// public string CommandText { get; } /// /// The parameters associated with the command /// public object? Parameters { get; } /// /// The active transaction for the command /// public IDbTransaction? Transaction { get; } /// /// The effective timeout for the command /// public int? CommandTimeout { get; } internal readonly CommandType CommandTypeDirect; /// /// The type of command that the command-text represents /// #if DEBUG // prevent use in our own code [Obsolete("Prefer " + nameof(CommandTypeDirect), true)] #endif public CommandType? CommandType => CommandTypeDirect; /// /// Should data be buffered before returning? /// public bool Buffered => (Flags & CommandFlags.Buffered) != 0; /// /// Should the plan for this query be cached? /// internal bool AddToCache => (Flags & CommandFlags.NoCache) == 0; /// /// Additional state flags against this command /// public CommandFlags Flags { get; } /// /// Can async queries be pipelined? /// public bool Pipelined => (Flags & CommandFlags.Pipelined) != 0; /// /// Initialize the command definition /// /// The text for this command. /// The parameters for this command. /// The transaction for this command to participate in. /// The timeout (in seconds) for this command. /// The for this command. /// The behavior flags for this command. /// The cancellation token for this command. public CommandDefinition(string commandText, object? parameters = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null, CommandFlags flags = CommandFlags.Buffered , CancellationToken cancellationToken = default ) { CommandText = commandText; Parameters = parameters; Transaction = transaction; CommandTimeout = commandTimeout; CommandTypeDirect = commandType ?? InferCommandType(commandText); Flags = flags; CancellationToken = cancellationToken; } internal static CommandType InferCommandType(string sql) { // if the sql contains any whitespace character (space/tab/cr/lf/etc - via unicode), // has operators, comments, semi-colon, or a known exception: interpret as ad-hoc; // otherwise, simple names like "SomeName" should be treated as a stored-proc // (note TableDirect would need to be specified explicitly, but in reality providers don't usually support TableDirect anyway) if (sql is null || CompiledRegex.WhitespaceOrReserved.IsMatch(sql)) return System.Data.CommandType.Text; return System.Data.CommandType.StoredProcedure; } private CommandDefinition(object? parameters, CommandFlags flags) : this() { Parameters = parameters; Flags = flags; CommandText = ""; } /// /// For asynchronous operations, the cancellation-token /// public CancellationToken CancellationToken { get; } internal IDbCommand SetupCommand(IDbConnection cnn, Action? paramReader) { var cmd = cnn.CreateCommand(); var init = GetInit(cmd.GetType()); init?.Invoke(cmd); if (Transaction is not null) cmd.Transaction = Transaction; cmd.CommandText = CommandText; if (CommandTimeout.HasValue) { cmd.CommandTimeout = CommandTimeout.Value; } else if (SqlMapper.Settings.CommandTimeout.HasValue) { cmd.CommandTimeout = SqlMapper.Settings.CommandTimeout.Value; } cmd.CommandType = CommandTypeDirect; paramReader?.Invoke(cmd, Parameters); return cmd; } private static SqlMapper.Link>? commandInitCache; internal static void ResetCommandInitCache() => SqlMapper.Link>.Clear(ref commandInitCache); private static Action? GetInit(Type commandType) { if (commandType is null) return null; // GIGO if (SqlMapper.Link>.TryGet(commandInitCache, commandType, out Action? action)) { return action; } var bindByName = GetBasicPropertySetter(commandType, "BindByName", typeof(bool)); var initialLongFetchSize = GetBasicPropertySetter(commandType, "InitialLONGFetchSize", typeof(int)); var fetchSize = GetBasicPropertySetter(commandType, "FetchSize", typeof(long)); action = null; if (bindByName is not null || initialLongFetchSize is not null || fetchSize is not null) { var method = new DynamicMethod(commandType.Name + "_init", null, new Type[] { typeof(IDbCommand) }); var il = method.GetILGenerator(); if (bindByName is not null) { // .BindByName = true il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Castclass, commandType); il.Emit(OpCodes.Ldc_I4_1); il.EmitCall(OpCodes.Callvirt, bindByName, null); } if (initialLongFetchSize is not null) { // .InitialLONGFetchSize = -1 il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Castclass, commandType); il.Emit(OpCodes.Ldc_I4_M1); il.EmitCall(OpCodes.Callvirt, initialLongFetchSize, null); } if (fetchSize is not null) { var snapshot = SqlMapper.Settings.FetchSize; if (snapshot >= 0) { // .FetchSize = {withValue} il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Castclass, commandType); il.Emit(OpCodes.Ldc_I8, snapshot); // bake it as a constant il.EmitCall(OpCodes.Callvirt, fetchSize, null); } } il.Emit(OpCodes.Ret); action = (Action)method.CreateDelegate(typeof(Action)); } // cache it SqlMapper.Link>.TryAdd(ref commandInitCache, commandType, ref action!); return action; } private static MethodInfo? GetBasicPropertySetter(Type declaringType, string name, Type expectedType) { var prop = declaringType.GetProperty(name, BindingFlags.Public | BindingFlags.Instance); if (prop?.CanWrite == true && prop.PropertyType == expectedType && prop.GetIndexParameters().Length == 0) { return prop.GetSetMethod(); } return null; } } } ================================================ FILE: Dapper/CommandFlags.cs ================================================ using System; namespace Dapper { /// /// Additional state flags that control command behaviour /// [Flags] public enum CommandFlags { /// /// No additional flags /// None = 0, /// /// Should data be buffered before returning? /// Buffered = 1, /// /// Can async queries be pipelined? /// Pipelined = 2, /// /// Should the plan cache be bypassed? /// NoCache = 4, } } ================================================ FILE: Dapper/CompiledRegex.cs ================================================ using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; namespace Dapper; internal static partial class CompiledRegex { #if DEBUG && NET7_0_OR_GREATER // enables colorization in IDE [StringSyntax("Regex")] #endif private const string WhitespaceOrReservedPattern = @"[\s;/\-+*]|^vacuum$|^commit$|^rollback$|^revert$", LegacyParameterPattern = @"(? LegacyParameterGen(); internal static Regex LiteralTokens => LiteralTokensGen(); internal static Regex PseudoPositional => PseudoPositionalGen(); internal static Regex WhitespaceOrReserved => WhitespaceOrReservedGen(); #else internal static Regex LegacyParameter { get; } = new(LegacyParameterPattern, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant | RegexOptions.Compiled); internal static Regex LiteralTokens { get; } = new(LiteralTokensPattern, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant | RegexOptions.Compiled); internal static Regex PseudoPositional { get; } = new(PseudoPositionalPattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled); internal static Regex WhitespaceOrReserved { get; } = new(WhitespaceOrReservedPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase); #endif } ================================================ FILE: Dapper/CustomPropertyTypeMap.cs ================================================ using System; using System.Reflection; namespace Dapper { /// /// Implements custom property mapping by user provided criteria (usually presence of some custom attribute with column to member mapping) /// public sealed class CustomPropertyTypeMap : SqlMapper.ITypeMap { private readonly Type _type; private readonly Func _propertySelector; /// /// Creates custom property mapping /// /// Target entity type /// Property selector based on target type and DataReader column name public CustomPropertyTypeMap(Type type, Func propertySelector) { _type = type ?? throw new ArgumentNullException(nameof(type)); _propertySelector = propertySelector ?? throw new ArgumentNullException(nameof(propertySelector)); } /// /// Always returns default constructor /// /// DataReader column names /// DataReader column types /// Default constructor public ConstructorInfo? FindConstructor(string[] names, Type[] types) => _type.GetConstructor(Array.Empty())!; /// /// Always returns null /// /// public ConstructorInfo? FindExplicitConstructor() => null; /// /// Not implemented as far as default constructor used for all cases /// /// /// /// public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName) { throw new NotSupportedException(); } /// /// Returns property based on selector strategy /// /// DataReader column name /// Property member map public SqlMapper.IMemberMap? GetMember(string columnName) { var prop = _propertySelector(_type, columnName); return prop is not null ? new SimpleMemberMap(columnName, prop) : null; } } } ================================================ FILE: Dapper/Dapper.csproj ================================================  Dapper Dapper orm;sql;micro-orm A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc. Major Sponsor: Dapper Plus from ZZZ Projects. Sam Saffron;Marc Gravell;Nick Craver net461;netstandard2.0;net8.0;net10.0 enable true all runtime; build; native; contentfiles; analyzers ================================================ FILE: Dapper/DataTableHandler.cs ================================================ using System; using System.Data; namespace Dapper { internal sealed class DataTableHandler : SqlMapper.ITypeHandler { public object Parse(Type destinationType, object value) { throw new NotImplementedException(); } public void SetValue(IDbDataParameter parameter, object value) { TableValuedParameter.Set(parameter, value as DataTable, null); } } } ================================================ FILE: Dapper/DbString.cs ================================================ using System; using System.Data; namespace Dapper { /// /// This class represents a SQL string, it can be used if you need to denote your parameter is a Char vs VarChar vs nVarChar vs nChar /// public sealed class DbString : SqlMapper.ICustomQueryParameter { /// /// Default value for IsAnsi. /// public static bool IsAnsiDefault { get; set; } /// /// A value to set the default value of strings /// going through Dapper. Default is 4000, any value larger than this /// field will not have the default value applied. /// public const int DefaultLength = 4000; /// /// Create a new DbString /// public DbString() { Length = -1; IsAnsi = IsAnsiDefault; } /// /// Create a new DbString /// public DbString(string? value, int length = -1) { Value = value; Length = length; IsAnsi = IsAnsiDefault; } /// /// Ansi vs Unicode /// public bool IsAnsi { get; set; } /// /// Fixed length /// public bool IsFixedLength { get; set; } /// /// Length of the string -1 for max /// public int Length { get; set; } /// /// The value of the string /// public string? Value { get; set; } /// /// Gets a string representation of this DbString. /// public override string ToString() => Value is null ? $"Dapper.DbString (Value: null, Length: {Length}, IsAnsi: {IsAnsi}, IsFixedLength: {IsFixedLength})" : $"Dapper.DbString (Value: '{Value}', Length: {Length}, IsAnsi: {IsAnsi}, IsFixedLength: {IsFixedLength})"; /// /// Add the parameter to the command... internal use only /// /// /// public void AddParameter(IDbCommand command, string name) { if (IsFixedLength && Length == -1) { throw new InvalidOperationException("If specifying IsFixedLength, a Length must also be specified"); } bool add = !command.Parameters.Contains(name); IDbDataParameter param; if (add) { param = command.CreateParameter(); param.ParameterName = name; } else { param = (IDbDataParameter)command.Parameters[name]; } #pragma warning disable 0618 param.Value = SqlMapper.SanitizeParameterValue(Value); #pragma warning restore 0618 if (Length == -1 && Value is not null && Value.Length <= DefaultLength) { param.Size = DefaultLength; } else { param.Size = Length; } param.DbType = IsAnsi ? (IsFixedLength ? DbType.AnsiStringFixedLength : DbType.AnsiString) : (IsFixedLength ? DbType.StringFixedLength : DbType.String); if (add) { command.Parameters.Add(param); } } } } ================================================ FILE: Dapper/DefaultTypeMap.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; namespace Dapper { /// /// Represents default type mapping strategy used by Dapper /// public sealed class DefaultTypeMap : SqlMapper.ITypeMap { private readonly List _fields; private readonly Type _type; /// /// Creates default type map /// /// Entity type public DefaultTypeMap(Type type) { if (type is null) throw new ArgumentNullException(nameof(type)); _fields = GetSettableFields(type); Properties = GetSettableProps(type); _type = type; } internal static MethodInfo GetPropertySetterOrThrow(PropertyInfo propertyInfo, Type type) { return GetPropertySetter(propertyInfo, type) ?? Throw(propertyInfo); static MethodInfo Throw(PropertyInfo propertyInfo) => throw new InvalidOperationException("Property setting not found for: " + propertyInfo?.Name); } internal static MethodInfo? GetPropertySetter(PropertyInfo propertyInfo, Type type) { if (propertyInfo.DeclaringType == type) return propertyInfo.GetSetMethod(true); return propertyInfo.DeclaringType!.GetProperty( propertyInfo.Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, Type.DefaultBinder, propertyInfo.PropertyType, propertyInfo.GetIndexParameters().Select(p => p.ParameterType).ToArray(), null)!.GetSetMethod(true); } internal static List GetSettableProps(Type t) { return t .GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) .Where(p => GetPropertySetter(p, t) is not null) .ToList(); } internal static List GetSettableFields(Type t) { return t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).ToList(); } /// /// Finds best constructor /// /// DataReader column names /// DataReader column types /// Matching constructor or default one public ConstructorInfo? FindConstructor(string[] names, Type[] types) { var constructors = _type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (ConstructorInfo ctor in constructors.OrderBy(c => c.IsPublic ? 0 : (c.IsPrivate ? 2 : 1)).ThenBy(c => c.GetParameters().Length)) { ParameterInfo[] ctorParameters = ctor.GetParameters(); if (ctorParameters.Length == 0) return ctor; if (ctorParameters.Length != types.Length) continue; int i = 0; for (; i < ctorParameters.Length; i++) { if (EqualsCI(ctorParameters[i].Name, names[i])) { } // exact match else if (MatchNamesWithUnderscores && EqualsCIU(ctorParameters[i].Name, names[i])) { } // match after applying underscores else { // not a name match break; } if (types[i] == typeof(byte[]) && ctorParameters[i].ParameterType.FullName == SqlMapper.LinqBinary) continue; var unboxedType = Nullable.GetUnderlyingType(ctorParameters[i].ParameterType) ?? ctorParameters[i].ParameterType; if ((unboxedType != types[i] && !SqlMapper.HasTypeHandler(unboxedType)) && !(unboxedType.IsEnum && Enum.GetUnderlyingType(unboxedType) == types[i]) && !(unboxedType == typeof(char) && types[i] == typeof(string)) && !(unboxedType.IsEnum && types[i] == typeof(string))) { break; } } if (i == ctorParameters.Length) return ctor; } return null; } /// /// Returns the constructor, if any, that has the ExplicitConstructorAttribute on it. /// public ConstructorInfo? FindExplicitConstructor() { var constructors = _type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); var withAttr = constructors.Where(c => c.GetCustomAttributes(typeof(ExplicitConstructorAttribute), true).Length > 0).ToList(); if (withAttr.Count == 1) { return withAttr[0]; } return null; } /// /// Gets mapping for constructor parameter /// /// Constructor to resolve /// DataReader column name /// Mapping implementation public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName) { var param = MatchFirstOrDefault(constructor.GetParameters(), columnName, static p => p.Name) ?? Throw(columnName); return new SimpleMemberMap(columnName, param); static ParameterInfo Throw(string name) => throw new ArgumentException("Constructor parameter not found for " + name); } /// /// Gets member mapping for column /// /// DataReader column name /// Mapping implementation public SqlMapper.IMemberMap? GetMember(string columnName) { var property = MatchFirstOrDefault(Properties, columnName, static p => p.Name); if (property is not null) return new SimpleMemberMap(columnName, property); // roslyn automatically implemented properties, in particular for get-only properties: <{Name}>k__BackingField; var backingFieldName = "<" + columnName + ">k__BackingField"; // preference order is: // exact match over underscore match, exact case over wrong case, backing fields over regular fields, match-inc-underscores over match-exc-underscores var field = _fields.Find(p => string.Equals(p.Name, columnName, StringComparison.Ordinal)) ?? _fields.Find(p => string.Equals(p.Name, backingFieldName, StringComparison.Ordinal)) ?? _fields.Find(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase)) ?? _fields.Find(p => string.Equals(p.Name, backingFieldName, StringComparison.OrdinalIgnoreCase)); if (field is null && MatchNamesWithUnderscores) { var effectiveColumnName = columnName.Replace("_", ""); backingFieldName = "<" + effectiveColumnName + ">k__BackingField"; field = _fields.Find(p => string.Equals(p.Name, effectiveColumnName, StringComparison.Ordinal)) ?? _fields.Find(p => string.Equals(p.Name, backingFieldName, StringComparison.Ordinal)) ?? _fields.Find(p => string.Equals(p.Name, effectiveColumnName, StringComparison.OrdinalIgnoreCase)) ?? _fields.Find(p => string.Equals(p.Name, backingFieldName, StringComparison.OrdinalIgnoreCase)); } if (field is not null) return new SimpleMemberMap(columnName, field); return null; } /// /// Should column names like User_Id be allowed to match properties/fields like UserId ? /// public static bool MatchNamesWithUnderscores { get; set; } static T? MatchFirstOrDefault(IList? members, string? name, Func selector) where T : class { if (members is { Count: > 0 }) { // try exact first foreach (var member in members) { if (string.Equals(name, selector(member), StringComparison.Ordinal)) { return member; } } // then exact ignoring case foreach (var member in members) { if (string.Equals(name, selector(member), StringComparison.OrdinalIgnoreCase)) { return member; } } if (MatchNamesWithUnderscores) { // same again, minus underscore delta name = name?.Replace("_", ""); // match normalized column name vs actual property name foreach (var member in members) { if (string.Equals(name, selector(member), StringComparison.Ordinal)) { return member; } } foreach (var member in members) { if (string.Equals(name, selector(member), StringComparison.OrdinalIgnoreCase)) { return member; } } // match normalized column name vs normalized property name foreach (var member in members) { if (string.Equals(name, selector(member)?.Replace("_", ""), StringComparison.Ordinal)) { return member; } } foreach (var member in members) { if (string.Equals(name, selector(member)?.Replace("_", ""), StringComparison.OrdinalIgnoreCase)) { return member; } } } } return null; } internal static bool EqualsCI(string? x, string? y) => string.Equals(x, y, StringComparison.OrdinalIgnoreCase); internal static bool EqualsCIU(string? x, string? y) => string.Equals(x?.Replace("_", ""), y?.Replace("_", ""), StringComparison.OrdinalIgnoreCase); /// /// The settable properties for this typemap /// public List Properties { get; } } } ================================================ FILE: Dapper/DynamicParameters.CachedOutputSetters.cs ================================================ using System.Collections; namespace Dapper { public partial class DynamicParameters { // The type here is used to differentiate the cache by type via generics // ReSharper disable once UnusedTypeParameter internal static class CachedOutputSetters { // Intentional, abusing generics to get our cache splits // ReSharper disable once StaticMemberInGenericType public static readonly Hashtable Cache = new Hashtable(); } } } ================================================ FILE: Dapper/DynamicParameters.ParamInfo.cs ================================================ using System; using System.Data; namespace Dapper { public partial class DynamicParameters { private sealed class ParamInfo { public string Name { get; set; } = null!; public object? Value { get; set; } public ParameterDirection ParameterDirection { get; set; } public DbType? DbType { get; set; } public int? Size { get; set; } public IDbDataParameter AttachedParam { get; set; } = null!; internal Action? OutputCallback { get; set; } internal object OutputTarget { get; set; } = null!; internal bool CameFromTemplate { get; set; } public byte? Precision { get; set; } public byte? Scale { get; set; } } } } ================================================ FILE: Dapper/DynamicParameters.cs ================================================ using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; namespace Dapper { /// /// A bag of parameters that can be passed to the Dapper Query and Execute methods /// public partial class DynamicParameters : SqlMapper.IDynamicParameters, SqlMapper.IParameterLookup, SqlMapper.IParameterCallbacks { internal const DbType EnumerableMultiParameter = (DbType)(-1); private static readonly Dictionary> paramReaderCache = new Dictionary>(); private readonly Dictionary parameters = new Dictionary(); private List? templates; object? SqlMapper.IParameterLookup.this[string name] => parameters.TryGetValue(name, out ParamInfo? param) ? param.Value : null; /// /// construct a dynamic parameter bag /// public DynamicParameters() { RemoveUnused = true; } /// /// construct a dynamic parameter bag /// /// can be an anonymous type or a DynamicParameters bag public DynamicParameters(object? template) { RemoveUnused = true; AddDynamicParams(template); } /// /// Append a whole object full of params to the dynamic /// EG: AddDynamicParams(new {A = 1, B = 2}) // will add property A and B to the dynamic /// /// public void AddDynamicParams(object? param) { var obj = param; if (obj is not null) { if (obj is DynamicParameters subDynamic) { if (subDynamic.parameters is not null) { foreach (var kvp in subDynamic.parameters) { parameters.Add(kvp.Key, kvp.Value); } } if (subDynamic.templates is not null) { templates ??= new List(); foreach (var t in subDynamic.templates) { templates.Add(t); } } } else { if (obj is IEnumerable> dictionary) { foreach (var kvp in dictionary) { Add(kvp.Key, kvp.Value, null, null, null); } } else { templates ??= new List(); templates.Add(obj); } } } } /// /// Add a parameter to this dynamic parameter list. /// /// The name of the parameter. /// The value of the parameter. /// The type of the parameter. /// The in or out direction of the parameter. /// The size of the parameter. public void Add(string name, object? value, DbType? dbType, ParameterDirection? direction, int? size) { parameters[Clean(name)] = new ParamInfo { Name = name, Value = value, ParameterDirection = direction ?? ParameterDirection.Input, DbType = dbType, Size = size }; } /// /// Add a parameter to this dynamic parameter list. /// /// The name of the parameter. /// The value of the parameter. /// The type of the parameter. /// The in or out direction of the parameter. /// The size of the parameter. /// The precision of the parameter. /// The scale of the parameter. public void Add(string name, object? value = null, DbType? dbType = null, ParameterDirection? direction = null, int? size = null, byte? precision = null, byte? scale = null) { parameters[Clean(name)] = new ParamInfo { Name = name, Value = value, ParameterDirection = direction ?? ParameterDirection.Input, DbType = dbType, Size = size, Precision = precision, Scale = scale }; } private static string Clean(string name) { if (!string.IsNullOrEmpty(name)) { switch (name[0]) { case '@': case ':': case '?': return name.Substring(1); } } return name; } void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Identity identity) { AddParameters(command, identity); } /// /// If true, the command-text is inspected and only values that are clearly used are included on the connection /// public bool RemoveUnused { get; set; } internal static bool ShouldSetDbType(DbType? dbType) => dbType.HasValue && dbType.GetValueOrDefault() != EnumerableMultiParameter; internal static bool ShouldSetDbType(DbType dbType) => dbType != EnumerableMultiParameter; // just in case called with non-nullable /// /// Add all the parameters needed to the command just before it executes /// /// The raw command prior to execution /// Information about the query protected void AddParameters(IDbCommand command, SqlMapper.Identity identity) { var literals = SqlMapper.GetLiteralTokens(identity.Sql); if (templates is not null) { foreach (var template in templates) { var newIdent = identity.ForDynamicParameters(template.GetType()); Action appender; lock (paramReaderCache) { if (!paramReaderCache.TryGetValue(newIdent, out appender!)) { appender = SqlMapper.CreateParamInfoGenerator(newIdent, true, RemoveUnused, literals); paramReaderCache[newIdent] = appender; } } appender(command, template); } // The parameters were added to the command, but not the // DynamicParameters until now. foreach (IDbDataParameter param in command.Parameters) { // If someone makes a DynamicParameters with a template, // then explicitly adds a parameter of a matching name, // it will already exist in 'parameters'. if (!parameters.ContainsKey(param.ParameterName)) { parameters.Add(param.ParameterName, new ParamInfo { AttachedParam = param, CameFromTemplate = true, DbType = param.DbType, Name = param.ParameterName, ParameterDirection = param.Direction, Size = param.Size, Value = param.Value }); } } // Now that the parameters are added to the command, let's place our output callbacks var tmp = outputCallbacks; if (tmp is not null) { foreach (var generator in tmp) { generator(); } } } foreach (var param in parameters.Values) { if (param.CameFromTemplate) continue; var dbType = param.DbType; var val = param.Value; string name = Clean(param.Name); var isCustomQueryParameter = val is SqlMapper.ICustomQueryParameter; SqlMapper.ITypeHandler? handler = null; if (dbType is null && val is not null && !isCustomQueryParameter) { #pragma warning disable 618 dbType = SqlMapper.LookupDbType(val.GetType(), name, true, out handler); #pragma warning disable 618 } if (isCustomQueryParameter) { ((SqlMapper.ICustomQueryParameter)val!).AddParameter(command, name); } else if (dbType == EnumerableMultiParameter) { #pragma warning disable 612, 618 SqlMapper.PackListParameters(command, name, val); #pragma warning restore 612, 618 } else { bool add = !command.Parameters.Contains(name); IDbDataParameter p; if (add) { p = command.CreateParameter(); p.ParameterName = name; } else { p = (IDbDataParameter)command.Parameters[name]; } p.Direction = param.ParameterDirection; if (handler is null) { #pragma warning disable 0618 p.Value = SqlMapper.SanitizeParameterValue(val); #pragma warning restore 0618 if (ShouldSetDbType(dbType) && p.DbType != dbType.GetValueOrDefault()) { p.DbType = dbType.GetValueOrDefault(); } var s = val as string; if (s?.Length <= DbString.DefaultLength) { p.Size = DbString.DefaultLength; } if (param.Size is not null) p.Size = param.Size.Value; if (param.Precision is not null) p.Precision = param.Precision.Value; if (param.Scale is not null) p.Scale = param.Scale.Value; } else { if (ShouldSetDbType(dbType)) p.DbType = dbType.GetValueOrDefault(); if (param.Size is not null) p.Size = param.Size.Value; if (param.Precision is not null) p.Precision = param.Precision.Value; if (param.Scale is not null) p.Scale = param.Scale.Value; handler.SetValue(p, val ?? DBNull.Value); } if (add) { command.Parameters.Add(p); } param.AttachedParam = p; } } // note: most non-privileged implementations would use: this.ReplaceLiterals(command); if (literals.Count != 0) SqlMapper.ReplaceLiterals(this, command, literals); } /// /// All the names of the param in the bag, use Get to yank them out /// public IEnumerable ParameterNames => parameters.Select(p => p.Key); /// /// Get the value of a parameter /// /// /// /// The value, note DBNull.Value is not returned, instead the value is returned as null public T Get(string name) { var paramInfo = parameters[Clean(name)]; var attachedParam = paramInfo.AttachedParam; object? val = attachedParam is null ? paramInfo.Value : attachedParam.Value; if (val == DBNull.Value) { if (default(T) is not null) { throw new ApplicationException("Attempting to cast a DBNull to a non nullable type! Note that out/return parameters will not have updated values until the data stream completes (after the 'foreach' for Query(..., buffered: false), or after the GridReader has been disposed for QueryMultiple)"); } return default!; } return (T)val!; } /// /// Allows you to automatically populate a target property/field from output parameters. It actually /// creates an InputOutput parameter, so you can still pass data in. /// /// /// The object whose property/field you wish to populate. /// A MemberExpression targeting a property/field of the target (or descendant thereof.) /// /// The size to set on the parameter. Defaults to 0, or DbString.DefaultLength in case of strings. /// The DynamicParameters instance public DynamicParameters Output(T target, Expression> expression, DbType? dbType = null, int? size = null) { static void ThrowInvalidChain() => throw new InvalidOperationException($"Expression must be a property/field chain off of a(n) {typeof(T).Name} instance"); // Is it even a MemberExpression? #pragma warning disable IDE0019 // Use pattern matching - already complex enough var lastMemberAccess = expression.Body as MemberExpression; #pragma warning restore IDE0019 // Use pattern matching if (lastMemberAccess is null || (!(lastMemberAccess.Member is PropertyInfo) && !(lastMemberAccess.Member is FieldInfo))) { if (expression.Body.NodeType == ExpressionType.Convert && expression.Body.Type == typeof(object) && ((UnaryExpression)expression.Body).Operand is MemberExpression member) { // It's got to be unboxed lastMemberAccess = member; } else { ThrowInvalidChain(); } } // Does the chain consist of MemberExpressions leading to a ParameterExpression of type T? MemberExpression? diving = lastMemberAccess; // Retain a list of member names and the member expressions so we can rebuild the chain. List names = new List(); List chain = new List(); do { // Insert the names in the right order so expression // "Post.Author.Name" becomes parameter "PostAuthorName" names.Insert(0, diving?.Member.Name); chain.Insert(0, diving); #pragma warning disable IDE0019 // use pattern matching; this is fine! var constant = diving?.Expression as ParameterExpression; diving = diving?.Expression as MemberExpression; #pragma warning restore IDE0019 // use pattern matching if (constant is not null && constant.Type == typeof(T)) { break; } else if (diving is null || (!(diving.Member is PropertyInfo) && !(diving.Member is FieldInfo))) { ThrowInvalidChain(); } } while (diving is not null); var dynamicParamName = string.Concat(names.ToArray()); // Before we get all emitty... var lookup = string.Join("|", names.ToArray()); var cache = CachedOutputSetters.Cache; var setter = (Action?)cache[lookup]; if (setter is not null) goto MAKECALLBACK; // Come on let's build a method, let's build it, let's build it now! var dm = new DynamicMethod("ExpressionParam" + Guid.NewGuid().ToString(), null, new[] { typeof(object), GetType() }, true); var il = dm.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); // [object] il.Emit(OpCodes.Castclass, typeof(T)); // [T] // Count - 1 to skip the last member access for (var i = 0; i < chain.Count - 1; i++) { var member = chain[i]?.Member; if (member is PropertyInfo info) { var get = info.GetGetMethod(true); il.Emit(OpCodes.Callvirt, get!); // [Member{i}] } else // Else it must be a field! { il.Emit(OpCodes.Ldfld, (FieldInfo)member!); // [Member{i}] } } var paramGetter = GetType().GetMethod("Get", new Type[] { typeof(string) })!.MakeGenericMethod(lastMemberAccess!.Type); il.Emit(OpCodes.Ldarg_1); // [target] [DynamicParameters] il.Emit(OpCodes.Ldstr, dynamicParamName); // [target] [DynamicParameters] [ParamName] il.Emit(OpCodes.Callvirt, paramGetter); // [target] [value], it's already typed thanks to generic method // GET READY var lastMember = lastMemberAccess.Member; if (lastMember is PropertyInfo property) { var set = property.GetSetMethod(true); il.Emit(OpCodes.Callvirt, set!); // SET } else { il.Emit(OpCodes.Stfld, (FieldInfo)lastMember); // SET } il.Emit(OpCodes.Ret); // GO setter = (Action)dm.CreateDelegate(typeof(Action)); lock (cache) { cache[lookup] = setter; } // Queue the preparation to be fired off when adding parameters to the DbCommand MAKECALLBACK: (outputCallbacks ??= new List()).Add(() => { // Finally, prep the parameter and attach the callback to it var targetMemberType = lastMemberAccess?.Type; int sizeToSet = (!size.HasValue && targetMemberType == typeof(string)) ? DbString.DefaultLength : size ?? 0; if (parameters.TryGetValue(dynamicParamName, out ParamInfo? parameter)) { parameter.ParameterDirection = parameter.AttachedParam.Direction = ParameterDirection.InputOutput; if (parameter.AttachedParam.Size == 0) { parameter.Size = parameter.AttachedParam.Size = sizeToSet; } } else { // CameFromTemplate property would not apply here because this new param // Still needs to be added to the command Add(dynamicParamName, expression.Compile().Invoke(target), dbType, ParameterDirection.InputOutput, sizeToSet); } parameter = parameters[dynamicParamName]; parameter.OutputCallback = setter; parameter.OutputTarget = target!; }); return this; } private List? outputCallbacks; void SqlMapper.IParameterCallbacks.OnCompleted() { foreach (var param in from p in parameters select p.Value) { param.OutputCallback?.Invoke(param.OutputTarget, this); } } } } ================================================ FILE: Dapper/ExplicitConstructorAttribute.cs ================================================ using System; namespace Dapper { /// /// Tell Dapper to use an explicit constructor, passing nulls or 0s for all parameters /// /// /// Usage on methods is limited to the usage with Dapper.AOT (https://github.com/DapperLib/DapperAOT) /// [AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method, AllowMultiple = false)] public sealed class ExplicitConstructorAttribute : Attribute { } } ================================================ FILE: Dapper/Extensions.cs ================================================ using System; using System.Threading.Tasks; namespace Dapper { internal static class Extensions { /// /// Creates a with a less specific generic parameter that perfectly mirrors the /// state of the specified . /// internal static Task CastResult(this Task task) where TFrom : TTo { if (task is null) throw new ArgumentNullException(nameof(task)); if (task.Status == TaskStatus.RanToCompletion) return Task.FromResult((TTo)task.Result); var source = new TaskCompletionSource(); task.ContinueWith(OnTaskCompleted, state: source, TaskContinuationOptions.ExecuteSynchronously); return source.Task; } private static void OnTaskCompleted(Task completedTask, object? state) where TFrom : TTo { var source = (TaskCompletionSource)state!; switch (completedTask.Status) { case TaskStatus.RanToCompletion: source.SetResult(completedTask.Result); break; case TaskStatus.Canceled: source.SetCanceled(); break; case TaskStatus.Faulted: source.SetException(completedTask.Exception!.InnerExceptions); break; } } } } ================================================ FILE: Dapper/FeatureSupport.cs ================================================ using System; using System.Data; namespace Dapper { /// /// Handles variances in features per DBMS /// internal class FeatureSupport { private static readonly FeatureSupport Default = new FeatureSupport(false), Postgres = new FeatureSupport(true), ClickHouse = new FeatureSupport(true); /// /// Gets the feature set based on the passed connection /// /// The connection to get supported features for. public static FeatureSupport Get(IDbConnection? connection) { string? name = connection?.GetType().Name; if (string.Equals(name, "npgsqlconnection", StringComparison.OrdinalIgnoreCase)) return Postgres; if (string.Equals(name, "clickhouseconnection", StringComparison.OrdinalIgnoreCase)) return ClickHouse; return Default; } private FeatureSupport(bool arrays) { Arrays = arrays; } /// /// True if the db supports array columns e.g. Postgresql /// public bool Arrays { get; } } } ================================================ FILE: Dapper/Global.cs ================================================ using System.Runtime.CompilerServices; #if !STRONG_NAME [assembly: InternalsVisibleTo("Dapper.Tests")] #endif ================================================ FILE: Dapper/NRT.cs ================================================ #if !NET5_0_OR_GREATER namespace System.Diagnostics.CodeAnalysis { [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] internal sealed class NotNullWhenAttribute : Attribute { public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; public bool ReturnValue { get; } } } #endif ================================================ FILE: Dapper/Properties/AssemblyInfo.cs ================================================ [module: System.Runtime.CompilerServices.SkipLocalsInit] #if !NET5_0_OR_GREATER namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event | AttributeTargets.Interface, Inherited = false)] internal sealed class SkipLocalsInitAttribute : Attribute {} } namespace System.Diagnostics.CodeAnalysis { [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] internal sealed class MemberNotNullAttribute : Attribute { public MemberNotNullAttribute(string member) {} public MemberNotNullAttribute(params string[] members) {} } } #endif ================================================ FILE: Dapper/PublicAPI/net461/PublicAPI.Shipped.txt ================================================ #nullable enable ================================================ FILE: Dapper/PublicAPI/net461/PublicAPI.Unshipped.txt ================================================ #nullable enable ================================================ FILE: Dapper/PublicAPI/net8.0/PublicAPI.Shipped.txt ================================================ #nullable enable ================================================ FILE: Dapper/PublicAPI/net8.0/PublicAPI.Unshipped.txt ================================================ #nullable enable ================================================ FILE: Dapper/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt ================================================ #nullable enable ================================================ FILE: Dapper/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt ================================================ #nullable enable ================================================ FILE: Dapper/PublicAPI.Shipped.txt ================================================ #nullable enable abstract Dapper.SqlMapper.StringTypeHandler.Format(T xml) -> string! abstract Dapper.SqlMapper.StringTypeHandler.Parse(string! xml) -> T abstract Dapper.SqlMapper.TypeHandler.Parse(object! value) -> T? abstract Dapper.SqlMapper.TypeHandler.SetValue(System.Data.IDbDataParameter! parameter, T? value) -> void const Dapper.DbString.DefaultLength = 4000 -> int Dapper.CommandDefinition Dapper.CommandDefinition.Buffered.get -> bool Dapper.CommandDefinition.CancellationToken.get -> System.Threading.CancellationToken Dapper.CommandDefinition.CommandDefinition() -> void Dapper.CommandDefinition.CommandDefinition(string! commandText, object? parameters = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null, Dapper.CommandFlags flags = Dapper.CommandFlags.Buffered, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> void Dapper.CommandDefinition.CommandText.get -> string! Dapper.CommandDefinition.CommandTimeout.get -> int? Dapper.CommandDefinition.CommandType.get -> System.Data.CommandType? Dapper.CommandDefinition.Flags.get -> Dapper.CommandFlags Dapper.CommandDefinition.Parameters.get -> object? Dapper.CommandDefinition.Pipelined.get -> bool Dapper.CommandDefinition.Transaction.get -> System.Data.IDbTransaction? Dapper.CommandFlags Dapper.CommandFlags.Buffered = 1 -> Dapper.CommandFlags Dapper.CommandFlags.NoCache = 4 -> Dapper.CommandFlags Dapper.CommandFlags.None = 0 -> Dapper.CommandFlags Dapper.CommandFlags.Pipelined = 2 -> Dapper.CommandFlags Dapper.CustomPropertyTypeMap Dapper.CustomPropertyTypeMap.CustomPropertyTypeMap(System.Type! type, System.Func! propertySelector) -> void Dapper.CustomPropertyTypeMap.FindConstructor(string![]! names, System.Type![]! types) -> System.Reflection.ConstructorInfo? Dapper.CustomPropertyTypeMap.FindExplicitConstructor() -> System.Reflection.ConstructorInfo? Dapper.CustomPropertyTypeMap.GetConstructorParameter(System.Reflection.ConstructorInfo! constructor, string! columnName) -> Dapper.SqlMapper.IMemberMap! Dapper.CustomPropertyTypeMap.GetMember(string! columnName) -> Dapper.SqlMapper.IMemberMap? Dapper.DbString Dapper.DbString.AddParameter(System.Data.IDbCommand! command, string! name) -> void Dapper.DbString.DbString() -> void Dapper.DbString.DbString(string? value, int length = -1) -> void Dapper.DbString.IsAnsi.get -> bool Dapper.DbString.IsAnsi.set -> void Dapper.DbString.IsFixedLength.get -> bool Dapper.DbString.IsFixedLength.set -> void Dapper.DbString.Length.get -> int Dapper.DbString.Length.set -> void Dapper.DbString.Value.get -> string? Dapper.DbString.Value.set -> void Dapper.DefaultTypeMap Dapper.DefaultTypeMap.DefaultTypeMap(System.Type! type) -> void Dapper.DefaultTypeMap.FindConstructor(string![]! names, System.Type![]! types) -> System.Reflection.ConstructorInfo? Dapper.DefaultTypeMap.FindExplicitConstructor() -> System.Reflection.ConstructorInfo? Dapper.DefaultTypeMap.GetConstructorParameter(System.Reflection.ConstructorInfo! constructor, string! columnName) -> Dapper.SqlMapper.IMemberMap! Dapper.DefaultTypeMap.GetMember(string! columnName) -> Dapper.SqlMapper.IMemberMap? Dapper.DefaultTypeMap.Properties.get -> System.Collections.Generic.List! Dapper.DynamicParameters Dapper.DynamicParameters.Add(string! name, object? value = null, System.Data.DbType? dbType = null, System.Data.ParameterDirection? direction = null, int? size = null, byte? precision = null, byte? scale = null) -> void Dapper.DynamicParameters.Add(string! name, object? value, System.Data.DbType? dbType, System.Data.ParameterDirection? direction, int? size) -> void Dapper.DynamicParameters.AddDynamicParams(object? param) -> void Dapper.DynamicParameters.AddParameters(System.Data.IDbCommand! command, Dapper.SqlMapper.Identity! identity) -> void Dapper.DynamicParameters.DynamicParameters() -> void Dapper.DynamicParameters.DynamicParameters(object? template) -> void Dapper.DynamicParameters.Get(string! name) -> T Dapper.DynamicParameters.Output(T target, System.Linq.Expressions.Expression!>! expression, System.Data.DbType? dbType = null, int? size = null) -> Dapper.DynamicParameters! Dapper.DynamicParameters.ParameterNames.get -> System.Collections.Generic.IEnumerable! Dapper.DynamicParameters.RemoveUnused.get -> bool Dapper.DynamicParameters.RemoveUnused.set -> void Dapper.ExplicitConstructorAttribute Dapper.ExplicitConstructorAttribute.ExplicitConstructorAttribute() -> void Dapper.IWrappedDataReader Dapper.IWrappedDataReader.Command.get -> System.Data.IDbCommand! Dapper.IWrappedDataReader.Reader.get -> System.Data.IDataReader! Dapper.SqlMapper Dapper.SqlMapper.GridReader Dapper.SqlMapper.GridReader.CancellationToken.get -> System.Threading.CancellationToken Dapper.SqlMapper.GridReader.Command.get -> System.Data.IDbCommand! Dapper.SqlMapper.GridReader.Command.set -> void Dapper.SqlMapper.GridReader.Dispose() -> void Dapper.SqlMapper.GridReader.DisposeAsync() -> System.Threading.Tasks.ValueTask Dapper.SqlMapper.GridReader.GridReader(System.Data.IDbCommand! command, System.Data.Common.DbDataReader! reader, Dapper.SqlMapper.Identity? identity, System.Action? onCompleted = null, object? state = null, bool addToCache = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> void Dapper.SqlMapper.GridReader.IsConsumed.get -> bool Dapper.SqlMapper.GridReader.OnAfterGrid(int index) -> void Dapper.SqlMapper.GridReader.OnAfterGridAsync(int index) -> System.Threading.Tasks.Task! Dapper.SqlMapper.GridReader.OnBeforeGrid() -> int Dapper.SqlMapper.GridReader.Read(bool buffered = true) -> System.Collections.Generic.IEnumerable! Dapper.SqlMapper.GridReader.Read(System.Type! type, bool buffered = true) -> System.Collections.Generic.IEnumerable! Dapper.SqlMapper.GridReader.Read(bool buffered = true) -> System.Collections.Generic.IEnumerable! Dapper.SqlMapper.GridReader.Read(System.Func! func, string! splitOn = "id", bool buffered = true) -> System.Collections.Generic.IEnumerable! Dapper.SqlMapper.GridReader.Read(System.Func! func, string! splitOn = "id", bool buffered = true) -> System.Collections.Generic.IEnumerable! Dapper.SqlMapper.GridReader.Read(System.Func! func, string! splitOn = "id", bool buffered = true) -> System.Collections.Generic.IEnumerable! Dapper.SqlMapper.GridReader.Read(System.Func! func, string! splitOn = "id", bool buffered = true) -> System.Collections.Generic.IEnumerable! Dapper.SqlMapper.GridReader.Read(System.Func! func, string! splitOn = "id", bool buffered = true) -> System.Collections.Generic.IEnumerable! Dapper.SqlMapper.GridReader.Read(System.Func! func, string! splitOn = "id", bool buffered = true) -> System.Collections.Generic.IEnumerable! Dapper.SqlMapper.GridReader.Read(System.Type![]! types, System.Func! map, string! splitOn = "id", bool buffered = true) -> System.Collections.Generic.IEnumerable! Dapper.SqlMapper.GridReader.ReadAsync(bool buffered = true) -> System.Threading.Tasks.Task!>! Dapper.SqlMapper.GridReader.ReadAsync(System.Type! type, bool buffered = true) -> System.Threading.Tasks.Task!>! Dapper.SqlMapper.GridReader.ReadAsync(bool buffered = true) -> System.Threading.Tasks.Task!>! Dapper.SqlMapper.GridReader.Reader.get -> System.Data.Common.DbDataReader! Dapper.SqlMapper.GridReader.ReadFirst() -> dynamic! Dapper.SqlMapper.GridReader.ReadFirst(System.Type! type) -> object! Dapper.SqlMapper.GridReader.ReadFirst() -> T Dapper.SqlMapper.GridReader.ReadFirstAsync() -> System.Threading.Tasks.Task! Dapper.SqlMapper.GridReader.ReadFirstAsync(System.Type! type) -> System.Threading.Tasks.Task! Dapper.SqlMapper.GridReader.ReadFirstAsync() -> System.Threading.Tasks.Task! Dapper.SqlMapper.GridReader.ReadFirstOrDefault() -> dynamic? Dapper.SqlMapper.GridReader.ReadFirstOrDefault(System.Type! type) -> object? Dapper.SqlMapper.GridReader.ReadFirstOrDefault() -> T? Dapper.SqlMapper.GridReader.ReadFirstOrDefaultAsync() -> System.Threading.Tasks.Task! Dapper.SqlMapper.GridReader.ReadFirstOrDefaultAsync(System.Type! type) -> System.Threading.Tasks.Task! Dapper.SqlMapper.GridReader.ReadFirstOrDefaultAsync() -> System.Threading.Tasks.Task! Dapper.SqlMapper.GridReader.ReadSingle() -> dynamic! Dapper.SqlMapper.GridReader.ReadSingle(System.Type! type) -> object! Dapper.SqlMapper.GridReader.ReadSingle() -> T Dapper.SqlMapper.GridReader.ReadSingleAsync() -> System.Threading.Tasks.Task! Dapper.SqlMapper.GridReader.ReadSingleAsync(System.Type! type) -> System.Threading.Tasks.Task! Dapper.SqlMapper.GridReader.ReadSingleAsync() -> System.Threading.Tasks.Task! Dapper.SqlMapper.GridReader.ReadSingleOrDefault() -> dynamic? Dapper.SqlMapper.GridReader.ReadSingleOrDefault(System.Type! type) -> object? Dapper.SqlMapper.GridReader.ReadSingleOrDefault() -> T? Dapper.SqlMapper.GridReader.ReadSingleOrDefaultAsync() -> System.Threading.Tasks.Task! Dapper.SqlMapper.GridReader.ReadSingleOrDefaultAsync(System.Type! type) -> System.Threading.Tasks.Task! Dapper.SqlMapper.GridReader.ReadSingleOrDefaultAsync() -> System.Threading.Tasks.Task! Dapper.SqlMapper.GridReader.ReadUnbufferedAsync() -> System.Collections.Generic.IAsyncEnumerable! Dapper.SqlMapper.GridReader.ReadUnbufferedAsync() -> System.Collections.Generic.IAsyncEnumerable! Dapper.SqlMapper.GridReader.ResultIndex.get -> int Dapper.SqlMapper.ICustomQueryParameter Dapper.SqlMapper.ICustomQueryParameter.AddParameter(System.Data.IDbCommand! command, string! name) -> void Dapper.SqlMapper.Identity Dapper.SqlMapper.Identity.Equals(Dapper.SqlMapper.Identity? other) -> bool Dapper.SqlMapper.Identity.ForDynamicParameters(System.Type! type) -> Dapper.SqlMapper.Identity! Dapper.SqlMapper.IDynamicParameters Dapper.SqlMapper.IDynamicParameters.AddParameters(System.Data.IDbCommand! command, Dapper.SqlMapper.Identity! identity) -> void Dapper.SqlMapper.IMemberMap Dapper.SqlMapper.IMemberMap.ColumnName.get -> string! Dapper.SqlMapper.IMemberMap.Field.get -> System.Reflection.FieldInfo? Dapper.SqlMapper.IMemberMap.MemberType.get -> System.Type! Dapper.SqlMapper.IMemberMap.Parameter.get -> System.Reflection.ParameterInfo? Dapper.SqlMapper.IMemberMap.Property.get -> System.Reflection.PropertyInfo? Dapper.SqlMapper.IParameterCallbacks Dapper.SqlMapper.IParameterCallbacks.OnCompleted() -> void Dapper.SqlMapper.IParameterLookup Dapper.SqlMapper.IParameterLookup.this[string! name].get -> object? Dapper.SqlMapper.ITypeHandler Dapper.SqlMapper.ITypeHandler.Parse(System.Type! destinationType, object! value) -> object? Dapper.SqlMapper.ITypeHandler.SetValue(System.Data.IDbDataParameter! parameter, object! value) -> void Dapper.SqlMapper.ITypeMap Dapper.SqlMapper.ITypeMap.FindConstructor(string![]! names, System.Type![]! types) -> System.Reflection.ConstructorInfo? Dapper.SqlMapper.ITypeMap.FindExplicitConstructor() -> System.Reflection.ConstructorInfo? Dapper.SqlMapper.ITypeMap.GetConstructorParameter(System.Reflection.ConstructorInfo! constructor, string! columnName) -> Dapper.SqlMapper.IMemberMap? Dapper.SqlMapper.ITypeMap.GetMember(string! columnName) -> Dapper.SqlMapper.IMemberMap? Dapper.SqlMapper.Settings Dapper.SqlMapper.StringTypeHandler Dapper.SqlMapper.StringTypeHandler.StringTypeHandler() -> void Dapper.SqlMapper.TypeHandler Dapper.SqlMapper.TypeHandler.TypeHandler() -> void Dapper.SqlMapper.TypeHandlerCache Dapper.SqlMapper.UdtTypeHandler Dapper.SqlMapper.UdtTypeHandler.UdtTypeHandler(string! udtTypeName) -> void override Dapper.DbString.ToString() -> string! override Dapper.SqlMapper.Identity.Equals(object? obj) -> bool override Dapper.SqlMapper.Identity.GetHashCode() -> int override Dapper.SqlMapper.Identity.ToString() -> string! override Dapper.SqlMapper.StringTypeHandler.Parse(object! value) -> T override Dapper.SqlMapper.StringTypeHandler.SetValue(System.Data.IDbDataParameter! parameter, T? value) -> void readonly Dapper.SqlMapper.Identity.commandType -> System.Data.CommandType? Dapper.SqlMapper.Identity.CommandType.get -> System.Data.CommandType? readonly Dapper.SqlMapper.Identity.connectionString -> string! readonly Dapper.SqlMapper.Identity.gridIndex -> int Dapper.SqlMapper.Identity.GridIndex.get -> int readonly Dapper.SqlMapper.Identity.hashCode -> int readonly Dapper.SqlMapper.Identity.parametersType -> System.Type? Dapper.SqlMapper.Identity.ParametersType.get -> System.Type? readonly Dapper.SqlMapper.Identity.sql -> string! Dapper.SqlMapper.Identity.Sql.get -> string! readonly Dapper.SqlMapper.Identity.type -> System.Type? Dapper.SqlMapper.Identity.Type.get -> System.Type? static Dapper.DbString.IsAnsiDefault.get -> bool static Dapper.DbString.IsAnsiDefault.set -> void static Dapper.DefaultTypeMap.MatchNamesWithUnderscores.get -> bool static Dapper.DefaultTypeMap.MatchNamesWithUnderscores.set -> void static Dapper.SqlMapper.AddTypeHandler(System.Type! type, Dapper.SqlMapper.ITypeHandler! handler) -> void static Dapper.SqlMapper.AddTypeHandler(Dapper.SqlMapper.TypeHandler! handler) -> void static Dapper.SqlMapper.AddTypeHandlerImpl(System.Type! type, Dapper.SqlMapper.ITypeHandler? handler, bool clone) -> void static Dapper.SqlMapper.AddTypeMap(System.Type! type, System.Data.DbType dbType) -> void static Dapper.SqlMapper.AddTypeMap(System.Type! type, System.Data.DbType dbType, bool useGetFieldValue) -> void static Dapper.SqlMapper.AsList(this System.Collections.Generic.IEnumerable? source) -> System.Collections.Generic.List! static Dapper.SqlMapper.AsTableValuedParameter(this System.Data.DataTable! table, string? typeName = null) -> Dapper.SqlMapper.ICustomQueryParameter! static Dapper.SqlMapper.AsTableValuedParameter(this System.Collections.Generic.IEnumerable! list, string? typeName = null) -> Dapper.SqlMapper.ICustomQueryParameter! static Dapper.SqlMapper.ConnectionStringComparer.get -> System.Collections.Generic.IEqualityComparer! static Dapper.SqlMapper.ConnectionStringComparer.set -> void static Dapper.SqlMapper.CreateParamInfoGenerator(Dapper.SqlMapper.Identity! identity, bool checkForDuplicates, bool removeUnused) -> System.Action! static Dapper.SqlMapper.Execute(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> int static Dapper.SqlMapper.Execute(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> int static Dapper.SqlMapper.ExecuteAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.ExecuteAsync(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.ExecuteReader(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Data.IDataReader! static Dapper.SqlMapper.ExecuteReader(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command, System.Data.CommandBehavior commandBehavior) -> System.Data.IDataReader! static Dapper.SqlMapper.ExecuteReader(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Data.IDataReader! static Dapper.SqlMapper.ExecuteReaderAsync(this System.Data.Common.DbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.ExecuteReaderAsync(this System.Data.Common.DbConnection! cnn, Dapper.CommandDefinition command, System.Data.CommandBehavior commandBehavior) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.ExecuteReaderAsync(this System.Data.Common.DbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.ExecuteReaderAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.ExecuteReaderAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command, System.Data.CommandBehavior commandBehavior) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.ExecuteReaderAsync(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.ExecuteScalar(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> object? static Dapper.SqlMapper.ExecuteScalar(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> object? static Dapper.SqlMapper.ExecuteScalar(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> T? static Dapper.SqlMapper.ExecuteScalar(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> T? static Dapper.SqlMapper.ExecuteScalarAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.ExecuteScalarAsync(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.ExecuteScalarAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.ExecuteScalarAsync(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.FindOrAddParameter(System.Data.IDataParameterCollection! parameters, System.Data.IDbCommand! command, string! name) -> System.Data.IDbDataParameter! static Dapper.SqlMapper.Format(object? value) -> string! static Dapper.SqlMapper.GetCachedSQL(int ignoreHitCountAbove = 2147483647) -> System.Collections.Generic.IEnumerable!>! static Dapper.SqlMapper.GetCachedSQLCount() -> int static Dapper.SqlMapper.GetHashCollissions() -> System.Collections.Generic.IEnumerable!>! static Dapper.SqlMapper.GetRowParser(this System.Data.Common.DbDataReader! reader, System.Type! type, int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) -> System.Func! static Dapper.SqlMapper.GetRowParser(this System.Data.IDataReader! reader, System.Type! type, int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) -> System.Func! static Dapper.SqlMapper.GetRowParser(this System.Data.Common.DbDataReader! reader, System.Type? concreteType = null, int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) -> System.Func! static Dapper.SqlMapper.GetRowParser(this System.Data.IDataReader! reader, System.Type? concreteType = null, int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) -> System.Func! static Dapper.SqlMapper.GetTypeDeserializer(System.Type! type, System.Data.Common.DbDataReader! reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false) -> System.Func! static Dapper.SqlMapper.GetTypeDeserializer(System.Type! type, System.Data.IDataReader! reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false) -> System.Func! static Dapper.SqlMapper.GetTypeMap(System.Type! type) -> Dapper.SqlMapper.ITypeMap! static Dapper.SqlMapper.GetTypeName(this System.Data.DataTable! table) -> string? static Dapper.SqlMapper.HasTypeHandler(System.Type! type) -> bool static Dapper.SqlMapper.LookupDbType(System.Type! type, string! name, bool demand, out Dapper.SqlMapper.ITypeHandler? handler) -> System.Data.DbType? static Dapper.SqlMapper.PackListParameters(System.Data.IDbCommand! command, string! namePrefix, object? value) -> void static Dapper.SqlMapper.Parse(this System.Data.IDataReader! reader) -> System.Collections.Generic.IEnumerable! static Dapper.SqlMapper.Parse(this System.Data.IDataReader! reader, System.Type! type) -> System.Collections.Generic.IEnumerable! static Dapper.SqlMapper.Parse(this System.Data.IDataReader! reader) -> System.Collections.Generic.IEnumerable! static Dapper.SqlMapper.PurgeQueryCache() -> void static Dapper.SqlMapper.Query(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable! static Dapper.SqlMapper.Query(this System.Data.IDbConnection! cnn, System.Type! type, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable! static Dapper.SqlMapper.Query(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Collections.Generic.IEnumerable! static Dapper.SqlMapper.Query(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable! static Dapper.SqlMapper.Query(this System.Data.IDbConnection! cnn, string! sql, System.Func! map, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, string! splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable! static Dapper.SqlMapper.Query(this System.Data.IDbConnection! cnn, string! sql, System.Func! map, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, string! splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable! static Dapper.SqlMapper.Query(this System.Data.IDbConnection! cnn, string! sql, System.Func! map, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, string! splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable! static Dapper.SqlMapper.Query(this System.Data.IDbConnection! cnn, string! sql, System.Func! map, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, string! splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable! static Dapper.SqlMapper.Query(this System.Data.IDbConnection! cnn, string! sql, System.Func! map, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, string! splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable! static Dapper.SqlMapper.Query(this System.Data.IDbConnection! cnn, string! sql, System.Func! map, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, string! splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable! static Dapper.SqlMapper.Query(this System.Data.IDbConnection! cnn, string! sql, System.Type![]! types, System.Func! map, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, string! splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable! static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task!>! static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task!>! static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, System.Type! type, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task!>! static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, System.Type! type, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task!>! static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task!>! static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task!>! static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command, System.Func! map, string! splitOn = "Id") -> System.Threading.Tasks.Task!>! static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, string! sql, System.Func! map, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, string! splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task!>! static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command, System.Func! map, string! splitOn = "Id") -> System.Threading.Tasks.Task!>! static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, string! sql, System.Func! map, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, string! splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task!>! static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command, System.Func! map, string! splitOn = "Id") -> System.Threading.Tasks.Task!>! static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, string! sql, System.Func! map, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, string! splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task!>! static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command, System.Func! map, string! splitOn = "Id") -> System.Threading.Tasks.Task!>! static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, string! sql, System.Func! map, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, string! splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task!>! static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command, System.Func! map, string! splitOn = "Id") -> System.Threading.Tasks.Task!>! static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, string! sql, System.Func! map, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, string! splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task!>! static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command, System.Func! map, string! splitOn = "Id") -> System.Threading.Tasks.Task!>! static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, string! sql, System.Func! map, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, string! splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task!>! static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, string! sql, System.Type![]! types, System.Func! map, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, string! splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task!>! static Dapper.SqlMapper.QueryCachePurged -> System.EventHandler? static Dapper.SqlMapper.QueryFirst(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> dynamic! static Dapper.SqlMapper.QueryFirst(this System.Data.IDbConnection! cnn, System.Type! type, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> object! static Dapper.SqlMapper.QueryFirst(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> T static Dapper.SqlMapper.QueryFirst(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> T static Dapper.SqlMapper.QueryFirstAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.QueryFirstAsync(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.QueryFirstAsync(this System.Data.IDbConnection! cnn, System.Type! type, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.QueryFirstAsync(this System.Data.IDbConnection! cnn, System.Type! type, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.QueryFirstAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.QueryFirstAsync(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.QueryFirstOrDefault(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> dynamic? static Dapper.SqlMapper.QueryFirstOrDefault(this System.Data.IDbConnection! cnn, System.Type! type, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> object? static Dapper.SqlMapper.QueryFirstOrDefault(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> T? static Dapper.SqlMapper.QueryFirstOrDefault(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> T? static Dapper.SqlMapper.QueryFirstOrDefaultAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.QueryFirstOrDefaultAsync(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.QueryFirstOrDefaultAsync(this System.Data.IDbConnection! cnn, System.Type! type, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.QueryFirstOrDefaultAsync(this System.Data.IDbConnection! cnn, System.Type! type, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.QueryFirstOrDefaultAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.QueryFirstOrDefaultAsync(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.QueryMultiple(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> Dapper.SqlMapper.GridReader! static Dapper.SqlMapper.QueryMultiple(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> Dapper.SqlMapper.GridReader! static Dapper.SqlMapper.QueryMultipleAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.QueryMultipleAsync(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.QuerySingle(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> dynamic! static Dapper.SqlMapper.QuerySingle(this System.Data.IDbConnection! cnn, System.Type! type, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> object! static Dapper.SqlMapper.QuerySingle(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> T static Dapper.SqlMapper.QuerySingle(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> T static Dapper.SqlMapper.QuerySingleAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.QuerySingleAsync(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.QuerySingleAsync(this System.Data.IDbConnection! cnn, System.Type! type, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.QuerySingleAsync(this System.Data.IDbConnection! cnn, System.Type! type, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.QuerySingleAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.QuerySingleAsync(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.QuerySingleOrDefault(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> dynamic? static Dapper.SqlMapper.QuerySingleOrDefault(this System.Data.IDbConnection! cnn, System.Type! type, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> object? static Dapper.SqlMapper.QuerySingleOrDefault(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> T? static Dapper.SqlMapper.QuerySingleOrDefault(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> T? static Dapper.SqlMapper.QuerySingleOrDefaultAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.QuerySingleOrDefaultAsync(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.QuerySingleOrDefaultAsync(this System.Data.IDbConnection! cnn, System.Type! type, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.QuerySingleOrDefaultAsync(this System.Data.IDbConnection! cnn, System.Type! type, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.QuerySingleOrDefaultAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.QuerySingleOrDefaultAsync(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.QueryUnbufferedAsync(this System.Data.Common.DbConnection! cnn, string! sql, object? param = null, System.Data.Common.DbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IAsyncEnumerable! static Dapper.SqlMapper.QueryUnbufferedAsync(this System.Data.Common.DbConnection! cnn, string! sql, object? param = null, System.Data.Common.DbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IAsyncEnumerable! static Dapper.SqlMapper.ReadChar(object! value) -> char static Dapper.SqlMapper.ReadNullableChar(object! value) -> char? static Dapper.SqlMapper.RemoveTypeMap(System.Type! type) -> void static Dapper.SqlMapper.ReplaceLiterals(this Dapper.SqlMapper.IParameterLookup! parameters, System.Data.IDbCommand! command) -> void static Dapper.SqlMapper.ResetTypeHandlers() -> void static Dapper.SqlMapper.SanitizeParameterValue(object? value) -> object! static Dapper.SqlMapper.SetDbType(System.Data.IDataParameter! parameter, object! value) -> void static Dapper.SqlMapper.Settings.ApplyNullValues.get -> bool static Dapper.SqlMapper.Settings.ApplyNullValues.set -> void static Dapper.SqlMapper.Settings.SupportLegacyParameterTokens.get -> bool static Dapper.SqlMapper.Settings.SupportLegacyParameterTokens.set -> void static Dapper.SqlMapper.Settings.CommandTimeout.get -> int? static Dapper.SqlMapper.Settings.CommandTimeout.set -> void static Dapper.SqlMapper.Settings.FetchSize.get -> long static Dapper.SqlMapper.Settings.FetchSize.set -> void static Dapper.SqlMapper.Settings.InListStringSplitCount.get -> int static Dapper.SqlMapper.Settings.InListStringSplitCount.set -> void static Dapper.SqlMapper.Settings.PadListExpansions.get -> bool static Dapper.SqlMapper.Settings.PadListExpansions.set -> void static Dapper.SqlMapper.Settings.SetDefaults() -> void static Dapper.SqlMapper.Settings.UseIncrementalPseudoPositionalParameterNames.get -> bool static Dapper.SqlMapper.Settings.UseIncrementalPseudoPositionalParameterNames.set -> void static Dapper.SqlMapper.Settings.UseSingleResultOptimization.get -> bool static Dapper.SqlMapper.Settings.UseSingleResultOptimization.set -> void static Dapper.SqlMapper.Settings.UseSingleRowOptimization.get -> bool static Dapper.SqlMapper.Settings.UseSingleRowOptimization.set -> void static Dapper.SqlMapper.SetTypeMap(System.Type! type, Dapper.SqlMapper.ITypeMap? map) -> void static Dapper.SqlMapper.SetTypeName(this System.Data.DataTable! table, string! typeName) -> void static Dapper.SqlMapper.ThrowDataException(System.Exception! ex, int index, System.Data.IDataReader! reader, object? value) -> void static Dapper.SqlMapper.ThrowNullCustomQueryParameter(string! name) -> void static Dapper.SqlMapper.TypeHandlerCache.Parse(object! value) -> T? static Dapper.SqlMapper.TypeHandlerCache.SetValue(System.Data.IDbDataParameter! parameter, object! value) -> void static Dapper.SqlMapper.TypeMapProvider -> System.Func! ================================================ FILE: Dapper/PublicAPI.Unshipped.txt ================================================ #nullable enable ================================================ FILE: Dapper/SimpleMemberMap.cs ================================================ using System; using System.Reflection; namespace Dapper { /// /// Represents simple member map for one of target parameter or property or field to source DataReader column /// internal sealed class SimpleMemberMap : SqlMapper.IMemberMap { /// /// Creates instance for simple property mapping /// /// DataReader column name /// Target property public SimpleMemberMap(string columnName, PropertyInfo property) { ColumnName = columnName ?? throw new ArgumentNullException(nameof(columnName)); Property = property ?? throw new ArgumentNullException(nameof(property)); } /// /// Creates instance for simple field mapping /// /// DataReader column name /// Target property public SimpleMemberMap(string columnName, FieldInfo field) { ColumnName = columnName ?? throw new ArgumentNullException(nameof(columnName)); Field = field ?? throw new ArgumentNullException(nameof(field)); } /// /// Creates instance for simple constructor parameter mapping /// /// DataReader column name /// Target constructor parameter public SimpleMemberMap(string columnName, ParameterInfo parameter) { ColumnName = columnName ?? throw new ArgumentNullException(nameof(columnName)); Parameter = parameter ?? throw new ArgumentNullException(nameof(parameter)); } /// /// DataReader column name /// public string ColumnName { get; } /// /// Target member type /// public Type MemberType => Field?.FieldType ?? Property?.PropertyType ?? Parameter?.ParameterType!; /// /// Target property /// public PropertyInfo? Property { get; } /// /// Target field /// public FieldInfo? Field { get; } /// /// Target constructor parameter /// public ParameterInfo? Parameter { get; } } } ================================================ FILE: Dapper/SqlDataRecordHandler.cs ================================================ using System; using System.Collections.Generic; using System.Data; namespace Dapper { internal sealed class SqlDataRecordHandler : SqlMapper.ITypeHandler where T : IDataRecord { public object Parse(Type destinationType, object value) { throw new NotSupportedException(); } public void SetValue(IDbDataParameter parameter, object value) { SqlDataRecordListTVPParameter.Set(parameter, value as IEnumerable, null); } } } ================================================ FILE: Dapper/SqlDataRecordListTVPParameter.cs ================================================ using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Linq; using System.Reflection; using System.Reflection.Emit; namespace Dapper { /// /// Used to pass a IEnumerable<SqlDataRecord> as a SqlDataRecordListTVPParameter /// internal sealed class SqlDataRecordListTVPParameter : SqlMapper.ICustomQueryParameter where T : IDataRecord { private readonly IEnumerable data; private readonly string typeName; /// /// Create a new instance of . /// /// The data records to convert into TVPs. /// The parameter type name. public SqlDataRecordListTVPParameter(IEnumerable data, string typeName) { this.data = data; this.typeName = typeName; } void SqlMapper.ICustomQueryParameter.AddParameter(IDbCommand command, string name) { var param = command.CreateParameter(); param.ParameterName = name; Set(param, data, typeName); command.Parameters.Add(param); } internal static void Set(IDbDataParameter parameter, IEnumerable? data, string? typeName) { parameter.Value = data is not null && data.Any() ? data : null; StructuredHelper.ConfigureTVP(parameter, typeName); } } static class StructuredHelper { private static readonly Hashtable s_udt = new Hashtable(), s_tvp = new Hashtable(); private static Action GetUDT(Type type) => (Action?)s_udt[type] ?? SlowGetHelper(type, s_udt, "UdtTypeName", 29); // 29 = SqlDbType.Udt (avoiding ref) private static Action GetTVP(Type type) => (Action?)s_tvp[type] ?? SlowGetHelper(type, s_tvp, "TypeName", 30); // 30 = SqlDbType.Structured (avoiding ref) static Action SlowGetHelper(Type type, Hashtable hashtable, string nameProperty, int sqlDbType) { lock (hashtable) { var helper = (Action?)hashtable[type]; if (helper is null) { helper = CreateFor(type, nameProperty, sqlDbType); hashtable.Add(type, helper); } return helper; } } static Action CreateFor(Type type, string nameProperty, int sqlDbType) { var name = type.GetProperty(nameProperty, BindingFlags.Public | BindingFlags.Instance); if (name is null || !name.CanWrite) { return (p, n) => { }; } var dbType = type.GetProperty("SqlDbType", BindingFlags.Public | BindingFlags.Instance); if (dbType is not null && !dbType.CanWrite) dbType = null; var dm = new DynamicMethod(nameof(CreateFor) + "_" + type.Name, null, new[] { typeof(IDbDataParameter), typeof(string) }, true); var il = dm.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Castclass, type); il.Emit(OpCodes.Ldarg_1); il.EmitCall(OpCodes.Callvirt, name.GetSetMethod()!, null); if (dbType is not null) { il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Castclass, type); il.Emit(OpCodes.Ldc_I4, sqlDbType); il.EmitCall(OpCodes.Callvirt, dbType.GetSetMethod()!, null); } il.Emit(OpCodes.Ret); return (Action)dm.CreateDelegate(typeof(Action)); } // this needs to be done per-provider; "dynamic" doesn't work well on all runtimes, although that // would be a fair option otherwise internal static void ConfigureUDT(IDbDataParameter parameter, string typeName) => GetUDT(parameter.GetType())(parameter, typeName); internal static void ConfigureTVP(IDbDataParameter parameter, string? typeName) => GetTVP(parameter.GetType())(parameter, typeName); } } ================================================ FILE: Dapper/SqlMapper.Async.cs ================================================ using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Globalization; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; namespace Dapper { public static partial class SqlMapper { /// /// Execute a query asynchronously using Task. /// /// The connection to query on. /// The SQL to execute for the query. /// The parameters to pass, if any. /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. /// Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object> [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task> QueryAsync(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryAsync(cnn, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default)); /// /// Execute a query asynchronously using Task. /// /// The connection to query on. /// The command used to query on this connection. /// Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object> public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command) => QueryAsync(cnn, typeof(DapperRow), command); /// /// Execute a single-row query asynchronously using Task. /// /// The connection to query on. /// The command used to query on this connection. /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> public static Task QueryFirstAsync(this IDbConnection cnn, CommandDefinition command) => QueryRowAsync(cnn, Row.First, typeof(DapperRow), command); /// /// Execute a single-row query asynchronously using Task. /// /// The connection to query on. /// The command used to query on this connection. /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, CommandDefinition command) => QueryRowAsync(cnn, Row.FirstOrDefault, typeof(DapperRow), command); /// /// Execute a single-row query asynchronously using Task. /// /// The connection to query on. /// The command used to query on this connection. /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> public static Task QuerySingleAsync(this IDbConnection cnn, CommandDefinition command) => QueryRowAsync(cnn, Row.Single, typeof(DapperRow), command); /// /// Execute a single-row query asynchronously using Task. /// /// The connection to query on. /// The command used to query on this connection. /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, CommandDefinition command) => QueryRowAsync(cnn, Row.SingleOrDefault, typeof(DapperRow), command); /// /// Execute a query asynchronously using Task. /// /// The type of results to return. /// The connection to query on. /// The SQL to execute for the query. /// The parameters to pass, if any. /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. /// /// A sequence of data of ; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task> QueryAsync(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryAsync(cnn, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default)); /// /// Execute a single-row query asynchronously using Task. /// /// The type of result to return. /// The connection to query on. /// The SQL to execute for the query. /// The parameters to pass, if any. /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task QueryFirstAsync(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryRowAsync(cnn, Row.First, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); /// /// Execute a single-row query asynchronously using Task. /// /// The type of result to return. /// The connection to query on. /// The SQL to execute for the query. /// The parameters to pass, if any. /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryRowAsync(cnn, Row.FirstOrDefault, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); /// /// Execute a single-row query asynchronously using Task. /// /// The type of result to return. /// The connection to query on. /// The SQL to execute for the query. /// The parameters to pass, if any. /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task QuerySingleAsync(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryRowAsync(cnn, Row.Single, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); /// /// Execute a single-row query asynchronously using Task. /// /// The type to return. /// The connection to query on. /// The SQL to execute for the query. /// The parameters to pass, if any. /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryRowAsync(cnn, Row.SingleOrDefault, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); /// /// Execute a single-row query asynchronously using Task. /// /// The connection to query on. /// The SQL to execute for the query. /// The parameters to pass, if any. /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task QueryFirstAsync(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryRowAsync(cnn, Row.First, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); /// /// Execute a single-row query asynchronously using Task. /// /// The connection to query on. /// The SQL to execute for the query. /// The parameters to pass, if any. /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryRowAsync(cnn, Row.FirstOrDefault, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); /// /// Execute a single-row query asynchronously using Task. /// /// The connection to query on. /// The SQL to execute for the query. /// The parameters to pass, if any. /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task QuerySingleAsync(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryRowAsync(cnn, Row.Single, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); /// /// Execute a single-row query asynchronously using Task. /// /// The connection to query on. /// The SQL to execute for the query. /// The parameters to pass, if any. /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryRowAsync(cnn, Row.SingleOrDefault, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); /// /// Execute a query asynchronously using Task. /// /// The connection to query on. /// The type to return. /// The SQL to execute for the query. /// The parameters to pass, if any. /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. /// is null. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task> QueryAsync(this IDbConnection cnn, Type type, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { if (type is null) throw new ArgumentNullException(nameof(type)); return QueryAsync(cnn, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default)); } /// /// Execute a single-row query asynchronously using Task. /// /// The connection to query on. /// The type to return. /// The SQL to execute for the query. /// The parameters to pass, if any. /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. /// is null. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task QueryFirstAsync(this IDbConnection cnn, Type type, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { if (type is null) throw new ArgumentNullException(nameof(type)); return QueryRowAsync(cnn, Row.First, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); } /// /// Execute a single-row query asynchronously using Task. /// /// The connection to query on. /// The type to return. /// The SQL to execute for the query. /// The parameters to pass, if any. /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. /// is null. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, Type type, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { if (type is null) throw new ArgumentNullException(nameof(type)); return QueryRowAsync(cnn, Row.FirstOrDefault, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); } /// /// Execute a single-row query asynchronously using Task. /// /// The connection to query on. /// The type to return. /// The SQL to execute for the query. /// The parameters to pass, if any. /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. /// is null. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task QuerySingleAsync(this IDbConnection cnn, Type type, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { if (type is null) throw new ArgumentNullException(nameof(type)); return QueryRowAsync(cnn, Row.Single, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); } /// /// Execute a single-row query asynchronously using Task. /// /// The connection to query on. /// The type to return. /// The SQL to execute for the query. /// The parameters to pass, if any. /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. /// is null. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, Type type, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { if (type is null) throw new ArgumentNullException(nameof(type)); return QueryRowAsync(cnn, Row.SingleOrDefault, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); } /// /// Execute a query asynchronously using Task. /// /// The type to return. /// The connection to query on. /// The command used to query on this connection. /// /// A sequence of data of ; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command) => QueryAsync(cnn, typeof(T), command); /// /// Execute a query asynchronously using Task. /// /// The connection to query on. /// The type to return. /// The command used to query on this connection. public static Task> QueryAsync(this IDbConnection cnn, Type type, CommandDefinition command) => QueryAsync(cnn, type, command); /// /// Execute a single-row query asynchronously using Task. /// /// The connection to query on. /// The type to return. /// The command used to query on this connection. public static Task QueryFirstAsync(this IDbConnection cnn, Type type, CommandDefinition command) => QueryRowAsync(cnn, Row.First, type, command); /// /// Execute a single-row query asynchronously using Task. /// /// The type to return. /// The connection to query on. /// The command used to query on this connection. public static Task QueryFirstAsync(this IDbConnection cnn, CommandDefinition command) => QueryRowAsync(cnn, Row.First, typeof(T), command); /// /// Execute a single-row query asynchronously using Task. /// /// The connection to query on. /// The type to return. /// The command used to query on this connection. public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, Type type, CommandDefinition command) => QueryRowAsync(cnn, Row.FirstOrDefault, type, command); /// /// Execute a single-row query asynchronously using Task. /// /// The type to return. /// The connection to query on. /// The command used to query on this connection. public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, CommandDefinition command) => QueryRowAsync(cnn, Row.FirstOrDefault, typeof(T), command); /// /// Execute a single-row query asynchronously using Task. /// /// The connection to query on. /// The type to return. /// The command used to query on this connection. public static Task QuerySingleAsync(this IDbConnection cnn, Type type, CommandDefinition command) => QueryRowAsync(cnn, Row.Single, type, command); /// /// Execute a single-row query asynchronously using Task. /// /// The type to return. /// The connection to query on. /// The command used to query on this connection. public static Task QuerySingleAsync(this IDbConnection cnn, CommandDefinition command) => QueryRowAsync(cnn, Row.Single, typeof(T), command); /// /// Execute a single-row query asynchronously using Task. /// /// The connection to query on. /// The type to return. /// The command used to query on this connection. public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, Type type, CommandDefinition command) => QueryRowAsync(cnn, Row.SingleOrDefault, type, command); /// /// Execute a single-row query asynchronously using Task. /// /// The type to return. /// The connection to query on. /// The command used to query on this connection. public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, CommandDefinition command) => QueryRowAsync(cnn, Row.SingleOrDefault, typeof(T), command); private static Task ExecuteReaderWithFlagsFallbackAsync(DbCommand cmd, bool wasClosed, CommandBehavior behavior, CancellationToken cancellationToken) { var task = cmd.ExecuteReaderAsync(GetBehavior(wasClosed, behavior), cancellationToken); if (task.Status == TaskStatus.Faulted && Settings.DisableCommandBehaviorOptimizations(behavior, task.Exception!.InnerException!)) { // we can retry; this time it will have different flags return cmd.ExecuteReaderAsync(GetBehavior(wasClosed, behavior), cancellationToken); } return task; } /// /// Attempts to open a connection asynchronously, with a better error message for unsupported usages. /// private static Task TryOpenAsync(this IDbConnection cnn, CancellationToken cancel) { if (cnn is DbConnection dbConn) { return dbConn.OpenAsync(cancel); } else { throw new InvalidOperationException("Async operations require use of a DbConnection or an already-open IDbConnection"); } } /// /// Attempts setup a on a , with a better error message for unsupported usages. /// private static DbCommand TrySetupAsyncCommand(this CommandDefinition command, IDbConnection cnn, Action? paramReader) { if (command.SetupCommand(cnn, paramReader) is DbCommand dbCommand) { return dbCommand; } else { throw new InvalidOperationException("Async operations require use of a DbConnection or an IDbConnection where .CreateCommand() returns a DbCommand"); } } private static async Task> QueryAsync(this IDbConnection cnn, Type effectiveType, CommandDefinition command) { object? param = command.Parameters; var identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, effectiveType, param?.GetType()); var info = GetCacheInfo(identity, param, command.AddToCache); bool wasClosed = cnn.State == ConnectionState.Closed; var cancel = command.CancellationToken; using var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader); DbDataReader? reader = null; try { if (wasClosed) await cnn.TryOpenAsync(cancel).ConfigureAwait(false); reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, cancel).ConfigureAwait(false); var tuple = info.Deserializer; int hash = GetColumnHash(reader); if (tuple.Func is null || tuple.Hash != hash) { if (reader.FieldCount == 0) return Enumerable.Empty(); tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false)); if (command.AddToCache) SetQueryCache(identity, info); } var func = tuple.Func; if (command.Buffered) { var buffer = new List(); var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType; while (await reader.ReadAsync(cancel).ConfigureAwait(false)) { object val = func(reader); buffer.Add(GetValue(reader, effectiveType, val)); } while (await reader.NextResultAsync(cancel).ConfigureAwait(false)) { /* ignore subsequent result sets */ } command.OnCompleted(); return buffer; } else { // can't use ReadAsync / cancellation; but this will have to do wasClosed = false; // don't close if handing back an open reader; rely on the command-behavior var deferred = ExecuteReaderSync(reader, func, command.Parameters); reader = null; // to prevent it being disposed before the caller gets to see it return deferred; } } finally { using (reader) { /* dispose if non-null */ } if (wasClosed) cnn.Close(); } } private static async Task QueryRowAsync(this IDbConnection cnn, Row row, Type effectiveType, CommandDefinition command) { object? param = command.Parameters; var identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, effectiveType, param?.GetType()); var info = GetCacheInfo(identity, param, command.AddToCache); bool wasClosed = cnn.State == ConnectionState.Closed; var cancel = command.CancellationToken; using var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader); DbDataReader? reader = null; try { if (wasClosed) await cnn.TryOpenAsync(cancel).ConfigureAwait(false); reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, (row & Row.Single) != 0 ? CommandBehavior.SequentialAccess | CommandBehavior.SingleResult // need to allow multiple rows, to check fail condition : CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow, cancel).ConfigureAwait(false); T result = default!; if (await reader.ReadAsync(cancel).ConfigureAwait(false) && reader.FieldCount != 0) { result = ReadRow(info, identity, ref command, effectiveType, reader); if ((row & Row.Single) != 0 && await reader.ReadAsync(cancel).ConfigureAwait(false)) ThrowMultipleRows(row); while (await reader.ReadAsync(cancel).ConfigureAwait(false)) { /* ignore rows after the first */ } } else if ((row & Row.FirstOrDefault) == 0) // demanding a row, and don't have one { ThrowZeroRows(row); } while (await reader.NextResultAsync(cancel).ConfigureAwait(false)) { /* ignore result sets after the first */ } command.OnCompleted(); return result; } finally { using (reader) { /* dispose if non-null */ } if (wasClosed) cnn.Close(); } } /// /// Execute a command asynchronously using Task. /// /// The connection to query on. /// The SQL to execute for this query. /// The parameters to use for this query. /// The transaction to use for this query. /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// The number of rows affected. public static Task ExecuteAsync(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => ExecuteAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default)); /// /// Execute a command asynchronously using Task. /// /// The connection to execute on. /// The command to execute on this connection. /// The number of rows affected. public static Task ExecuteAsync(this IDbConnection cnn, CommandDefinition command) { object? param = command.Parameters; IEnumerable? multiExec = GetMultiExec(param); if (multiExec is not null) { return ExecuteMultiImplAsync(cnn, command, multiExec); } else { return ExecuteImplAsync(cnn, command, param); } } private readonly struct AsyncExecState { public readonly DbCommand Command; public readonly Task Task; public AsyncExecState(DbCommand command, Task task) { Command = command; Task = task; } } private static async Task ExecuteMultiImplAsync(IDbConnection cnn, CommandDefinition command, IEnumerable multiExec) { bool isFirst = true; int total = 0; bool wasClosed = cnn.State == ConnectionState.Closed; try { if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false); CacheInfo? info = null; string? masterSql = null; if ((command.Flags & CommandFlags.Pipelined) != 0) { const int MAX_PENDING = 100; var pending = new Queue(MAX_PENDING); DbCommand? cmd = null; try { foreach (var obj in multiExec) { if (isFirst) { isFirst = false; cmd = command.TrySetupAsyncCommand(cnn, null); masterSql = cmd.CommandText; var identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType()); info = GetCacheInfo(identity, obj, command.AddToCache); } else if (pending.Count >= MAX_PENDING) { var recycled = pending.Dequeue(); total += await recycled.Task.ConfigureAwait(false); cmd = recycled.Command; cmd.CommandText = masterSql; // because we do magic replaces on "in" etc cmd.Parameters.Clear(); // current code is Add-tastic } else { cmd = command.TrySetupAsyncCommand(cnn, null); } info!.ParamReader!(cmd, obj); var task = cmd.ExecuteNonQueryAsync(command.CancellationToken); pending.Enqueue(new AsyncExecState(cmd, task)); cmd = null; // note the using in the finally: this avoids a double-dispose } while (pending.Count != 0) { var pair = pending.Dequeue(); using (pair.Command) { /* dispose commands */ } total += await pair.Task.ConfigureAwait(false); } } finally { // this only has interesting work to do if there are failures using (cmd) { /* dispose commands */ } while (pending.Count != 0) { // dispose tasks even in failure using (pending.Dequeue().Command) { /* dispose commands */ } } } } else { using var cmd = command.TrySetupAsyncCommand(cnn, null); foreach (var obj in multiExec) { if (isFirst) { masterSql = cmd.CommandText; isFirst = false; var identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType()); info = GetCacheInfo(identity, obj, command.AddToCache); } else { cmd.CommandText = masterSql; // because we do magic replaces on "in" etc cmd.Parameters.Clear(); // current code is Add-tastic } info!.ParamReader!(cmd, obj); total += await cmd.ExecuteNonQueryAsync(command.CancellationToken).ConfigureAwait(false); } } command.OnCompleted(); } finally { if (wasClosed) cnn.Close(); } return total; } private static async Task ExecuteImplAsync(IDbConnection cnn, CommandDefinition command, object? param) { var identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, null, param?.GetType()); var info = GetCacheInfo(identity, param, command.AddToCache); bool wasClosed = cnn.State == ConnectionState.Closed; using var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader); try { if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false); var result = await cmd.ExecuteNonQueryAsync(command.CancellationToken).ConfigureAwait(false); command.OnCompleted(); return result; } finally { if (wasClosed) cnn.Close(); } } /// /// Perform an asynchronous multi-mapping query with 2 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. /// The second type in the recordset. /// The combined type to return. /// The connection to query on. /// The SQL to execute for this query. /// The function to map row types to the return type. /// The parameters to use for this query. /// The transaction to use for this query. /// Whether to buffer the results in memory. /// The field we should split and read the second object from (default: "Id"). /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object? param = null, IDbTransaction? transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMapAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default), map, splitOn); /// /// Perform an asynchronous multi-mapping query with 2 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. /// The second type in the recordset. /// The combined type to return. /// The connection to query on. /// The field we should split and read the second object from (default: "Id"). /// The command to execute. /// The function to map row types to the return type. /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") => MultiMapAsync(cnn, command, map, splitOn); /// /// Perform an asynchronous multi-mapping query with 3 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. /// The second type in the recordset. /// The third type in the recordset. /// The combined type to return. /// The connection to query on. /// The SQL to execute for this query. /// The function to map row types to the return type. /// The parameters to use for this query. /// The transaction to use for this query. /// Whether to buffer the results in memory. /// The field we should split and read the second object from (default: "Id"). /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object? param = null, IDbTransaction? transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMapAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default), map, splitOn); /// /// Perform an asynchronous multi-mapping query with 3 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. /// The second type in the recordset. /// The third type in the recordset. /// The combined type to return. /// The connection to query on. /// The field we should split and read the second object from (default: "Id"). /// The command to execute. /// The function to map row types to the return type. /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") => MultiMapAsync(cnn, command, map, splitOn); /// /// Perform an asynchronous multi-mapping query with 4 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. /// The second type in the recordset. /// The third type in the recordset. /// The fourth type in the recordset. /// The combined type to return. /// The connection to query on. /// The SQL to execute for this query. /// The function to map row types to the return type. /// The parameters to use for this query. /// The transaction to use for this query. /// Whether to buffer the results in memory. /// The field we should split and read the second object from (default: "Id"). /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object? param = null, IDbTransaction? transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMapAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default), map, splitOn); /// /// Perform an asynchronous multi-mapping query with 4 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. /// The second type in the recordset. /// The third type in the recordset. /// The fourth type in the recordset. /// The combined type to return. /// The connection to query on. /// The field we should split and read the second object from (default: "Id"). /// The command to execute. /// The function to map row types to the return type. /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") => MultiMapAsync(cnn, command, map, splitOn); /// /// Perform an asynchronous multi-mapping query with 5 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. /// The second type in the recordset. /// The third type in the recordset. /// The fourth type in the recordset. /// The fifth type in the recordset. /// The combined type to return. /// The connection to query on. /// The SQL to execute for this query. /// The function to map row types to the return type. /// The parameters to use for this query. /// The transaction to use for this query. /// Whether to buffer the results in memory. /// The field we should split and read the second object from (default: "Id"). /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object? param = null, IDbTransaction? transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMapAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default), map, splitOn); /// /// Perform an asynchronous multi-mapping query with 5 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. /// The second type in the recordset. /// The third type in the recordset. /// The fourth type in the recordset. /// The fifth type in the recordset. /// The combined type to return. /// The connection to query on. /// The field we should split and read the second object from (default: "Id"). /// The command to execute. /// The function to map row types to the return type. /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") => MultiMapAsync(cnn, command, map, splitOn); /// /// Perform an asynchronous multi-mapping query with 6 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. /// The second type in the recordset. /// The third type in the recordset. /// The fourth type in the recordset. /// The fifth type in the recordset. /// The sixth type in the recordset. /// The combined type to return. /// The connection to query on. /// The SQL to execute for this query. /// The function to map row types to the return type. /// The parameters to use for this query. /// The transaction to use for this query. /// Whether to buffer the results in memory. /// The field we should split and read the second object from (default: "Id"). /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object? param = null, IDbTransaction? transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMapAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default), map, splitOn); /// /// Perform an asynchronous multi-mapping query with 6 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. /// The second type in the recordset. /// The third type in the recordset. /// The fourth type in the recordset. /// The fifth type in the recordset. /// The sixth type in the recordset. /// The combined type to return. /// The connection to query on. /// The field we should split and read the second object from (default: "Id"). /// The command to execute. /// The function to map row types to the return type. /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") => MultiMapAsync(cnn, command, map, splitOn); /// /// Perform an asynchronous multi-mapping query with 7 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. /// The second type in the recordset. /// The third type in the recordset. /// The fourth type in the recordset. /// The fifth type in the recordset. /// The sixth type in the recordset. /// The seventh type in the recordset. /// The combined type to return. /// The connection to query on. /// The SQL to execute for this query. /// The function to map row types to the return type. /// The parameters to use for this query. /// The transaction to use for this query. /// Whether to buffer the results in memory. /// The field we should split and read the second object from (default: "Id"). /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object? param = null, IDbTransaction? transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMapAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default), map, splitOn); /// /// Perform an asynchronous multi-mapping query with 7 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. /// The second type in the recordset. /// The third type in the recordset. /// The fourth type in the recordset. /// The fifth type in the recordset. /// The sixth type in the recordset. /// The seventh type in the recordset. /// The combined type to return. /// The connection to query on. /// The field we should split and read the second object from (default: "Id"). /// The command to execute. /// The function to map row types to the return type. /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") => MultiMapAsync(cnn, command, map, splitOn); private static async Task> MultiMapAsync(this IDbConnection cnn, CommandDefinition command, Delegate map, string splitOn) { object? param = command.Parameters; var identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, typeof(TFirst), param?.GetType()); var info = GetCacheInfo(identity, param, command.AddToCache); bool wasClosed = cnn.State == ConnectionState.Closed; try { if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false); using var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader); using var reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, command.CancellationToken).ConfigureAwait(false); if (!command.Buffered) wasClosed = false; // handing back open reader; rely on command-behavior var results = MultiMapImpl(null, CommandDefinition.ForCallback(command.Parameters, command.Flags), map, splitOn, reader, identity, true); return command.Buffered ? results.ToList() : results; } finally { if (wasClosed) cnn.Close(); } } /// /// Perform an asynchronous multi-mapping query with an arbitrary number of input types. /// This returns a single type, combined from the raw types via . /// /// The combined type to return. /// The connection to query on. /// The SQL to execute for this query. /// Array of types in the recordset. /// The function to map row types to the return type. /// The parameters to use for this query. /// The transaction to use for this query. /// Whether to buffer the results in memory. /// The field we should split and read the second object from (default: "Id"). /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task> QueryAsync(this IDbConnection cnn, string sql, Type[] types, Func map, object? param = null, IDbTransaction? transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default); return MultiMapAsync(cnn, command, types, map, splitOn); } private static async Task> MultiMapAsync(this IDbConnection cnn, CommandDefinition command, Type[] types, Func map, string splitOn) { if (types.Length < 1) { throw new ArgumentException("you must provide at least one type to deserialize"); } object? param = command.Parameters; var identity = new IdentityWithTypes(command.CommandText, command.CommandTypeDirect, cnn, types[0], param?.GetType(), types); var info = GetCacheInfo(identity, param, command.AddToCache); bool wasClosed = cnn.State == ConnectionState.Closed; try { if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false); using var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader); using var reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, command.CancellationToken).ConfigureAwait(false); var results = MultiMapImpl(null, default, types, map, splitOn, reader, identity, true); return command.Buffered ? results.ToList() : results; } finally { if (wasClosed) cnn.Close(); } } private static IEnumerable ExecuteReaderSync(DbDataReader reader, Func func, object? parameters) { using (reader) { while (reader.Read()) { yield return (T)func(reader); } while (reader.NextResult()) { /* ignore subsequent result sets */ } (parameters as IParameterCallbacks)?.OnCompleted(); } } /// /// Execute a command that returns multiple result sets, and access each in turn. /// /// The connection to query on. /// The SQL to execute for this query. /// The parameters to use for this query. /// The transaction to use for this query. /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? public static Task QueryMultipleAsync(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryMultipleAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered)); /// /// Execute a command that returns multiple result sets, and access each in turn. /// /// The connection to query on. /// The command to execute for this query. public static async Task QueryMultipleAsync(this IDbConnection cnn, CommandDefinition command) { object? param = command.Parameters; var identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, typeof(GridReader), param?.GetType()); CacheInfo info = GetCacheInfo(identity, param, command.AddToCache); DbCommand? cmd = null; DbDataReader? reader = null; bool wasClosed = cnn.State == ConnectionState.Closed; try { if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false); cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader); reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess, command.CancellationToken).ConfigureAwait(false); var result = new GridReader(cmd, reader, identity, command.Parameters as DynamicParameters, command.AddToCache, command.CancellationToken); wasClosed = false; // *if* the connection was closed and we got this far, then we now have a reader // with the CloseConnection flag, so the reader will deal with the connection; we // still need something in the "finally" to ensure that broken SQL still results // in the connection closing itself return result; } catch { if (reader is not null) { if (!reader.IsClosed) { try { cmd?.Cancel(); } catch { /* don't spoil the existing exception */ } } reader.Dispose(); } cmd?.Dispose(); if (wasClosed) cnn.Close(); throw; } } /// /// Execute parameterized SQL and return an . /// /// The connection to execute on. /// The SQL to execute. /// The parameters to use for this command. /// The transaction to use for this command. /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// An that can be used to iterate over the results of the SQL query. /// /// This is typically used when the results of a query are not processed by Dapper, for example, used to fill a /// or . /// /// /// /// /// /// [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task ExecuteReaderAsync(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => ExecuteWrappedReaderImplAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered), CommandBehavior.Default).CastResult(); /// /// Execute parameterized SQL and return a . /// /// The connection to execute on. /// The SQL to execute. /// The parameters to use for this command. /// The transaction to use for this command. /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task ExecuteReaderAsync(this DbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => ExecuteWrappedReaderImplAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered), CommandBehavior.Default); /// /// Execute parameterized SQL and return an . /// /// The connection to execute on. /// The command to execute. /// An that can be used to iterate over the results of the SQL query. /// /// This is typically used when the results of a query are not processed by Dapper, for example, used to fill a /// or . /// public static Task ExecuteReaderAsync(this IDbConnection cnn, CommandDefinition command) => ExecuteWrappedReaderImplAsync(cnn, command, CommandBehavior.Default).CastResult(); /// /// Execute parameterized SQL and return a . /// /// The connection to execute on. /// The command to execute. public static Task ExecuteReaderAsync(this DbConnection cnn, CommandDefinition command) => ExecuteWrappedReaderImplAsync(cnn, command, CommandBehavior.Default); /// /// Execute parameterized SQL and return an . /// /// The connection to execute on. /// The command to execute. /// The flags for this reader. /// An that can be used to iterate over the results of the SQL query. /// /// This is typically used when the results of a query are not processed by Dapper, for example, used to fill a /// or . /// public static Task ExecuteReaderAsync(this IDbConnection cnn, CommandDefinition command, CommandBehavior commandBehavior) => ExecuteWrappedReaderImplAsync(cnn, command, commandBehavior).CastResult(); /// /// Execute parameterized SQL and return a . /// /// The connection to execute on. /// The command to execute. /// The flags for this reader. public static Task ExecuteReaderAsync(this DbConnection cnn, CommandDefinition command, CommandBehavior commandBehavior) => ExecuteWrappedReaderImplAsync(cnn, command, commandBehavior); private static async Task ExecuteWrappedReaderImplAsync(IDbConnection cnn, CommandDefinition command, CommandBehavior commandBehavior) { Action? paramReader = GetParameterReader(cnn, ref command); DbCommand? cmd = null; bool wasClosed = cnn.State == ConnectionState.Closed, disposeCommand = true; try { cmd = command.TrySetupAsyncCommand(cnn, paramReader); if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false); var reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, commandBehavior, command.CancellationToken).ConfigureAwait(false); wasClosed = false; disposeCommand = false; return DbWrappedReader.Create(cmd, reader); } finally { if (wasClosed) cnn.Close(); if (cmd is not null && disposeCommand) cmd.Dispose(); } } /// /// Execute parameterized SQL that selects a single value. /// /// The connection to execute on. /// The SQL to execute. /// The parameters to use for this command. /// The transaction to use for this command. /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// The first cell returned, as . public static Task ExecuteScalarAsync(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => ExecuteScalarImplAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered)); /// /// Execute parameterized SQL that selects a single value. /// /// The type to return. /// The connection to execute on. /// The SQL to execute. /// The parameters to use for this command. /// The transaction to use for this command. /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// The first cell returned, as . public static Task ExecuteScalarAsync(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => ExecuteScalarImplAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered)); /// /// Execute parameterized SQL that selects a single value. /// /// The connection to execute on. /// The command to execute. /// The first cell selected as . public static Task ExecuteScalarAsync(this IDbConnection cnn, CommandDefinition command) => ExecuteScalarImplAsync(cnn, command); /// /// Execute parameterized SQL that selects a single value. /// /// The type to return. /// The connection to execute on. /// The command to execute. /// The first cell selected as . public static Task ExecuteScalarAsync(this IDbConnection cnn, CommandDefinition command) => ExecuteScalarImplAsync(cnn, command); private static async Task ExecuteScalarImplAsync(IDbConnection cnn, CommandDefinition command) { Action? paramReader = null; object? param = command.Parameters; if (param is not null) { var identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, null, param.GetType()); paramReader = GetCacheInfo(identity, command.Parameters, command.AddToCache).ParamReader; } DbCommand? cmd = null; bool wasClosed = cnn.State == ConnectionState.Closed; object? result; try { cmd = command.TrySetupAsyncCommand(cnn, paramReader); if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false); result = await cmd.ExecuteScalarAsync(command.CancellationToken).ConfigureAwait(false); command.OnCompleted(); } finally { if (wasClosed) cnn.Close(); cmd?.Dispose(); } return Parse(result); } /// /// Execute a query asynchronously using . /// /// The connection to query on. /// The SQL to execute for the query. /// The parameters to pass, if any. /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. /// /// A sequence of data of dynamic data /// public static IAsyncEnumerable QueryUnbufferedAsync(this DbConnection cnn, string sql, object? param = null, DbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { // note: in many cases of adding a new async method I might add a CancellationToken - however, cancellation is expressed via WithCancellation on iterators return QueryUnbufferedAsync(cnn, typeof(object), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); } /// /// Execute a query asynchronously using . /// /// The type of results to return. /// The connection to query on. /// The SQL to execute for the query. /// The parameters to pass, if any. /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. /// /// A sequence of data of ; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// public static IAsyncEnumerable QueryUnbufferedAsync(this DbConnection cnn, string sql, object? param = null, DbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { // note: in many cases of adding a new async method I might add a CancellationToken - however, cancellation is expressed via WithCancellation on iterators return QueryUnbufferedAsync(cnn, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); } private static IAsyncEnumerable QueryUnbufferedAsync(this IDbConnection cnn, Type effectiveType, CommandDefinition command) { return Impl(cnn, effectiveType, command, command.CancellationToken); // proxy to allow CT expression static async IAsyncEnumerable Impl(IDbConnection cnn, Type effectiveType, CommandDefinition command, [EnumeratorCancellation] CancellationToken cancel) { object? param = command.Parameters; var identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, effectiveType, param?.GetType()); var info = GetCacheInfo(identity, param, command.AddToCache); bool wasClosed = cnn.State == ConnectionState.Closed; using var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader); DbDataReader? reader = null; try { if (wasClosed) await cnn.TryOpenAsync(cancel).ConfigureAwait(false); reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, cancel).ConfigureAwait(false); var tuple = info.Deserializer; int hash = GetColumnHash(reader); if (tuple.Func is null || tuple.Hash != hash) { if (reader.FieldCount == 0) { yield break; } tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false)); if (command.AddToCache) SetQueryCache(identity, info); } var func = tuple.Func; var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType; while (await reader.ReadAsync(cancel).ConfigureAwait(false)) { object val = func(reader); yield return GetValue(reader, effectiveType, val); } while (await reader.NextResultAsync(cancel).ConfigureAwait(false)) { /* ignore subsequent result sets */ } command.OnCompleted(); } finally { if (reader is not null) { if (!reader.IsClosed) { try { cmd?.Cancel(); } catch { /* don't spoil any existing exception */ } } #if NET5_0_OR_GREATER await reader.DisposeAsync(); #else reader.Dispose(); #endif } if (wasClosed) cnn.Close(); } } } } } ================================================ FILE: Dapper/SqlMapper.CacheInfo.cs ================================================ using System; using System.Data; using System.Data.Common; using System.Threading; namespace Dapper { public static partial class SqlMapper { private class CacheInfo { public DeserializerState Deserializer { get; set; } public Func[]? OtherDeserializers { get; set; } public Action? ParamReader { get; set; } private int hitCount; public int GetHitCount() { return Interlocked.CompareExchange(ref hitCount, 0, 0); } public void RecordHit() { Interlocked.Increment(ref hitCount); } } } } ================================================ FILE: Dapper/SqlMapper.DapperRow.Descriptor.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel; namespace Dapper { public static partial class SqlMapper { [TypeDescriptionProvider(typeof(DapperRowTypeDescriptionProvider))] private sealed partial class DapperRow { private sealed class DapperRowTypeDescriptionProvider : TypeDescriptionProvider { public override ICustomTypeDescriptor GetExtendedTypeDescriptor(object instance) => new DapperRowTypeDescriptor(instance); public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object? instance) => new DapperRowTypeDescriptor(instance!); } //// in theory we could implement this for zero-length results to bind; would require //// additional changes, though, to capture a table even when no rows - so not currently provided //internal sealed class DapperRowList : List, ITypedList //{ // private readonly DapperTable _table; // public DapperRowList(DapperTable table) { _table = table; } // PropertyDescriptorCollection ITypedList.GetItemProperties(PropertyDescriptor[] listAccessors) // { // if (listAccessors is not null && listAccessors.Length != 0) return PropertyDescriptorCollection.Empty; // return DapperRowTypeDescriptor.GetProperties(_table); // } // string ITypedList.GetListName(PropertyDescriptor[] listAccessors) => null; //} private sealed class DapperRowTypeDescriptor : ICustomTypeDescriptor { private readonly DapperRow _row; public DapperRowTypeDescriptor(object instance) => _row = (DapperRow)instance; AttributeCollection ICustomTypeDescriptor.GetAttributes() => AttributeCollection.Empty; string ICustomTypeDescriptor.GetClassName() => typeof(DapperRow).FullName!; string ICustomTypeDescriptor.GetComponentName() => null!; private static readonly TypeConverter s_converter = new ExpandableObjectConverter(); TypeConverter ICustomTypeDescriptor.GetConverter() => s_converter; EventDescriptor ICustomTypeDescriptor.GetDefaultEvent() => null!; PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty() => null!; object ICustomTypeDescriptor.GetEditor(Type editorBaseType) => null!; EventDescriptorCollection ICustomTypeDescriptor.GetEvents() => EventDescriptorCollection.Empty; EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[]? attributes) => EventDescriptorCollection.Empty; internal static PropertyDescriptorCollection GetProperties(DapperRow row) => GetProperties(row?.table, row); internal static PropertyDescriptorCollection GetProperties(DapperTable? table, IDictionary? row = null) { string[]? names = table?.FieldNames; if (names is null || names.Length == 0) return PropertyDescriptorCollection.Empty; var arr = new PropertyDescriptor[names.Length]; for (int i = 0; i < arr.Length; i++) { var type = row is not null && row.TryGetValue(names[i], out var value) && value is not null ? value.GetType() : typeof(object); arr[i] = new RowBoundPropertyDescriptor(type, names[i], i); } return new PropertyDescriptorCollection(arr, true); } PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties() => GetProperties(_row); PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[]? attributes) => GetProperties(_row); object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor? pd) => _row; } private sealed class RowBoundPropertyDescriptor : PropertyDescriptor { private readonly Type _type; private readonly int _index; public RowBoundPropertyDescriptor(Type type, string name, int index) : base(name, null) { _type = type; _index = index; } public override bool CanResetValue(object component) => true; public override void ResetValue(object component) => ((DapperRow)component).Remove(_index); public override bool IsReadOnly => false; public override bool ShouldSerializeValue(object component) => ((DapperRow)component).TryGetValue(_index, out _); public override Type ComponentType => typeof(DapperRow); public override Type PropertyType => _type; public override object GetValue(object? component) => ((DapperRow)component!).TryGetValue(_index, out var val) ? (val ?? DBNull.Value): DBNull.Value; public override void SetValue(object? component, object? value) => ((DapperRow)component!).SetValue(_index, value is DBNull ? null : value); } } } } ================================================ FILE: Dapper/SqlMapper.DapperRow.cs ================================================ using System; using System.Collections; using System.Collections.Generic; using System.Linq; namespace Dapper { public static partial class SqlMapper { private sealed partial class DapperRow : IDictionary , IReadOnlyDictionary { private readonly DapperTable table; private object?[] values; public DapperRow(DapperTable table, object?[] values) { this.table = table ?? throw new ArgumentNullException(nameof(table)); this.values = values ?? throw new ArgumentNullException(nameof(values)); } private sealed class DeadValue { public static readonly DeadValue Default = new DeadValue(); private DeadValue() { /* hiding constructor */ } } int ICollection>.Count { get { int count = 0; for (int i = 0; i < values.Length; i++) { if (!(values[i] is DeadValue)) count++; } return count; } } public bool TryGetValue(string key, out object? value) => TryGetValue(table.IndexOfName(key), out value); internal bool TryGetValue(int index, out object? value) { if (index < 0) { // doesn't exist value = null; return false; } // exists, **even if** we don't have a value; consider table rows heterogeneous value = index < values.Length ? values[index] : null; if (value is DeadValue) { // pretend it isn't here value = null; return false; } return true; } public override string ToString() { var sb = GetStringBuilder().Append("{DapperRow"); foreach (var kv in this) { var value = kv.Value; sb.Append(", ").Append(kv.Key); if (value is not null) { sb.Append(" = '").Append(kv.Value).Append('\''); } else { sb.Append(" = NULL"); } } return sb.Append('}').ToStringRecycle(); } public IEnumerator> GetEnumerator() { var names = table.FieldNames; for (var i = 0; i < names.Length; i++) { object? value = i < values.Length ? values[i] : null!; if (!(value is DeadValue)) { yield return new KeyValuePair(names[i], value); } } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #region Implementation of ICollection> void ICollection>.Add(KeyValuePair item) { IDictionary dic = this; dic.Add(item.Key, item.Value); } void ICollection>.Clear() { // removes values for **this row**, but doesn't change the fundamental table for (int i = 0; i < values.Length; i++) values[i] = DeadValue.Default; } bool ICollection>.Contains(KeyValuePair item) { return TryGetValue(item.Key, out object? value) && Equals(value, item.Value); } void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { foreach (var kv in this) { array[arrayIndex++] = kv; // if they didn't leave enough space; not our fault } } bool ICollection>.Remove(KeyValuePair item) { IDictionary dic = this; return dic.Remove(item.Key); } bool ICollection>.IsReadOnly => false; #endregion #region Implementation of IDictionary bool IDictionary.ContainsKey(string key) { int index = table.IndexOfName(key); if (index < 0 || index >= values.Length || values[index] is DeadValue) return false; return true; } void IDictionary.Add(string key, object? value) { SetValue(key, value, true); } bool IDictionary.Remove(string key) => Remove(table.IndexOfName(key)); internal bool Remove(int index) { if (index < 0 || index >= values.Length || values[index] is DeadValue) return false; values[index] = DeadValue.Default; return true; } object? IDictionary.this[string key] { get { TryGetValue(key, out object? val); return val; } set { SetValue(key, value, false); } } public object? SetValue(string key, object? value) { return SetValue(key, value, false); } private object? SetValue(string key, object? value, bool isAdd) { if (key is null) throw new ArgumentNullException(nameof(key)); int index = table.IndexOfName(key); if (index < 0) { index = table.AddField(key); } else if (isAdd && index < values.Length && !(values[index] is DeadValue)) { // then semantically, this value already exists throw new ArgumentException("An item with the same key has already been added", nameof(key)); } return SetValue(index, value); } internal object? SetValue(int index, object? value) { int oldLength = values.Length; if (oldLength <= index) { // we'll assume they're doing lots of things, and // grow it to the full width of the table Array.Resize(ref values, table.FieldCount); for (int i = oldLength; i < values.Length; i++) { values[i] = DeadValue.Default; } } return values[index] = value; } ICollection IDictionary.Keys { get { return this.Select(kv => kv.Key).ToArray(); } } ICollection IDictionary.Values { get { return this.Select(kv => kv.Value).ToArray(); } } #endregion #region Implementation of IReadOnlyDictionary int IReadOnlyCollection>.Count { get { return values.Count(t => !(t is DeadValue)); } } bool IReadOnlyDictionary.ContainsKey(string key) { int index = table.IndexOfName(key); return index >= 0 && index < values.Length && !(values[index] is DeadValue); } object? IReadOnlyDictionary.this[string key] { get { TryGetValue(key, out object? val); return val; } } IEnumerable IReadOnlyDictionary.Keys { get { return this.Select(kv => kv.Key); } } IEnumerable IReadOnlyDictionary.Values { get { return this.Select(kv => kv.Value); } } #endregion } } } ================================================ FILE: Dapper/SqlMapper.DapperRowMetaObject.cs ================================================ using System; using System.Collections.Generic; using System.Reflection; namespace Dapper { public static partial class SqlMapper { private sealed partial class DapperRow : System.Dynamic.IDynamicMetaObjectProvider { System.Dynamic.DynamicMetaObject System.Dynamic.IDynamicMetaObjectProvider.GetMetaObject( System.Linq.Expressions.Expression parameter) { return new DapperRowMetaObject(parameter, System.Dynamic.BindingRestrictions.Empty, this); } } private sealed class DapperRowMetaObject : System.Dynamic.DynamicMetaObject { private static readonly MethodInfo getValueMethod = typeof(IDictionary).GetProperty("Item")!.GetGetMethod()!; private static readonly MethodInfo setValueMethod = typeof(DapperRow).GetMethod("SetValue", new Type[] { typeof(string), typeof(object) })!; public DapperRowMetaObject( System.Linq.Expressions.Expression expression, System.Dynamic.BindingRestrictions restrictions ) : base(expression, restrictions) { } public DapperRowMetaObject( System.Linq.Expressions.Expression expression, System.Dynamic.BindingRestrictions restrictions, object value ) : base(expression, restrictions, value) { } private System.Dynamic.DynamicMetaObject CallMethod( MethodInfo method, System.Linq.Expressions.Expression[] parameters ) { var callMethod = new System.Dynamic.DynamicMetaObject( System.Linq.Expressions.Expression.Call( System.Linq.Expressions.Expression.Convert(Expression, LimitType), method, parameters), System.Dynamic.BindingRestrictions.GetTypeRestriction(Expression, LimitType) ); return callMethod; } public override System.Dynamic.DynamicMetaObject BindGetMember(System.Dynamic.GetMemberBinder binder) { var parameters = new System.Linq.Expressions.Expression[] { System.Linq.Expressions.Expression.Constant(binder.Name) }; var callMethod = CallMethod(getValueMethod, parameters); return callMethod; } // Needed for Visual basic dynamic support public override System.Dynamic.DynamicMetaObject BindInvokeMember(System.Dynamic.InvokeMemberBinder binder, System.Dynamic.DynamicMetaObject[] args) { var parameters = new System.Linq.Expressions.Expression[] { System.Linq.Expressions.Expression.Constant(binder.Name) }; var callMethod = CallMethod(getValueMethod, parameters); return callMethod; } public override System.Dynamic.DynamicMetaObject BindSetMember(System.Dynamic.SetMemberBinder binder, System.Dynamic.DynamicMetaObject value) { var parameters = new System.Linq.Expressions.Expression[] { System.Linq.Expressions.Expression.Constant(binder.Name), System.Linq.Expressions.Expression.Convert(value.Expression, typeof(object)), }; var callMethod = CallMethod(setValueMethod, parameters); return callMethod; } public override IEnumerable GetDynamicMemberNames() { if(HasValue && Value is IDictionary lookup) return lookup.Keys; return Array.Empty(); } } } } ================================================ FILE: Dapper/SqlMapper.DapperTable.cs ================================================ using System; using System.Collections.Generic; namespace Dapper { public static partial class SqlMapper { private sealed class DapperTable { private string[] fieldNames; private readonly Dictionary fieldNameLookup; internal string[] FieldNames => fieldNames; public DapperTable(string[] fieldNames) { this.fieldNames = fieldNames ?? throw new ArgumentNullException(nameof(fieldNames)); fieldNameLookup = new Dictionary(fieldNames.Length, StringComparer.Ordinal); // if there are dups, we want the **first** key to be the "winner" - so iterate backwards for (int i = fieldNames.Length - 1; i >= 0; i--) { string key = fieldNames[i]; if (key is not null) fieldNameLookup[key] = i; } } internal int IndexOfName(string name) { return (name is not null && fieldNameLookup.TryGetValue(name, out int result)) ? result : -1; } internal int AddField(string name) { if (name is null) throw new ArgumentNullException(nameof(name)); if (fieldNameLookup.ContainsKey(name)) throw new InvalidOperationException("Field already exists: " + name); int oldLen = fieldNames.Length; Array.Resize(ref fieldNames, oldLen + 1); // yes, this is sub-optimal, but this is not the expected common case fieldNames[oldLen] = name; fieldNameLookup[name] = oldLen; return oldLen; } internal bool FieldExists(string key) => key is not null && fieldNameLookup.ContainsKey(key); public int FieldCount => fieldNames.Length; } } } ================================================ FILE: Dapper/SqlMapper.DeserializerState.cs ================================================ using System; using System.Data; using System.Data.Common; namespace Dapper { public static partial class SqlMapper { private readonly struct DeserializerState { public readonly int Hash; public readonly Func Func; public DeserializerState(int hash, Func func) { Hash = hash; Func = func; } } } } ================================================ FILE: Dapper/SqlMapper.DontMap.cs ================================================ namespace Dapper { public static partial class SqlMapper { /// /// Dummy type for excluding from multi-map /// private class DontMap { /* hiding constructor */ } } } ================================================ FILE: Dapper/SqlMapper.GridReader.Async.cs ================================================ using System; using System.Collections.Generic; using System.Data.Common; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; namespace Dapper { public static partial class SqlMapper { public partial class GridReader : IAsyncDisposable { /// /// Read the next grid of results, returned as a dynamic object /// /// Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object> /// Whether to buffer the results. public Task> ReadAsync(bool buffered = true) => ReadAsyncImpl(typeof(DapperRow), buffered); /// /// Read an individual row of the next grid of results, returned as a dynamic object /// /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> public Task ReadFirstAsync() => ReadRowAsyncImpl(typeof(DapperRow), Row.First); /// /// Read an individual row of the next grid of results, returned as a dynamic object /// /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> public Task ReadFirstOrDefaultAsync() => ReadRowAsyncImpl(typeof(DapperRow), Row.FirstOrDefault); /// /// Read an individual row of the next grid of results, returned as a dynamic object /// /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> public Task ReadSingleAsync() => ReadRowAsyncImpl(typeof(DapperRow), Row.Single); /// /// Read an individual row of the next grid of results, returned as a dynamic object /// /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> public Task ReadSingleOrDefaultAsync() => ReadRowAsyncImpl(typeof(DapperRow), Row.SingleOrDefault); /// /// Read the next grid of results /// /// The type to read. /// Whether to buffer the results. /// is null. public Task> ReadAsync(Type type, bool buffered = true) { if (type is null) throw new ArgumentNullException(nameof(type)); return ReadAsyncImpl(type, buffered); } /// /// Read an individual row of the next grid of results /// /// The type to read. /// is null. public Task ReadFirstAsync(Type type) { if (type is null) throw new ArgumentNullException(nameof(type)); return ReadRowAsyncImpl(type, Row.First); } /// /// Read an individual row of the next grid of results. /// /// The type to read. /// is null. public Task ReadFirstOrDefaultAsync(Type type) { if (type is null) throw new ArgumentNullException(nameof(type)); return ReadRowAsyncImpl(type, Row.FirstOrDefault); } /// /// Read an individual row of the next grid of results. /// /// The type to read. /// is null. public Task ReadSingleAsync(Type type) { if (type is null) throw new ArgumentNullException(nameof(type)); return ReadRowAsyncImpl(type, Row.Single); } /// /// Read an individual row of the next grid of results. /// /// The type to read. /// is null. public Task ReadSingleOrDefaultAsync(Type type) { if (type is null) throw new ArgumentNullException(nameof(type)); return ReadRowAsyncImpl(type, Row.SingleOrDefault); } /// /// Read the next grid of results. /// /// The type to read. /// Whether the results should be buffered in memory. public Task> ReadAsync(bool buffered = true) => ReadAsyncImpl(typeof(T), buffered); /// /// Read an individual row of the next grid of results. /// /// The type to read. public Task ReadFirstAsync() => ReadRowAsyncImpl(typeof(T), Row.First); /// /// Read an individual row of the next grid of results. /// /// The type to read. public Task ReadFirstOrDefaultAsync() => ReadRowAsyncImpl(typeof(T), Row.FirstOrDefault); /// /// Read an individual row of the next grid of results. /// /// The type to read. public Task ReadSingleAsync() => ReadRowAsyncImpl(typeof(T), Row.Single); /// /// Read an individual row of the next grid of results. /// /// The type to read. public Task ReadSingleOrDefaultAsync() => ReadRowAsyncImpl(typeof(T), Row.SingleOrDefault); /// /// Marks the current grid as consumed, and moves to the next result /// protected async Task OnAfterGridAsync(int index) { if (index != ResultIndex) { // not our data } else if (reader is null) { // nothing to do } else if (await reader.NextResultAsync(cancel).ConfigureAwait(false)) { // readCount++; _resultIndexAndConsumedFlag = index + 1; } else { // happy path; close the reader cleanly - no // need for "Cancel" etc #if NET5_0_OR_GREATER await reader.DisposeAsync(); #else reader.Dispose(); #endif reader = null!; onCompleted?.Invoke(state); await DisposeAsync(); } } private Task> ReadAsyncImpl(Type type, bool buffered) { var deserializer = ValidateAndMarkConsumed(type, out var index); if (buffered) { return ReadBufferedAsync(index, deserializer); } else { var result = ReadDeferred(index, deserializer, type); return Task.FromResult(result); } } private Func ValidateAndMarkConsumed(Type type, out int index) { index = OnBeforeGrid(); var typedIdentity = Identity.ForGrid(type, index); CacheInfo cache = GetCacheInfo(typedIdentity, null, addToCache); var deserializer = cache.Deserializer; int hash = GetColumnHash(reader); if (deserializer.Func is null || deserializer.Hash != hash) { deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false)); cache.Deserializer = deserializer; } return deserializer.Func; } private async Task ReadRowAsyncImpl(Type type, Row row) { var index = OnBeforeGrid(); T result = default!; if (await reader.ReadAsync(cancel).ConfigureAwait(false) && reader.FieldCount != 0) { var typedIdentity = Identity.ForGrid(type, index); CacheInfo cache = GetCacheInfo(typedIdentity, null, addToCache); var deserializer = cache.Deserializer; int hash = GetColumnHash(reader); if (deserializer.Func is null || deserializer.Hash != hash) { deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false)); cache.Deserializer = deserializer; } result = ConvertTo(deserializer.Func(reader)); if ((row & Row.Single) != 0 && await reader.ReadAsync(cancel).ConfigureAwait(false)) ThrowMultipleRows(row); while (await reader.ReadAsync(cancel).ConfigureAwait(false)) { /* ignore subsequent rows */ } } else if ((row & Row.FirstOrDefault) == 0) // demanding a row, and don't have one { ThrowZeroRows(row); } await OnAfterGridAsync(index).ConfigureAwait(false); return result; } private async Task> ReadBufferedAsync(int index, Func deserializer) { try { var buffer = new List(); while (index == ResultIndex && await reader!.ReadAsync(cancel).ConfigureAwait(false)) { buffer.Add(ConvertTo(deserializer(reader))); } return buffer; } finally // finally so that First etc progresses things even when multiple rows { await OnAfterGridAsync(index).ConfigureAwait(false); } } /// /// Read the next grid of results. /// /// The type to read. public IAsyncEnumerable ReadUnbufferedAsync() => ReadAsyncUnbufferedImpl(typeof(T)); /// /// Read the next grid of results. /// public IAsyncEnumerable ReadUnbufferedAsync() => ReadAsyncUnbufferedImpl(typeof(DapperRow)); private IAsyncEnumerable ReadAsyncUnbufferedImpl(Type type) { var deserializer = ValidateAndMarkConsumed(type, out var index); return ReadUnbufferedAsync(index, deserializer, cancel); } private async IAsyncEnumerable ReadUnbufferedAsync(int index, Func deserializer, [EnumeratorCancellation] CancellationToken cancel) { try { while (index == ResultIndex && await reader!.ReadAsync(cancel).ConfigureAwait(false)) { yield return ConvertTo(deserializer(reader)); } } finally // finally so that First etc progresses things even when multiple rows { await OnAfterGridAsync(index).ConfigureAwait(false); } } /// /// Dispose the grid, closing and disposing both the underlying reader and command. /// #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - for netfx version public async ValueTask DisposeAsync() #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { if (reader is not null) { if (!reader.IsClosed) Command?.Cancel(); #if NET5_0_OR_GREATER await reader.DisposeAsync(); #else reader.Dispose(); #endif reader = null!; } if (Command is not null) { if (Command is DbCommand typed) { #if NET5_0_OR_GREATER await typed.DisposeAsync(); #else typed.Dispose(); #endif } else { Command.Dispose(); } Command = null!; } GC.SuppressFinalize(this); } } } } ================================================ FILE: Dapper/SqlMapper.GridReader.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Data.Common; using System.Globalization; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; namespace Dapper { public static partial class SqlMapper { /// /// The grid reader provides interfaces for reading multiple result sets from a Dapper query /// public partial class GridReader : IDisposable { private DbDataReader reader; private Identity? _identity; private readonly bool addToCache; private readonly Action? onCompleted; private readonly object? state; private readonly CancellationToken cancel; /// /// Creates a grid reader over an existing command and reader /// [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] protected GridReader(IDbCommand command, DbDataReader reader, Identity? identity, Action? onCompleted = null, object? state = null, bool addToCache = false, CancellationToken cancellationToken = default) { Command = command; this.reader = reader; _identity = identity; this.onCompleted = onCompleted; this.state = state; this.addToCache = addToCache; cancel = cancellationToken; } internal GridReader(IDbCommand command, DbDataReader reader, Identity identity, IParameterCallbacks? callbacks, bool addToCache, CancellationToken cancellationToken = default) : this(command, reader, identity, callbacks is null ? null : static state => ((IParameterCallbacks)state!).OnCompleted(), callbacks, addToCache, cancellationToken) { } private Identity Identity => _identity ??= CreateIdentity(); private Identity CreateIdentity() { var cmd = Command; if (cmd is not null && cmd.Connection is not null) { return new Identity(cmd.CommandText, cmd.CommandType, cmd.Connection, null, null); } throw new InvalidOperationException("This operation requires an identity or a connected command"); } /// /// Read the next grid of results, returned as a dynamic object. /// /// Whether the results should be buffered in memory. /// Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object> public IEnumerable Read(bool buffered = true) => ReadImpl(typeof(DapperRow), buffered); /// /// Read an individual row of the next grid of results, returned as a dynamic object. /// /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> public dynamic ReadFirst() => ReadRow(typeof(DapperRow), Row.First); /// /// Read an individual row of the next grid of results, returned as a dynamic object. /// /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> public dynamic? ReadFirstOrDefault() => ReadRow(typeof(DapperRow), Row.FirstOrDefault); /// /// Read an individual row of the next grid of results, returned as a dynamic object. /// /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> public dynamic ReadSingle() => ReadRow(typeof(DapperRow), Row.Single); /// /// Read an individual row of the next grid of results, returned as a dynamic object. /// /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> public dynamic? ReadSingleOrDefault() => ReadRow(typeof(DapperRow), Row.SingleOrDefault); /// /// Read the next grid of results. /// /// The type to read. /// Whether the results should be buffered in memory. public IEnumerable Read(bool buffered = true) => ReadImpl(typeof(T), buffered); /// /// Read an individual row of the next grid of results. /// /// The type to read. public T ReadFirst() => ReadRow(typeof(T), Row.First); /// /// Read an individual row of the next grid of results. /// /// The type to read. public T? ReadFirstOrDefault() => ReadRow(typeof(T), Row.FirstOrDefault); /// /// Read an individual row of the next grid of results. /// /// The type to read. public T ReadSingle() => ReadRow(typeof(T), Row.Single); /// /// Read an individual row of the next grid of results. /// /// The type to read. public T? ReadSingleOrDefault() => ReadRow(typeof(T), Row.SingleOrDefault); /// /// Read the next grid of results. /// /// The type to read. /// Whether to buffer the results. /// is null. public IEnumerable Read(Type type, bool buffered = true) { if (type is null) throw new ArgumentNullException(nameof(type)); return ReadImpl(type, buffered); } /// /// Read an individual row of the next grid of results. /// /// The type to read. /// is null. public object ReadFirst(Type type) { if (type is null) throw new ArgumentNullException(nameof(type)); return ReadRow(type, Row.First); } /// /// Read an individual row of the next grid of results. /// /// The type to read. /// is null. public object? ReadFirstOrDefault(Type type) { if (type is null) throw new ArgumentNullException(nameof(type)); return ReadRow(type, Row.FirstOrDefault); } /// /// Read an individual row of the next grid of results. /// /// The type to read. /// is null. public object ReadSingle(Type type) { if (type is null) throw new ArgumentNullException(nameof(type)); return ReadRow(type, Row.Single); } /// /// Read an individual row of the next grid of results. /// /// The type to read. /// is null. public object? ReadSingleOrDefault(Type type) { if (type is null) throw new ArgumentNullException(nameof(type)); return ReadRow(type, Row.SingleOrDefault); } /// /// Validates that data is available, returning the that corresponds to the current grid - and marks the current grid as consumed; /// this call must be paired with a call to or /// protected int OnBeforeGrid() { if (reader is null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed"); if (IsConsumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once"); _resultIndexAndConsumedFlag |= CONSUMED_FLAG; return ResultIndex; } private IEnumerable ReadImpl(Type type, bool buffered) { var index = OnBeforeGrid(); var typedIdentity = Identity.ForGrid(type, index); CacheInfo cache = GetCacheInfo(typedIdentity, null, addToCache); var deserializer = cache.Deserializer; int hash = GetColumnHash(reader); if (deserializer.Func is null || deserializer.Hash != hash) { deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false)); cache.Deserializer = deserializer; } var result = ReadDeferred(index, deserializer.Func, type); return buffered ? result.ToList() : result; } private T ReadRow(Type type, Row row) { var index = OnBeforeGrid(); T result = default!; if (reader.Read() && reader.FieldCount != 0) { var typedIdentity = Identity.ForGrid(type, index); CacheInfo cache = GetCacheInfo(typedIdentity, null, addToCache); var deserializer = cache.Deserializer; int hash = GetColumnHash(reader); if (deserializer.Func is null || deserializer.Hash != hash) { deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false)); cache.Deserializer = deserializer; } result = ConvertTo(deserializer.Func(reader)); if ((row & Row.Single) != 0 && reader.Read()) ThrowMultipleRows(row); while (reader.Read()) { /* ignore subsequent rows */ } } else if ((row & Row.FirstOrDefault) == 0) // demanding a row, and don't have one { ThrowZeroRows(row); } OnAfterGrid(index); return result; } private IEnumerable MultiReadInternal(Delegate func, string splitOn) { var index = OnBeforeGrid(); var identity = Identity.ForGrid(typeof(TReturn), index); try { foreach (var r in MultiMapImpl(null, default, func, splitOn, reader, identity, false)) { yield return r; } } finally { OnAfterGrid(index); } } private IEnumerable MultiReadInternal(Type[] types, Func map, string splitOn) { var index = OnBeforeGrid(); var identity = Identity.ForGrid(typeof(TReturn), types, index); try { foreach (var r in MultiMapImpl(null, default, types, map, splitOn, reader, identity, false)) { yield return r; } } finally { OnAfterGrid(index); } } /// /// Read multiple objects from a single record set on the grid. /// /// The first type in the record set. /// The second type in the record set. /// The type to return from the record set. /// The mapping function from the read types to the return type. /// The field(s) we should split and read the second object from (defaults to "id") /// Whether to buffer results in memory. public IEnumerable Read(Func func, string splitOn = "id", bool buffered = true) { var result = MultiReadInternal(func, splitOn); return buffered ? result.ToList() : result; } /// /// Read multiple objects from a single record set on the grid. /// /// The first type in the record set. /// The second type in the record set. /// The third type in the record set. /// The type to return from the record set. /// The mapping function from the read types to the return type. /// The field(s) we should split and read the second object from (defaults to "id") /// Whether to buffer results in memory. public IEnumerable Read(Func func, string splitOn = "id", bool buffered = true) { var result = MultiReadInternal(func, splitOn); return buffered ? result.ToList() : result; } /// /// Read multiple objects from a single record set on the grid /// /// The first type in the record set. /// The second type in the record set. /// The third type in the record set. /// The fourth type in the record set. /// The type to return from the record set. /// The mapping function from the read types to the return type. /// The field(s) we should split and read the second object from (defaults to "id") /// Whether to buffer results in memory. public IEnumerable Read(Func func, string splitOn = "id", bool buffered = true) { var result = MultiReadInternal(func, splitOn); return buffered ? result.ToList() : result; } /// /// Read multiple objects from a single record set on the grid /// /// The first type in the record set. /// The second type in the record set. /// The third type in the record set. /// The fourth type in the record set. /// The fifth type in the record set. /// The type to return from the record set. /// The mapping function from the read types to the return type. /// The field(s) we should split and read the second object from (defaults to "id") /// Whether to buffer results in memory. public IEnumerable Read(Func func, string splitOn = "id", bool buffered = true) { var result = MultiReadInternal(func, splitOn); return buffered ? result.ToList() : result; } /// /// Read multiple objects from a single record set on the grid /// /// The first type in the record set. /// The second type in the record set. /// The third type in the record set. /// The fourth type in the record set. /// The fifth type in the record set. /// The sixth type in the record set. /// The type to return from the record set. /// The mapping function from the read types to the return type. /// The field(s) we should split and read the second object from (defaults to "id") /// Whether to buffer results in memory. public IEnumerable Read(Func func, string splitOn = "id", bool buffered = true) { var result = MultiReadInternal(func, splitOn); return buffered ? result.ToList() : result; } /// /// Read multiple objects from a single record set on the grid /// /// The first type in the record set. /// The second type in the record set. /// The third type in the record set. /// The fourth type in the record set. /// The fifth type in the record set. /// The sixth type in the record set. /// The seventh type in the record set. /// The type to return from the record set. /// The mapping function from the read types to the return type. /// The field(s) we should split and read the second object from (defaults to "id") /// Whether to buffer results in memory. public IEnumerable Read(Func func, string splitOn = "id", bool buffered = true) { var result = MultiReadInternal(func, splitOn); return buffered ? result.ToList() : result; } /// /// Read multiple objects from a single record set on the grid /// /// The type to return from the record set. /// The types to read from the result set. /// The mapping function from the read types to the return type. /// The field(s) we should split and read the second object from (defaults to "id") /// Whether to buffer results in memory. public IEnumerable Read(Type[] types, Func map, string splitOn = "id", bool buffered = true) { var result = MultiReadInternal(types, map, splitOn); return buffered ? result.ToList() : result; } private IEnumerable ReadDeferred(int index, Func deserializer, Type effectiveType) { try { while (index == ResultIndex && reader?.Read() == true) { yield return ConvertTo(deserializer(reader)); } } finally // finally so that First etc progresses things even when multiple rows { OnAfterGrid(index); } } const int CONSUMED_FLAG = 1 << 31; private int _resultIndexAndConsumedFlag; //, readCount; /// /// Indicates the current result index /// protected int ResultIndex => _resultIndexAndConsumedFlag & ~CONSUMED_FLAG; /// /// Has the underlying reader been consumed? /// /// This also reports true if the current grid is actively being consumed public bool IsConsumed => (_resultIndexAndConsumedFlag & CONSUMED_FLAG) != 0; /// /// The command associated with the reader /// public IDbCommand Command { get; set; } /// /// The underlying reader /// protected DbDataReader Reader => reader; /// /// The cancellation token associated with this reader /// protected CancellationToken CancellationToken => cancel; /// /// Marks the current grid as consumed, and moves to the next result /// protected void OnAfterGrid(int index) { if (index != ResultIndex) { // not our data! } else if (reader is null) { // nothing to do } else if (reader.NextResult()) { // readCount++; _resultIndexAndConsumedFlag = index + 1; } else { // happy path; close the reader cleanly - no // need for "Cancel" etc reader.Dispose(); reader = null!; onCompleted?.Invoke(state); Dispose(); } } /// /// Dispose the grid, closing and disposing both the underlying reader and command. /// public void Dispose() { if (reader is not null) { if (!reader.IsClosed) Command?.Cancel(); reader.Dispose(); reader = null!; } if (Command is not null) { Command.Dispose(); Command = null!; } GC.SuppressFinalize(this); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static T ConvertTo(object? value) => value switch { T typed => typed, null or DBNull => default!, _ => (T)Convert.ChangeType(value, Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T), CultureInfo.InvariantCulture), }; } } } ================================================ FILE: Dapper/SqlMapper.ICustomQueryParameter.cs ================================================ using System.Data; namespace Dapper { public static partial class SqlMapper { /// /// Implement this interface to pass an arbitrary db specific parameter to Dapper /// public interface ICustomQueryParameter { /// /// Add the parameter needed to the command before it executes /// /// The raw command prior to execution /// Parameter name void AddParameter(IDbCommand command, string name); } } } ================================================ FILE: Dapper/SqlMapper.IDataReader.cs ================================================ using System; using System.Collections.Generic; using System.Data; using System.Data.Common; namespace Dapper { public static partial class SqlMapper { /// /// Parses a data reader to a sequence of data of the supplied type. Used for deserializing a reader without a connection, etc. /// /// The type to parse from the . /// The data reader to parse results from. public static IEnumerable Parse(this IDataReader reader) { var dbReader = GetDbDataReader(reader); if (dbReader.Read()) { var effectiveType = typeof(T); var deser = GetDeserializer(effectiveType, dbReader, 0, -1, false); var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType; do { object val = deser(dbReader); if (val is null || val is T) { yield return (T)val!; } else { yield return (T)Convert.ChangeType(val, convertToType, System.Globalization.CultureInfo.InvariantCulture); } } while (dbReader.Read()); } } /// /// Parses a data reader to a sequence of data of the supplied type (as object). Used for deserializing a reader without a connection, etc. /// /// The data reader to parse results from. /// The type to parse from the . public static IEnumerable Parse(this IDataReader reader, Type type) { var dbReader = GetDbDataReader(reader); if (dbReader.Read()) { var deser = GetDeserializer(type, dbReader, 0, -1, false); do { yield return deser(dbReader); } while (dbReader.Read()); } } /// /// Parses a data reader to a sequence of dynamic. Used for deserializing a reader without a connection, etc. /// /// The data reader to parse results from. public static IEnumerable Parse(this IDataReader reader) { var dbReader = GetDbDataReader(reader); if (dbReader.Read()) { var deser = GetDapperRowDeserializer(dbReader, 0, -1, false); do { yield return deser(dbReader); } while (dbReader.Read()); } } /// /// Gets the row parser for a specific row on a data reader. This allows for type switching every row based on, for example, a TypeId column. /// You could return a collection of the base type but have each more specific. /// /// The data reader to get the parser for the current row from /// The type to get the parser for /// The start column index of the object (default 0) /// The length of columns to read (default -1 = all fields following startIndex) /// Return null if we can't find the first column? (default false) /// A parser for this specific object from this row. #if DEBUG // make sure we're not using this internally [Obsolete(nameof(DbDataReader) + " API should be preferred")] #endif [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Func GetRowParser(this IDataReader reader, Type type, int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) { return WrapObjectReader(GetDeserializer(type, GetDbDataReader(reader), startIndex, length, returnNullIfFirstMissing)); } /// /// Gets the row parser for a specific row on a data reader. This allows for type switching every row based on, for example, a TypeId column. /// You could return a collection of the base type but have each more specific. /// /// The data reader to get the parser for the current row from /// The type to get the parser for /// The start column index of the object (default 0) /// The length of columns to read (default -1 = all fields following startIndex) /// Return null if we can't find the first column? (default false) /// A parser for this specific object from this row. public static Func GetRowParser(this DbDataReader reader, Type type, int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) { return GetDeserializer(type, reader, startIndex, length, returnNullIfFirstMissing); } /// #if DEBUG // make sure we're not using this internally [Obsolete(nameof(DbDataReader) + " API should be preferred")] #endif [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Func GetRowParser(this IDataReader reader, Type? concreteType = null, int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) { concreteType ??= typeof(T); var func = GetDeserializer(concreteType, GetDbDataReader(reader), startIndex, length, returnNullIfFirstMissing); return Wrap(func); // this is just to be very clear about what we're capturing static Func Wrap(Func func) => reader => (T)func(GetDbDataReader(reader)); } /// /// Gets the row parser for a specific row on a data reader. This allows for type switching every row based on, for example, a TypeId column. /// You could return a collection of the base type but have each more specific. /// /// The type of results to return. /// The data reader to get the parser for the current row from. /// The type to get the parser for. /// The start column index of the object (default: 0). /// The length of columns to read (default: -1 = all fields following startIndex). /// Return null if we can't find the first column? (default: false). /// A parser for this specific object from this row. /// /// var result = new List<BaseType>(); /// using (var reader = connection.ExecuteReader(@" /// select 'abc' as Name, 1 as Type, 3.0 as Value /// union all /// select 'def' as Name, 2 as Type, 4.0 as Value")) /// { /// if (reader.Read()) /// { /// var toFoo = reader.GetRowParser<BaseType>(typeof(Foo)); /// var toBar = reader.GetRowParser<BaseType>(typeof(Bar)); /// var col = reader.GetOrdinal("Type"); /// do /// { /// switch (reader.GetInt32(col)) /// { /// case 1: /// result.Add(toFoo(reader)); /// break; /// case 2: /// result.Add(toBar(reader)); /// break; /// } /// } while (reader.Read()); /// } /// } /// /// abstract class BaseType /// { /// public abstract int Type { get; } /// } /// class Foo : BaseType /// { /// public string Name { get; set; } /// public override int Type => 1; /// } /// class Bar : BaseType /// { /// public float Value { get; set; } /// public override int Type => 2; /// } /// public static Func GetRowParser(this DbDataReader reader, Type? concreteType = null, int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) { concreteType ??= typeof(T); var func = GetDeserializer(concreteType, reader, startIndex, length, returnNullIfFirstMissing); if (concreteType.IsValueType) { return _ => (T)func(_); } else { return (Func)(Delegate)func; } } } } ================================================ FILE: Dapper/SqlMapper.IDynamicParameters.cs ================================================ using System.Data; namespace Dapper { public static partial class SqlMapper { /// /// Implement this interface to pass an arbitrary db specific set of parameters to Dapper /// public interface IDynamicParameters { /// /// Add all the parameters needed to the command just before it executes /// /// The raw command prior to execution /// Information about the query void AddParameters(IDbCommand command, Identity identity); } } } ================================================ FILE: Dapper/SqlMapper.IMemberMap.cs ================================================ using System; using System.Reflection; namespace Dapper { public static partial class SqlMapper { /// /// Implements this interface to provide custom member mapping /// public interface IMemberMap { /// /// Source DataReader column name /// string ColumnName { get; } /// /// Target member type /// Type MemberType { get; } /// /// Target property /// PropertyInfo? Property { get; } /// /// Target field /// FieldInfo? Field { get; } /// /// Target constructor parameter /// ParameterInfo? Parameter { get; } } } } ================================================ FILE: Dapper/SqlMapper.IParameterCallbacks.cs ================================================ namespace Dapper { public static partial class SqlMapper { /// /// Extends IDynamicParameters with facilities for executing callbacks after commands have completed /// public interface IParameterCallbacks : IDynamicParameters { /// /// Invoked when the command has executed /// void OnCompleted(); } } } ================================================ FILE: Dapper/SqlMapper.IParameterLookup.cs ================================================ namespace Dapper { public static partial class SqlMapper { /// /// Extends IDynamicParameters providing by-name lookup of parameter values /// public interface IParameterLookup : IDynamicParameters { /// /// Get the value of the specified parameter (return null if not found) /// /// The name of the parameter to get. object? this[string name] { get; } } } } ================================================ FILE: Dapper/SqlMapper.ITypeHandler.cs ================================================ using System; using System.Data; namespace Dapper { public static partial class SqlMapper { /// /// Implement this interface to perform custom type-based parameter handling and value parsing /// public interface ITypeHandler { /// /// Assign the value of a parameter before a command executes /// /// The parameter to configure /// Parameter value void SetValue(IDbDataParameter parameter, object value); /// /// Parse a database value back to a typed value /// /// The value from the database /// The type to parse to /// The typed value object? Parse(Type destinationType, object value); } } } ================================================ FILE: Dapper/SqlMapper.ITypeMap.cs ================================================ using System; using System.Reflection; namespace Dapper { public static partial class SqlMapper { /// /// Implement this interface to change default mapping of reader columns to type members /// public interface ITypeMap { /// /// Finds best constructor /// /// DataReader column names /// DataReader column types /// Matching constructor or default one ConstructorInfo? FindConstructor(string[] names, Type[] types); /// /// Returns a constructor which should *always* be used. /// /// Parameters will be default values, nulls for reference types and zero'd for value types. /// /// Use this class to force object creation away from parameterless constructors you don't control. /// ConstructorInfo? FindExplicitConstructor(); /// /// Gets mapping for constructor parameter /// /// Constructor to resolve /// DataReader column name /// Mapping implementation IMemberMap? GetConstructorParameter(ConstructorInfo constructor, string columnName); /// /// Gets member mapping for column /// /// DataReader column name /// Mapping implementation IMemberMap? GetMember(string columnName); } } } ================================================ FILE: Dapper/SqlMapper.Identity.cs ================================================ using System; using System.ComponentModel; using System.Data; using System.Runtime.CompilerServices; namespace Dapper { public static partial class SqlMapper { internal sealed class Identity : Identity { private static readonly int s_typeHash; private static readonly int s_typeCount = CountNonTrivial(out s_typeHash); internal Identity(string sql, CommandType? commandType, string connectionString, Type type, Type? parametersType, int gridIndex = 0) : base(sql, commandType, connectionString, type, parametersType, s_typeHash, gridIndex) {} internal Identity(string sql, CommandType? commandType, IDbConnection connection, Type type, Type? parametersType, int gridIndex = 0) : base(sql, commandType, connection.ConnectionString, type, parametersType, s_typeHash, gridIndex) { } static int CountNonTrivial(out int hashCode) { int hashCodeLocal = 0; int count = 0; bool Map() { if(typeof(T) != typeof(DontMap)) { count++; hashCodeLocal = (hashCodeLocal * 23) + (typeof(T).GetHashCode()); return true; } return false; } _ = Map() && Map() && Map() && Map() && Map() && Map() && Map(); hashCode = hashCodeLocal; return count; } internal override int TypeCount => s_typeCount; internal override Type GetType(int index) => index switch { 0 => typeof(TFirst), 1 => typeof(TSecond), 2 => typeof(TThird), 3 => typeof(TFourth), 4 => typeof(TFifth), 5 => typeof(TSixth), 6 => typeof(TSeventh), _ => base.GetType(index), }; } internal sealed class IdentityWithTypes : Identity { private readonly Type[] _types; internal IdentityWithTypes(string sql, CommandType? commandType, string connectionString, Type type, Type? parametersType, Type[] otherTypes, int gridIndex = 0) : base(sql, commandType, connectionString, type, parametersType, HashTypes(otherTypes), gridIndex) { _types = otherTypes ?? Type.EmptyTypes; } internal IdentityWithTypes(string sql, CommandType? commandType, IDbConnection connection, Type type, Type? parametersType, Type[] otherTypes, int gridIndex = 0) : base(sql, commandType, connection.ConnectionString, type, parametersType, HashTypes(otherTypes), gridIndex) { _types = otherTypes ?? Type.EmptyTypes; } internal override int TypeCount => _types.Length; internal override Type GetType(int index) => _types[index]; static int HashTypes(Type[] types) { var hashCode = 0; if (types is not null) { foreach (var t in types) { hashCode = (hashCode * 23) + (t?.GetHashCode() ?? 0); } } return hashCode; } } /// /// Identity of a cached query in Dapper, used for extensibility. /// public class Identity : IEquatable { internal virtual int TypeCount => 0; internal virtual Type GetType(int index) => throw new IndexOutOfRangeException(nameof(index)); #pragma warning disable CS0618 // Type or member is obsolete internal Identity ForGrid(Type primaryType, int gridIndex) => new Identity(sql, commandType, connectionString, primaryType, parametersType, gridIndex); internal Identity ForGrid(Type primaryType, int gridIndex) => new Identity(sql, commandType, connectionString, primaryType, parametersType, 0, gridIndex); internal Identity ForGrid(Type primaryType, Type[] otherTypes, int gridIndex) => (otherTypes is null || otherTypes.Length == 0) ? new Identity(sql, commandType, connectionString, primaryType, parametersType, 0, gridIndex) : new IdentityWithTypes(sql, commandType, connectionString, primaryType, parametersType, otherTypes, gridIndex); /// /// Create an identity for use with DynamicParameters, internal use only. /// /// The parameters type to create an for. /// public Identity ForDynamicParameters(Type type) => new Identity(sql, commandType, connectionString, this.type, type, 0, -1); #pragma warning restore CS0618 // Type or member is obsolete internal Identity(string sql, CommandType? commandType, IDbConnection connection, Type? type, Type? parametersType) : this(sql, commandType, connection.ConnectionString, type, parametersType, 0, 0) { /* base call */ } private protected Identity(string sql, CommandType? commandType, string connectionString, Type? type, Type? parametersType, int otherTypesHash, int gridIndex) { #pragma warning disable CS0618 // Type or member is obsolete this.sql = sql; this.commandType = commandType; this.connectionString = connectionString; this.type = type; this.parametersType = parametersType; this.gridIndex = gridIndex; unchecked { hashCode = 17; // we *know* we are using this in a dictionary, so pre-compute this hashCode = (hashCode * 23) + commandType.GetHashCode(); hashCode = (hashCode * 23) + gridIndex.GetHashCode(); hashCode = (hashCode * 23) + (sql?.GetHashCode() ?? 0); hashCode = (hashCode * 23) + (type?.GetHashCode() ?? 0); hashCode = (hashCode * 23) + otherTypesHash; hashCode = (hashCode * 23) + (connectionString is null ? 0 : connectionStringComparer.GetHashCode(connectionString)); hashCode = (hashCode * 23) + (parametersType?.GetHashCode() ?? 0); } #pragma warning restore CS0618 // Type or member is obsolete } /// /// Whether this equals another. /// /// The other to compare to. public override bool Equals(object? obj) => Equals(obj as Identity); /// /// The raw SQL command. /// [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] [Obsolete("Please use " + nameof(Sql) + ". This API may be removed at a later date.")] public readonly string sql; /// /// The raw SQL command. /// #pragma warning disable CS0618 // Type or member is obsolete public string Sql => sql; #pragma warning restore CS0618 // Type or member is obsolete /// /// The SQL command type. /// [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] [Obsolete("Please use " + nameof(CommandType) + ". This API may be removed at a later date.")] public readonly CommandType? commandType; /// /// The SQL command type. /// #pragma warning disable CS0618 // Type or member is obsolete public CommandType? CommandType => commandType; #pragma warning restore CS0618 // Type or member is obsolete /// /// The hash code of this Identity. /// [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] [Obsolete("Please use " + nameof(GetHashCode) + ". This API may be removed at a later date.")] public readonly int hashCode; /// /// The grid index (position in the reader) of this Identity. /// [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] [Obsolete("Please use " + nameof(GridIndex) + ". This API may be removed at a later date.")] public readonly int gridIndex; /// /// The grid index (position in the reader) of this Identity. /// #pragma warning disable CS0618 // Type or member is obsolete public int GridIndex => gridIndex; #pragma warning restore CS0618 // Type or member is obsolete /// /// The of this Identity. /// [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] [Obsolete("Please use " + nameof(Type) + ". This API may be removed at a later date.")] public readonly Type? type; /// /// The of this Identity. /// #pragma warning disable CS0618 // Type or member is obsolete public Type? Type => type; #pragma warning restore CS0618 // Type or member is obsolete /// /// The connection string for this Identity. /// [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] [Obsolete("This API may be removed at a later date.")] public readonly string connectionString; /// /// The type of the parameters object for this Identity. /// [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] [Obsolete("Please use " + nameof(ParametersType) + ". This API may be removed at a later date.")] public readonly Type? parametersType; /// /// The type of the parameters object for this Identity. /// #pragma warning disable CS0618 // Type or member is obsolete public Type? ParametersType => parametersType; #pragma warning restore CS0618 // Type or member is obsolete /// /// Gets the hash code for this identity. /// /// #pragma warning disable CS0618 // Type or member is obsolete public override int GetHashCode() => hashCode; #pragma warning restore CS0618 // Type or member is obsolete /// /// See object.ToString() /// #pragma warning disable CS0618 // Type or member is obsolete public override string ToString() => sql; #pragma warning restore CS0618 // Type or member is obsolete /// /// Compare 2 Identity objects /// /// The other object to compare. /// Whether the two are equal public bool Equals(Identity? other) { if (ReferenceEquals(this, other)) return true; if (other is null) return false; int typeCount; #pragma warning disable CS0618 // Type or member is obsolete return gridIndex == other.gridIndex && type == other.type && sql == other.sql && commandType == other.commandType && connectionStringComparer.Equals(connectionString, other.connectionString) && parametersType == other.parametersType && (typeCount = TypeCount) == other.TypeCount && (typeCount == 0 || TypesEqual(this, other, typeCount)); #pragma warning restore CS0618 // Type or member is obsolete } [MethodImpl(MethodImplOptions.NoInlining)] private static bool TypesEqual(Identity x, Identity y, int count) { if (y.TypeCount != count) return false; for(int i = 0; i < count; i++) { if (x.GetType(i) != y.GetType(i)) return false; } return true; } } } } ================================================ FILE: Dapper/SqlMapper.Link.cs ================================================ using System.Diagnostics.CodeAnalysis; using System.Threading; namespace Dapper { public static partial class SqlMapper { /// /// This is a micro-cache; suitable when the number of terms is controllable (a few hundred, for example), /// and strictly append-only; you cannot change existing values. All key matches are on **REFERENCE** /// equality. The type is fully thread-safe. /// /// The type to cache. /// The value type of the cache. internal class Link where TKey : class { public static void Clear(ref Link? head) => Interlocked.Exchange(ref head, null); public static bool TryGet(Link? link, TKey key, [NotNullWhen(true)] out TValue? value) { while (link is not null) { if ((object)key == (object)link.Key) { value = link.Value!; return true; } link = link.Tail; } value = default; return false; } public static bool TryAdd(ref Link? head, TKey key, ref TValue value) { bool tryAgain; do { var snapshot = Interlocked.CompareExchange(ref head, null, null); if (TryGet(snapshot, key, out TValue? found)) { // existing match; report the existing value instead value = found; return false; } var newNode = new Link(key, value, snapshot); // did somebody move our cheese? tryAgain = Interlocked.CompareExchange(ref head, newNode, snapshot) != snapshot; } while (tryAgain); return true; } private Link(TKey key, TValue value, Link? tail) { Key = key; Value = value; Tail = tail; } public TKey Key { get; } public TValue Value { get; } public Link? Tail { get; } } } } ================================================ FILE: Dapper/SqlMapper.LiteralToken.cs ================================================ using System; using System.Collections.Generic; namespace Dapper { public static partial class SqlMapper { /// /// Represents a placeholder for a value that should be replaced as a literal value in the resulting sql /// internal readonly struct LiteralToken { /// /// The text in the original command that should be replaced /// public string Token { get; } /// /// The name of the member referred to by the token /// public string Member { get; } internal LiteralToken(string token, string member) { Token = token; Member = member; } internal static IList None => Array.Empty(); } } } ================================================ FILE: Dapper/SqlMapper.Settings.cs ================================================ using System; using System.Data; using System.Threading; namespace Dapper { public static partial class SqlMapper { /// /// Permits specifying certain SqlMapper values globally. /// public static class Settings { // disable single row/result by default; prevents errors AFTER the select being detected properly private const CommandBehavior DefaultAllowedCommandBehaviors = ~(CommandBehavior.SingleResult | CommandBehavior.SingleRow); internal static CommandBehavior AllowedCommandBehaviors { get; private set; } = DefaultAllowedCommandBehaviors; private static void SetAllowedCommandBehaviors(CommandBehavior behavior, bool enabled) { if (enabled) AllowedCommandBehaviors |= behavior; else AllowedCommandBehaviors &= ~behavior; } /// /// Gets or sets whether Dapper should use the CommandBehavior.SingleResult optimization /// /// Note that a consequence of enabling this option is that errors that happen after the first select may not be reported public static bool UseSingleResultOptimization { get { return (AllowedCommandBehaviors & CommandBehavior.SingleResult) != 0; } set { SetAllowedCommandBehaviors(CommandBehavior.SingleResult, value); } } /// /// Gets or sets whether Dapper should use the CommandBehavior.SingleRow optimization /// /// Note that on some DB providers this optimization can have adverse performance impact public static bool UseSingleRowOptimization { get { return (AllowedCommandBehaviors & CommandBehavior.SingleRow) != 0; } set { SetAllowedCommandBehaviors(CommandBehavior.SingleRow, value); } } internal static bool DisableCommandBehaviorOptimizations(CommandBehavior behavior, Exception ex) { if (AllowedCommandBehaviors == DefaultAllowedCommandBehaviors && (behavior & (CommandBehavior.SingleResult | CommandBehavior.SingleRow)) != 0) { if (ex.Message.Contains(nameof(CommandBehavior.SingleResult)) || ex.Message.Contains(nameof(CommandBehavior.SingleRow))) { // some providers just allow these, so: try again without them and stop issuing them SetAllowedCommandBehaviors(CommandBehavior.SingleResult | CommandBehavior.SingleRow, false); return true; } } return false; } static Settings() { SetDefaults(); } /// /// Resets all Settings to their default values /// public static void SetDefaults() { CommandTimeout = null; ApplyNullValues = PadListExpansions = UseIncrementalPseudoPositionalParameterNames = false; AllowedCommandBehaviors = DefaultAllowedCommandBehaviors; FetchSize = InListStringSplitCount = -1; } /// /// Specifies the default Command Timeout for all Queries /// public static int? CommandTimeout { get; set; } /// /// Indicates whether nulls in data are silently ignored (default) vs actively applied and assigned to members /// public static bool ApplyNullValues { get; set; } /// /// Should list expansions be padded with null-valued parameters, to prevent query-plan saturation? For example, /// an 'in @foo' expansion with 7, 8 or 9 values will be sent as a list of 10 values, with 3, 2 or 1 of them null. /// The padding size is relative to the size of the list; "next 10" under 150, "next 50" under 500, /// "next 100" under 1500, etc. /// /// /// Caution: this should be treated with care if your DB provider (or the specific configuration) allows for null /// equality (aka "ansi nulls off"), as this may change the intent of your query; as such, this is disabled by /// default and must be enabled. /// public static bool PadListExpansions { get; set; } /// /// If set (non-negative), when performing in-list expansions of integer types ("where id in @ids", etc), switch to a string_split based /// operation if there are this many elements or more. Note that this feature requires SQL Server 2016 / compatibility level 130 (or above). /// public static int InListStringSplitCount { get; set; } = -1; /// /// If set, pseudo-positional parameters (i.e. ?foo?) are passed using auto-generated incremental names, i.e. "1", "2", "3" /// instead of the original name; for most scenarios, this is ignored since the name is redundant, but "snowflake" requires this. /// public static bool UseIncrementalPseudoPositionalParameterNames { get; set; } /// /// If assigned a non-negative value, then that value is applied to any commands FetchSize property, if it exists; /// see https://docs.oracle.com/en/database/oracle/oracle-database/18/odpnt/CommandFetchSize.html; note that this value /// can only be set globally - it is not intended for frequent/contextual changing. /// public static long FetchSize { get => Volatile.Read(ref s_FetchSize); set { if (Volatile.Read(ref s_FetchSize) != value) { Volatile.Write(ref s_FetchSize, value); CommandDefinition.ResetCommandInitCache(); // if this setting is useful: we've invalidated things } } } /// /// Indicates whether single-character parameter tokens (? etc) will be detected and used where possible; /// this feature is not recommended and will be disabled by default in future versions; /// where possible, prefer named parameters (@yourParam etc) or Dapper's "pseudo-positional" parameters (?yourParam? etc). /// public static bool SupportLegacyParameterTokens { get; set; } = true; private static long s_FetchSize = -1; } } } ================================================ FILE: Dapper/SqlMapper.TypeDeserializerCache.cs ================================================ using System; using System.Collections; using System.Collections.Generic; using System.Data.Common; using System.Text; namespace Dapper { public static partial class SqlMapper { private class TypeDeserializerCache { private TypeDeserializerCache(Type type) { this.type = type; } private static readonly Hashtable byType = new(); private readonly Type type; internal static void Purge(Type type) { lock (byType) { byType.Remove(type); } } internal static void Purge() { lock (byType) { byType.Clear(); } } internal static Func GetReader(Type type, DbDataReader reader, int startBound, int length, bool returnNullIfFirstMissing) { var found = (TypeDeserializerCache?)byType[type]; if (found is null) { lock (byType) { found = (TypeDeserializerCache?)byType[type]; if (found is null) { byType[type] = found = new TypeDeserializerCache(type); } } } return found.GetReader(reader, startBound, length, returnNullIfFirstMissing); } private readonly Dictionary> readers = new(); private readonly struct DeserializerKey : IEquatable { private readonly int startBound, length; private readonly bool returnNullIfFirstMissing; private readonly DbDataReader? reader; private readonly string[]? names; private readonly Type[]? types; private readonly int hashCode; public DeserializerKey(int hashCode, int startBound, int length, bool returnNullIfFirstMissing, DbDataReader reader, bool copyDown) { this.hashCode = hashCode; this.startBound = startBound; this.length = length; this.returnNullIfFirstMissing = returnNullIfFirstMissing; if (copyDown) { this.reader = null; names = new string[length]; types = new Type[length]; int index = startBound; for (int i = 0; i < length; i++) { names[i] = reader.GetName(index); types[i] = reader.GetFieldType(index++); } } else { this.reader = reader; names = null; types = null; } } public override int GetHashCode() => hashCode; public override string ToString() { // only used in the debugger if (names is not null) { return string.Join(", ", names); } if (reader is not null) { var sb = new StringBuilder(); int index = startBound; for (int i = 0; i < length; i++) { if (i != 0) sb.Append(", "); sb.Append(reader.GetName(index++)); } return sb.ToString(); } return base.ToString() ?? ""; } public override bool Equals(object? obj) => obj is DeserializerKey key && Equals(key); public bool Equals(DeserializerKey other) { if (hashCode != other.hashCode || startBound != other.startBound || length != other.length || returnNullIfFirstMissing != other.returnNullIfFirstMissing) { return false; // clearly different } for (int i = 0; i < length; i++) { if ((names?[i] ?? reader?.GetName(startBound + i)) != (other.names?[i] ?? other.reader?.GetName(startBound + i)) || (types?[i] ?? reader?.GetFieldType(startBound + i)) != (other.types?[i] ?? other.reader?.GetFieldType(startBound + i)) ) { return false; // different column name or type } } return true; } } private Func GetReader(DbDataReader reader, int startBound, int length, bool returnNullIfFirstMissing) { if (length < 0) length = reader.FieldCount - startBound; int hash = GetColumnHash(reader, startBound, length); if (returnNullIfFirstMissing) hash *= -27; // get a cheap key first: false means don't copy the values down var key = new DeserializerKey(hash, startBound, length, returnNullIfFirstMissing, reader, false); Func? deser; lock (readers) { if (readers.TryGetValue(key, out deser)) return deser!; } deser = GetTypeDeserializerImpl(type, reader, startBound, length, returnNullIfFirstMissing); // get a more expensive key: true means copy the values down so it can be used as a key later key = new DeserializerKey(hash, startBound, length, returnNullIfFirstMissing, reader, true); lock (readers) { return readers[key] = deser; } } } } } ================================================ FILE: Dapper/SqlMapper.TypeHandler.cs ================================================ using System; using System.Data; namespace Dapper { public static partial class SqlMapper { /// /// Base-class for simple type-handlers /// /// This this handler is for. public abstract class TypeHandler : ITypeHandler { /// /// Assign the value of a parameter before a command executes /// /// The parameter to configure /// Parameter value public abstract void SetValue(IDbDataParameter parameter, T? value); /// /// Parse a database value back to a typed value /// /// The value from the database /// The typed value public abstract T? Parse(object value); void ITypeHandler.SetValue(IDbDataParameter parameter, object value) { if (value is DBNull) { parameter.Value = value; } else { SetValue(parameter, (T?)value); } } object? ITypeHandler.Parse(Type destinationType, object value) { return Parse(value); } } /// /// Base-class for simple type-handlers that are based around strings /// /// This this handler is for. public abstract class StringTypeHandler : TypeHandler { /// /// Parse a string into the expected type (the string will never be null) /// /// The string to parse. protected abstract T Parse(string xml); /// /// Format an instance into a string (the instance will never be null) /// /// The string to format. protected abstract string Format(T xml); /// /// Assign the value of a parameter before a command executes /// /// The parameter to configure /// Parameter value public override void SetValue(IDbDataParameter parameter, T? value) { parameter.Value = value is null ? (object)DBNull.Value : Format(value); } /// /// Parse a database value back to a typed value /// /// The value from the database /// The typed value public override T Parse(object value) { if (value is null || value is DBNull) return default!; return Parse((string)value); } } } } ================================================ FILE: Dapper/SqlMapper.TypeHandlerCache.cs ================================================ using System; using System.ComponentModel; using System.Data; namespace Dapper { public static partial class SqlMapper { /// /// Not intended for direct usage /// /// The type to have a cache for. [Obsolete(ObsoleteInternalUsageOnly, false)] [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] public static class TypeHandlerCache { /// /// Not intended for direct usage. /// /// The object to parse. [Obsolete(ObsoleteInternalUsageOnly, true)] public static T? Parse(object value) => (T?)handler.Parse(typeof(T), value); /// /// Not intended for direct usage. /// /// The parameter to set a value for. /// The value to set. [Obsolete(ObsoleteInternalUsageOnly, true)] public static void SetValue(IDbDataParameter parameter, object value) => handler.SetValue(parameter, value); internal static void SetHandler(ITypeHandler handler) { TypeHandlerCache.handler = handler; } private static ITypeHandler handler = null!; } } } ================================================ FILE: Dapper/SqlMapper.cs ================================================ /* License: http://www.apache.org/licenses/LICENSE-2.0 Home page: https://github.com/DapperLib/Dapper-dot-net */ using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Data.Common; using System.Data.SqlTypes; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Xml; using System.Xml.Linq; namespace Dapper { /// /// Dapper, a light weight object mapper for ADO.NET /// public static partial class SqlMapper { private class PropertyInfoByNameComparer : IComparer { public int Compare(PropertyInfo? x, PropertyInfo? y) => string.CompareOrdinal(x?.Name, y?.Name); } private static int GetColumnHash(DbDataReader reader, int startBound = 0, int length = -1) { unchecked { int max = length < 0 ? reader.FieldCount : startBound + length; int hash = (-37 * startBound) + max; for (int i = startBound; i < max; i++) { object tmp = reader.GetName(i); hash = (-79 * ((hash * 31) + (tmp?.GetHashCode() ?? 0))) + (reader.GetFieldType(i)?.GetHashCode() ?? 0); } return hash; } } /// /// Called if the query cache is purged via PurgeQueryCache /// public static event EventHandler? QueryCachePurged; private static void OnQueryCachePurged() { var handler = QueryCachePurged; handler?.Invoke(null, EventArgs.Empty); } private static readonly System.Collections.Concurrent.ConcurrentDictionary _queryCache = new(); private static void SetQueryCache(Identity key, CacheInfo value) { if (Interlocked.Increment(ref collect) == COLLECT_PER_ITEMS) { CollectCacheGarbage(); } _queryCache[key] = value; } private static void CollectCacheGarbage() { try { foreach (var pair in _queryCache) { if (pair.Value.GetHitCount() <= COLLECT_HIT_COUNT_MIN) { _queryCache.TryRemove(pair.Key, out var _); } } } finally { Interlocked.Exchange(ref collect, 0); } } private const int COLLECT_PER_ITEMS = 1000, COLLECT_HIT_COUNT_MIN = 0; private static int collect; private static bool TryGetQueryCache(Identity key, [NotNullWhen(true)] out CacheInfo? value) { if (_queryCache.TryGetValue(key, out value!)) { value.RecordHit(); return true; } value = null; return false; } /// /// Purge the query cache /// public static void PurgeQueryCache() { _queryCache.Clear(); TypeDeserializerCache.Purge(); OnQueryCachePurged(); } private static void PurgeQueryCacheByType(Type type) { foreach (var entry in _queryCache) { if (entry.Key.Type == type) _queryCache.TryRemove(entry.Key, out _); } TypeDeserializerCache.Purge(type); } /// /// Return a count of all the cached queries by Dapper /// /// public static int GetCachedSQLCount() { return _queryCache.Count; } /// /// Return a list of all the queries cached by Dapper /// /// /// public static IEnumerable> GetCachedSQL(int ignoreHitCountAbove = int.MaxValue) { #pragma warning disable CS0618 // Type or member is obsolete var data = _queryCache.Select(pair => Tuple.Create(pair.Key.connectionString, pair.Key.Sql, pair.Value.GetHitCount())); #pragma warning restore CS0618 // Type or member is obsolete return (ignoreHitCountAbove < int.MaxValue) ? data.Where(tuple => tuple.Item3 <= ignoreHitCountAbove) : data; } /// /// Deep diagnostics only: find any hash collisions in the cache /// public static IEnumerable> GetHashCollissions() // legacy incorrect spelling, oops { var counts = new Dictionary(); foreach (var key in _queryCache.Keys) { var hash = key.GetHashCode(); if (!counts.TryGetValue(hash, out int count)) { counts.Add(hash, 1); } else { counts[hash] = count + 1; } } return from pair in counts where pair.Value > 1 select Tuple.Create(pair.Key, pair.Value); } private static Dictionary typeMap; [Flags] internal enum TypeMapEntryFlags { None = 0, SetType = 1 << 0, UseGetFieldValue = 1 << 1, } internal readonly struct TypeMapEntry : IEquatable { public readonly DbType DbType { get; } public readonly TypeMapEntryFlags Flags; public TypeMapEntry(DbType dbType, TypeMapEntryFlags flags) { DbType = dbType; Flags = flags; } public override int GetHashCode() => (int)DbType ^ (int)Flags; public override string ToString() => $"{DbType}, {Flags}"; public override bool Equals(object? obj) => obj is TypeMapEntry other && Equals(other); public bool Equals(TypeMapEntry other) => other.DbType == DbType && other.Flags == Flags; public static readonly TypeMapEntry DoNotSet = new((DbType)(-2), TypeMapEntryFlags.None), DoNotSetFieldValue = new((DbType)(-2), TypeMapEntryFlags.UseGetFieldValue), DecimalFieldValue = new(DbType.Decimal, TypeMapEntryFlags.SetType | TypeMapEntryFlags.UseGetFieldValue); public static implicit operator TypeMapEntry(DbType dbType) => new(dbType, TypeMapEntryFlags.SetType); } static SqlMapper() { typeMap = new Dictionary(41 #if NET6_0_OR_GREATER && DATEONLY + 4 // {Date|Time}Only[?] #endif ) { [typeof(byte)] = DbType.Byte, [typeof(sbyte)] = DbType.SByte, [typeof(short)] = DbType.Int16, [typeof(ushort)] = DbType.UInt16, [typeof(int)] = DbType.Int32, [typeof(uint)] = DbType.UInt32, [typeof(long)] = DbType.Int64, [typeof(ulong)] = DbType.UInt64, [typeof(float)] = DbType.Single, [typeof(double)] = DbType.Double, [typeof(decimal)] = DbType.Decimal, [typeof(bool)] = DbType.Boolean, [typeof(string)] = DbType.String, [typeof(char)] = DbType.StringFixedLength, [typeof(Guid)] = DbType.Guid, [typeof(DateTime)] = TypeMapEntry.DoNotSet, [typeof(DateTimeOffset)] = DbType.DateTimeOffset, [typeof(TimeSpan)] = TypeMapEntry.DoNotSet, [typeof(byte[])] = DbType.Binary, [typeof(byte?)] = DbType.Byte, [typeof(sbyte?)] = DbType.SByte, [typeof(short?)] = DbType.Int16, [typeof(ushort?)] = DbType.UInt16, [typeof(int?)] = DbType.Int32, [typeof(uint?)] = DbType.UInt32, [typeof(long?)] = DbType.Int64, [typeof(ulong?)] = DbType.UInt64, [typeof(float?)] = DbType.Single, [typeof(double?)] = DbType.Double, [typeof(decimal?)] = DbType.Decimal, [typeof(bool?)] = DbType.Boolean, [typeof(char?)] = DbType.StringFixedLength, [typeof(Guid?)] = DbType.Guid, [typeof(DateTime?)] = TypeMapEntry.DoNotSet, [typeof(DateTimeOffset?)] = DbType.DateTimeOffset, [typeof(TimeSpan?)] = TypeMapEntry.DoNotSet, [typeof(object)] = DbType.Object, [typeof(SqlDecimal)] = TypeMapEntry.DecimalFieldValue, [typeof(SqlDecimal?)] = TypeMapEntry.DecimalFieldValue, [typeof(SqlMoney)] = TypeMapEntry.DecimalFieldValue, [typeof(SqlMoney?)] = TypeMapEntry.DecimalFieldValue, #if NET6_0_OR_GREATER && DATEONLY [typeof(DateOnly)] = TypeMapEntry.DoNotSetFieldValue, [typeof(TimeOnly)] = TypeMapEntry.DoNotSetFieldValue, [typeof(DateOnly?)] = TypeMapEntry.DoNotSetFieldValue, [typeof(TimeOnly?)] = TypeMapEntry.DoNotSetFieldValue, #endif }; ResetTypeHandlers(false); } /// /// Clear the registered type handlers. /// public static void ResetTypeHandlers() => ResetTypeHandlers(true); [MemberNotNull(nameof(typeHandlers))] private static void ResetTypeHandlers(bool clone) { lock (typeHandlersSyncLock) { typeHandlers = []; AddTypeHandlerCore(typeof(DataTable), new DataTableHandler(), clone); AddTypeHandlerCore(typeof(XmlDocument), new XmlDocumentHandler(), clone); AddTypeHandlerCore(typeof(XDocument), new XDocumentHandler(), clone); AddTypeHandlerCore(typeof(XElement), new XElementHandler(), clone); } } /// /// Configure the specified type to be mapped to a given db-type. /// /// The type to map from. /// The database type to map to. public static void AddTypeMap(Type type, DbType dbType) => AddTypeMap(type, dbType, false); /// /// Configure the specified type to be mapped to a given db-type. /// /// The type to map from. /// The database type to map to. /// Whether to prefer over . public static void AddTypeMap(Type type, DbType dbType, bool useGetFieldValue) { // use clone, mutate, replace to avoid threading issues var snapshot = typeMap; var flags = TypeMapEntryFlags.None; if (dbType >= 0) { flags |= TypeMapEntryFlags.SetType; } if (useGetFieldValue) { flags |= TypeMapEntryFlags.UseGetFieldValue; } var value = new TypeMapEntry(dbType, flags); if (snapshot.TryGetValue(type, out var oldValue) && oldValue.Equals(value)) return; // nothing to do SetTypeMap(new Dictionary(snapshot) { [type] = value }); } private static void SetTypeMap(Dictionary value) { typeMap = value; // this cache is predicated on the contents of the type-map; reset it lock (s_ReadViaGetFieldValueCache) { s_ReadViaGetFieldValueCache.Clear(); } } /// /// Removes the specified type from the Type/DbType mapping table. /// /// The type to remove from the current map. public static void RemoveTypeMap(Type type) { // use clone, mutate, replace to avoid threading issues var snapshot = typeMap; if (!snapshot.ContainsKey(type)) return; // nothing to do var newCopy = new Dictionary(snapshot); newCopy.Remove(type); SetTypeMap(newCopy); } /// /// Configure the specified type to be processed by a custom handler. /// /// The type to handle. /// The handler to process the . public static void AddTypeHandler(Type type, ITypeHandler handler) => AddTypeHandlerCore(type, handler, true); /// /// Determine if the specified type will be processed by a custom handler. /// /// The type to handle. /// Boolean value specifying whether the type will be processed by a custom handler. public static bool HasTypeHandler(Type type) => typeHandlers.ContainsKey(type); /// /// Configure the specified type to be processed by a custom handler. /// /// The type to handle. /// The handler to process the . /// Whether to clone the current type handler map. [Obsolete("Please use " + nameof(AddTypeHandler), error: true)] [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] public static void AddTypeHandlerImpl(Type type, ITypeHandler? handler, bool clone) { // this method was accidentally made public; we'll mark it as illegal, but // preserve existing usage in compiled code; sorry about this! AddTypeHandlerCore(type, handler, true); // do not allow suppress clone } private static void AddTypeHandlerCore(Type type, ITypeHandler? handler, bool clone) { if (type is null) throw new ArgumentNullException(nameof(type)); Type? secondary = null; if (type.IsValueType) { var underlying = Nullable.GetUnderlyingType(type); if (underlying is null) { secondary = typeof(Nullable<>).MakeGenericType(type); // the Nullable // type is already the T } else { secondary = type; // the Nullable type = underlying; // the T } } // synchronize between callers mutating type-handlers; note that regular query // code may still be accessing the field, so we still use snapshot/mutate/swap; // the synchronize is just to prevent lost writes lock (typeHandlersSyncLock) { if (typeHandlers.TryGetValue(type, out var oldValue) && handler == oldValue) return; // nothing to do var newCopy = clone ? new Dictionary(typeHandlers) : typeHandlers; #pragma warning disable 618 typeof(TypeHandlerCache<>).MakeGenericType(type).GetMethod(nameof(TypeHandlerCache.SetHandler), BindingFlags.Static | BindingFlags.NonPublic)!.Invoke(null, [handler]); if (secondary is not null) { typeof(TypeHandlerCache<>).MakeGenericType(secondary).GetMethod(nameof(TypeHandlerCache.SetHandler), BindingFlags.Static | BindingFlags.NonPublic)!.Invoke(null, [handler]); } #pragma warning restore 618 if (handler is null) { newCopy.Remove(type); if (secondary is not null) newCopy.Remove(secondary); } else { newCopy[type] = handler; if (secondary is not null) newCopy[secondary] = handler; } typeHandlers = newCopy; } } /// /// Configure the specified type to be processed by a custom handler. /// /// The type to handle. /// The handler for the type . public static void AddTypeHandler(TypeHandler handler) => AddTypeHandlerCore(typeof(T), handler, true); private static Dictionary typeHandlers; private static readonly object typeHandlersSyncLock = new(); internal const string LinqBinary = "System.Data.Linq.Binary"; private const string ObsoleteInternalUsageOnly = "This method is for internal use only"; /// /// Get the DbType that maps to a given value. /// /// The parameter to configure the value for. /// The object to get a corresponding database type for. [Obsolete(ObsoleteInternalUsageOnly, false)] [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] public static void SetDbType(IDataParameter parameter, object value) { if (value is null || value is DBNull) return; var dbType = LookupDbType(value.GetType(), "n/a", false, out _); if (DynamicParameters.ShouldSetDbType(dbType)) { parameter.DbType = dbType.GetValueOrDefault(); } } /// /// OBSOLETE: For internal usage only. Lookup the DbType and handler for a given Type and member /// /// The type to lookup. /// The name (for error messages). /// Whether to demand a value (throw if missing). /// The handler for . [Obsolete(ObsoleteInternalUsageOnly, false)] [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] public static DbType? LookupDbType(Type type, string name, bool demand, out ITypeHandler? handler) { handler = null; var nullUnderlyingType = Nullable.GetUnderlyingType(type); if (nullUnderlyingType is not null) type = nullUnderlyingType; if (type.IsEnum && !typeMap.ContainsKey(type)) { type = Enum.GetUnderlyingType(type); } if (typeMap.TryGetValue(type, out var mapEntry)) { if ((mapEntry.Flags & TypeMapEntryFlags.SetType) == 0) { return null; } return mapEntry.DbType; } if (type.FullName == LinqBinary) { return DbType.Binary; } if (typeHandlers.TryGetValue(type, out handler)) { return DbType.Object; } if (typeof(IEnumerable).IsAssignableFrom(type)) { // auto-detect things like IEnumerable as a family if (type.IsInterface && type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>) && typeof(IEnumerable).IsAssignableFrom(type)) { var argTypes = type.GetGenericArguments(); if (typeof(IDataRecord).IsAssignableFrom(argTypes[0])) { try { handler = (ITypeHandler)Activator.CreateInstance( typeof(SqlDataRecordHandler<>).MakeGenericType(argTypes))!; AddTypeHandlerCore(type, handler, true); return DbType.Object; } catch { handler = null; } } } return DynamicParameters.EnumerableMultiParameter; } switch (type.FullName) { case "Microsoft.SqlServer.Types.SqlGeography": AddTypeHandler(type, handler = new UdtTypeHandler("geography")); return DbType.Object; case "Microsoft.SqlServer.Types.SqlGeometry": AddTypeHandler(type, handler = new UdtTypeHandler("geometry")); return DbType.Object; case "Microsoft.SqlServer.Types.SqlHierarchyId": AddTypeHandler(type, handler = new UdtTypeHandler("hierarchyid")); return DbType.Object; } if (demand) throw new NotSupportedException($"The member {name} of type {type.FullName} cannot be used as a parameter value"); return DbType.Object; } /// /// Obtains the data as a list; if it is *already* a list, the original object is returned without /// any duplication; otherwise, ToList() is invoked. /// /// The type of element in the list. /// The enumerable to return as a list. public static List AsList(this IEnumerable? source) => source switch { null => null!, List list => list, _ => Enumerable.ToList(source), }; /// /// Execute parameterized SQL. /// /// The connection to query on. /// The SQL to execute for this query. /// The parameters to use for this query. /// The transaction to use for this query. /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// The number of rows affected. public static int Execute(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered); return ExecuteImpl(cnn, ref command); } /// /// Execute parameterized SQL. /// /// The connection to execute on. /// The command to execute on this connection. /// The number of rows affected. public static int Execute(this IDbConnection cnn, CommandDefinition command) => ExecuteImpl(cnn, ref command); /// /// Execute parameterized SQL that selects a single value. /// /// The connection to execute on. /// The SQL to execute. /// The parameters to use for this command. /// The transaction to use for this command. /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// The first cell selected as . public static object? ExecuteScalar(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered); return ExecuteScalarImpl(cnn, ref command); } /// /// Execute parameterized SQL that selects a single value. /// /// The type to return. /// The connection to execute on. /// The SQL to execute. /// The parameters to use for this command. /// The transaction to use for this command. /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// The first cell returned, as . public static T? ExecuteScalar(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered); return ExecuteScalarImpl(cnn, ref command); } /// /// Execute parameterized SQL that selects a single value. /// /// The connection to execute on. /// The command to execute. /// The first cell selected as . public static object? ExecuteScalar(this IDbConnection cnn, CommandDefinition command) => ExecuteScalarImpl(cnn, ref command); /// /// Execute parameterized SQL that selects a single value. /// /// The type to return. /// The connection to execute on. /// The command to execute. /// The first cell selected as . public static T? ExecuteScalar(this IDbConnection cnn, CommandDefinition command) => ExecuteScalarImpl(cnn, ref command); private static IEnumerable? GetMultiExec(object? param) { #pragma warning disable IDE0038 // Use pattern matching - complicated enough! return (param is IEnumerable #pragma warning restore IDE0038 // Use pattern matching && !(param is string || param is IEnumerable> || param is IDynamicParameters) ) ? (IEnumerable)param : null; } private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition command) { object? param = command.Parameters; IEnumerable? multiExec = GetMultiExec(param); Identity identity; CacheInfo? info = null; if (multiExec is not null) { if ((command.Flags & CommandFlags.Pipelined) != 0) { // this includes all the code for concurrent/overlapped query return ExecuteMultiImplAsync(cnn, command, multiExec).Result; } bool isFirst = true; int total = 0; bool wasClosed = cnn.State == ConnectionState.Closed; try { if (wasClosed) cnn.Open(); using (var cmd = command.SetupCommand(cnn, null)) { string? masterSql = null; foreach (var obj in multiExec) { if (isFirst) { masterSql = cmd.CommandText; isFirst = false; identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType()); info = GetCacheInfo(identity, obj, command.AddToCache); } else { cmd.CommandText = masterSql; // because we do magic replacements on "in" etc cmd.Parameters.Clear(); // current code is Add-tastic } info!.ParamReader!(cmd, obj); total += cmd.ExecuteNonQuery(); } } command.OnCompleted(); } finally { if (wasClosed) cnn.Close(); } return total; } // nice and simple if (param is not null) { identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, null, param.GetType()); info = GetCacheInfo(identity, param, command.AddToCache); } return ExecuteCommand(cnn, ref command, param is null ? null : info!.ParamReader); } /// /// Execute parameterized SQL and return an . /// /// The connection to execute on. /// The SQL to execute. /// The parameters to use for this command. /// The transaction to use for this command. /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// An that can be used to iterate over the results of the SQL query. /// /// This is typically used when the results of a query are not processed by Dapper, for example, used to fill a /// or . /// /// /// /// /// /// public static IDataReader ExecuteReader(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered); var reader = ExecuteReaderImpl(cnn, ref command, CommandBehavior.Default, out IDbCommand? dbcmd); return DbWrappedReader.Create(dbcmd, reader); } /// /// Execute parameterized SQL and return an . /// /// The connection to execute on. /// The command to execute. /// An that can be used to iterate over the results of the SQL query. /// /// This is typically used when the results of a query are not processed by Dapper, for example, used to fill a /// or . /// public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinition command) { var reader = ExecuteReaderImpl(cnn, ref command, CommandBehavior.Default, out IDbCommand? dbcmd); return DbWrappedReader.Create(dbcmd, reader); } /// /// Execute parameterized SQL and return an . /// /// The connection to execute on. /// The command to execute. /// The flags for this reader. /// An that can be used to iterate over the results of the SQL query. /// /// This is typically used when the results of a query are not processed by Dapper, for example, used to fill a /// or . /// public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinition command, CommandBehavior commandBehavior) { var reader = ExecuteReaderImpl(cnn, ref command, commandBehavior, out IDbCommand? dbcmd); return DbWrappedReader.Create(dbcmd, reader); } /// /// Return a sequence of dynamic objects with properties matching the columns. /// /// The connection to query on. /// The SQL to execute for the query. /// The parameters to pass, if any. /// The transaction to use, if any. /// Whether to buffer the results in memory. /// The command timeout (in seconds). /// The type of command to execute. /// Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object> [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static IEnumerable Query(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) => Query(cnn, sql, param, transaction, buffered, commandTimeout, commandType); /// /// Return a dynamic object with properties matching the columns. /// /// The connection to query on. /// The SQL to execute for the query. /// The parameters to pass, if any. /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static dynamic QueryFirst(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryFirst(cnn, sql, param, transaction, commandTimeout, commandType); /// /// Return a dynamic object with properties matching the columns. /// /// The connection to query on. /// The SQL to execute for the query. /// The parameters to pass, if any. /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static dynamic? QueryFirstOrDefault(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryFirstOrDefault(cnn, sql, param, transaction, commandTimeout, commandType); /// /// Return a dynamic object with properties matching the columns. /// /// The connection to query on. /// The SQL to execute for the query. /// The parameters to pass, if any. /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static dynamic QuerySingle(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QuerySingle(cnn, sql, param, transaction, commandTimeout, commandType); /// /// Return a dynamic object with properties matching the columns. /// /// The connection to query on. /// The SQL to execute for the query. /// The parameters to pass, if any. /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static dynamic? QuerySingleOrDefault(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QuerySingleOrDefault(cnn, sql, param, transaction, commandTimeout, commandType); /// /// Executes a query, returning the data typed as . /// /// The type of results to return. /// The connection to query on. /// The SQL to execute for the query. /// The parameters to pass, if any. /// The transaction to use, if any. /// Whether to buffer results in memory. /// The command timeout (in seconds). /// The type of command to execute. /// /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static IEnumerable Query(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None); var data = QueryImpl(cnn, command, typeof(T)); return command.Buffered ? data.ToList() : data; } /// /// Executes a single-row query, returning the data typed as . /// /// The type of result to return. /// The connection to query on. /// The SQL to execute for the query. /// The parameters to pass, if any. /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. /// /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static T QueryFirst(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); return QueryRowImpl(cnn, Row.First, ref command, typeof(T)); } /// /// Executes a single-row query, returning the data typed as . /// /// The type of result to return. /// The connection to query on. /// The SQL to execute for the query. /// The parameters to pass, if any. /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. /// /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static T? QueryFirstOrDefault(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); return QueryRowImpl(cnn, Row.FirstOrDefault, ref command, typeof(T)); } /// /// Executes a single-row query, returning the data typed as . /// /// The type of result to return. /// The connection to query on. /// The SQL to execute for the query. /// The parameters to pass, if any. /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. /// /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static T QuerySingle(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); return QueryRowImpl(cnn, Row.Single, ref command, typeof(T)); } /// /// Executes a single-row query, returning the data typed as . /// /// The type of result to return. /// The connection to query on. /// The SQL to execute for the query. /// The parameters to pass, if any. /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. /// /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static T? QuerySingleOrDefault(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); return QueryRowImpl(cnn, Row.SingleOrDefault, ref command, typeof(T)); } /// /// Executes a single-row query, returning the data typed as . /// /// The connection to query on. /// The type to return. /// The SQL to execute for the query. /// The parameters to pass, if any. /// The transaction to use, if any. /// Whether to buffer results in memory. /// The command timeout (in seconds). /// The type of command to execute. /// is null. /// /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static IEnumerable Query(this IDbConnection cnn, Type type, string sql, object? param = null, IDbTransaction? transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) { if (type is null) throw new ArgumentNullException(nameof(type)); var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None); var data = QueryImpl(cnn, command, type); return command.Buffered ? data.ToList() : data; } /// /// Executes a single-row query, returning the data typed as . /// /// The connection to query on. /// The type to return. /// The SQL to execute for the query. /// The parameters to pass, if any. /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. /// is null. /// /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static object QueryFirst(this IDbConnection cnn, Type type, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { if (type is null) throw new ArgumentNullException(nameof(type)); var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); return QueryRowImpl(cnn, Row.First, ref command, type); } /// /// Executes a single-row query, returning the data typed as . /// /// The connection to query on. /// The type to return. /// The SQL to execute for the query. /// The parameters to pass, if any. /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. /// is null. /// /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static object? QueryFirstOrDefault(this IDbConnection cnn, Type type, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { if (type is null) throw new ArgumentNullException(nameof(type)); var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); return QueryRowImpl(cnn, Row.FirstOrDefault, ref command, type); } /// /// Executes a single-row query, returning the data typed as . /// /// The connection to query on. /// The type to return. /// The SQL to execute for the query. /// The parameters to pass, if any. /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. /// is null. /// /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static object QuerySingle(this IDbConnection cnn, Type type, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { if (type is null) throw new ArgumentNullException(nameof(type)); var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); return QueryRowImpl(cnn, Row.Single, ref command, type); } /// /// Executes a single-row query, returning the data typed as . /// /// The connection to query on. /// The type to return. /// The SQL to execute for the query. /// The parameters to pass, if any. /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. /// is null. /// /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static object? QuerySingleOrDefault(this IDbConnection cnn, Type type, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { if (type is null) throw new ArgumentNullException(nameof(type)); var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); return QueryRowImpl(cnn, Row.SingleOrDefault, ref command, type); } /// /// Executes a query, returning the data typed as . /// /// The type of results to return. /// The connection to query on. /// The command used to query on this connection. /// /// A sequence of data of ; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// public static IEnumerable Query(this IDbConnection cnn, CommandDefinition command) { var data = QueryImpl(cnn, command, typeof(T)); return command.Buffered ? data.ToList() : data; } /// /// Executes a query, returning the data typed as . /// /// The type of results to return. /// The connection to query on. /// The command used to query on this connection. /// /// A single instance or null of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// public static T QueryFirst(this IDbConnection cnn, CommandDefinition command) => QueryRowImpl(cnn, Row.First, ref command, typeof(T)); /// /// Executes a query, returning the data typed as . /// /// The type of results to return. /// The connection to query on. /// The command used to query on this connection. /// /// A single or null instance of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// public static T? QueryFirstOrDefault(this IDbConnection cnn, CommandDefinition command) => QueryRowImpl(cnn, Row.FirstOrDefault, ref command, typeof(T)); /// /// Executes a query, returning the data typed as . /// /// The type of results to return. /// The connection to query on. /// The command used to query on this connection. /// /// A single instance of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// public static T QuerySingle(this IDbConnection cnn, CommandDefinition command) => QueryRowImpl(cnn, Row.Single, ref command, typeof(T)); /// /// Executes a query, returning the data typed as . /// /// The type of results to return. /// The connection to query on. /// The command used to query on this connection. /// /// A single instance of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// public static T? QuerySingleOrDefault(this IDbConnection cnn, CommandDefinition command) => QueryRowImpl(cnn, Row.SingleOrDefault, ref command, typeof(T)); /// /// Execute a command that returns multiple result sets, and access each in turn. /// /// The connection to query on. /// The SQL to execute for this query. /// The parameters to use for this query. /// The transaction to use for this query. /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? public static GridReader QueryMultiple(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered); return QueryMultipleImpl(cnn, ref command); } /// /// Execute a command that returns multiple result sets, and access each in turn. /// /// The connection to query on. /// The command to execute for this query. public static GridReader QueryMultiple(this IDbConnection cnn, CommandDefinition command) => QueryMultipleImpl(cnn, ref command); private static GridReader QueryMultipleImpl(this IDbConnection cnn, ref CommandDefinition command) { object? param = command.Parameters; var identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, typeof(GridReader), param?.GetType()); CacheInfo info = GetCacheInfo(identity, param, command.AddToCache); IDbCommand? cmd = null; DbDataReader? reader = null; bool wasClosed = cnn.State == ConnectionState.Closed; try { if (wasClosed) cnn.Open(); cmd = command.SetupCommand(cnn, info.ParamReader); reader = ExecuteReaderWithFlagsFallback(cmd, wasClosed, CommandBehavior.SequentialAccess); var result = new GridReader(cmd, reader, identity, command.Parameters as DynamicParameters, command.AddToCache); cmd = null; // now owned by result wasClosed = false; // *if* the connection was closed and we got this far, then we now have a reader // with the CloseConnection flag, so the reader will deal with the connection; we // still need something in the "finally" to ensure that broken SQL still results // in the connection closing itself return result; } catch { if (reader is not null) { if (!reader.IsClosed) { try { cmd?.Cancel(); } catch { /* don't spoil any existing exception */ } } reader.Dispose(); } cmd?.Parameters.Clear(); cmd?.Dispose(); if (wasClosed) cnn.Close(); throw; } } private static DbDataReader ExecuteReaderWithFlagsFallback(IDbCommand cmd, bool wasClosed, CommandBehavior behavior) { try { return GetDbDataReader(cmd.ExecuteReader(GetBehavior(wasClosed, behavior))); } catch (ArgumentException ex) { // thanks, Sqlite! if (Settings.DisableCommandBehaviorOptimizations(behavior, ex)) { // we can retry; this time it will have different flags return GetDbDataReader(cmd.ExecuteReader(GetBehavior(wasClosed, behavior))); } throw; } } private static IEnumerable QueryImpl(this IDbConnection cnn, CommandDefinition command, Type effectiveType) { object? param = command.Parameters; var identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, effectiveType, param?.GetType()); var info = GetCacheInfo(identity, param, command.AddToCache); IDbCommand? cmd = null; DbDataReader? reader = null; bool wasClosed = cnn.State == ConnectionState.Closed; try { cmd = command.SetupCommand(cnn, info.ParamReader); if (wasClosed) cnn.Open(); reader = ExecuteReaderWithFlagsFallback(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult); wasClosed = false; // *if* the connection was closed and we got this far, then we now have a reader // with the CloseConnection flag, so the reader will deal with the connection; we // still need something in the "finally" to ensure that broken SQL still results // in the connection closing itself var tuple = info.Deserializer; int hash = GetColumnHash(reader); if (tuple.Func is null || tuple.Hash != hash) { if (reader.FieldCount == 0) //https://code.google.com/p/dapper-dot-net/issues/detail?id=57 yield break; tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false)); if (command.AddToCache) SetQueryCache(identity, info); } var func = tuple.Func; var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType; while (reader.Read()) { object? val = func(reader); yield return GetValue(reader, effectiveType, val); } while (reader.NextResult()) { /* ignore subsequent result sets */ } // happy path; close the reader cleanly - no // need for "Cancel" etc reader.Dispose(); reader = null; command.OnCompleted(); } finally { if (reader is not null) { if (!reader.IsClosed) { try { cmd?.Cancel(); } catch { /* don't spoil any existing exception */ } } reader.Dispose(); } if (wasClosed) cnn.Close(); cmd?.Parameters.Clear(); cmd?.Dispose(); } } [Flags] internal enum Row { First = 0, FirstOrDefault = 1, // & FirstOrDefault != 0: allow zero rows Single = 2, // & Single != 0: demand at least one row SingleOrDefault = 3 } private static readonly int[] ErrTwoRows = new int[2], ErrZeroRows = []; private static void ThrowMultipleRows(Row row) { _ = row switch { Row.Single => ErrTwoRows.Single(), Row.SingleOrDefault => ErrTwoRows.SingleOrDefault(), _ => throw new InvalidOperationException(), }; } private static void ThrowZeroRows(Row row) { _ = row switch { // get the standard exception from the runtime Row.First => ErrZeroRows.First(), Row.Single => ErrZeroRows.Single(), _ => throw new InvalidOperationException(), }; } private static T QueryRowImpl(IDbConnection cnn, Row row, ref CommandDefinition command, Type effectiveType) { object? param = command.Parameters; var identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, effectiveType, param?.GetType()); var info = GetCacheInfo(identity, param, command.AddToCache); IDbCommand? cmd = null; DbDataReader? reader = null; bool wasClosed = cnn.State == ConnectionState.Closed; try { cmd = command.SetupCommand(cnn, info.ParamReader); if (wasClosed) cnn.Open(); reader = ExecuteReaderWithFlagsFallback(cmd, wasClosed, (row & Row.Single) != 0 ? CommandBehavior.SequentialAccess | CommandBehavior.SingleResult // need to allow multiple rows, to check fail condition : CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow); wasClosed = false; // *if* the connection was closed and we got this far, then we now have a reader T result = default!; if (reader.Read() && reader.FieldCount != 0) { // with the CloseConnection flag, so the reader will deal with the connection; we // still need something in the "finally" to ensure that broken SQL still results // in the connection closing itself result = ReadRow(info, identity, ref command, effectiveType, reader); if ((row & Row.Single) != 0 && reader.Read()) ThrowMultipleRows(row); while (reader.Read()) { /* ignore subsequent rows */ } } else if ((row & Row.FirstOrDefault) == 0) // demanding a row, and don't have one { ThrowZeroRows(row); } while (reader.NextResult()) { /* ignore subsequent result sets */ } // happy path; close the reader cleanly - no // need for "Cancel" etc reader.Dispose(); reader = null; command.OnCompleted(); return result; } finally { if (reader is not null) { if (!reader.IsClosed) { try { cmd?.Cancel(); } catch { /* don't spoil any existing exception */ } } reader.Dispose(); } if (wasClosed) cnn.Close(); cmd?.Parameters.Clear(); cmd?.Dispose(); } } /// /// Shared value deserialization path for QueryRowImpl and QueryRowAsync /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private static T ReadRow(CacheInfo info, Identity identity, ref CommandDefinition command, Type effectiveType, DbDataReader reader) { var tuple = info.Deserializer; int hash = GetColumnHash(reader); if (tuple.Func is null || tuple.Hash != hash) { tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false)); if (command.AddToCache) SetQueryCache(identity, info); } var func = tuple.Func; object? val = func(reader); return GetValue(reader, effectiveType, val); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static T GetValue(DbDataReader reader, Type effectiveType, object? val) { if (val is T tVal) { return tVal; } else if (val is null && (!effectiveType.IsValueType || Nullable.GetUnderlyingType(effectiveType) is not null)) { return default!; } else if (val is Array array && typeof(T).IsArray) { var elementType = typeof(T).GetElementType()!; var result = Array.CreateInstance(elementType, array.Length); for (int i = 0; i < array.Length; i++) result.SetValue(Convert.ChangeType(array.GetValue(i), elementType, CultureInfo.InvariantCulture), i); return (T)(object)result; } else { try { var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType; return (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture)!; } catch (Exception ex) { #pragma warning disable CS0618 // Type or member is obsolete ThrowDataException(ex, 0, reader, val); #pragma warning restore CS0618 // Type or member is obsolete return default!; // For the compiler - we've already thrown } } } /// /// Perform a multi-mapping query with 2 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. /// The second type in the recordset. /// The combined type to return. /// The connection to query on. /// The SQL to execute for this query. /// The function to map row types to the return type. /// The parameters to use for this query. /// The transaction to use for this query. /// Whether to buffer the results in memory. /// The field we should split and read the second object from (default: "Id"). /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object? param = null, IDbTransaction? transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); /// /// Perform a multi-mapping query with 3 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. /// The second type in the recordset. /// The third type in the recordset. /// The combined type to return. /// The connection to query on. /// The SQL to execute for this query. /// The function to map row types to the return type. /// The parameters to use for this query. /// The transaction to use for this query. /// Whether to buffer the results in memory. /// The field we should split and read the second object from (default: "Id"). /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object? param = null, IDbTransaction? transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); /// /// Perform a multi-mapping query with 4 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. /// The second type in the recordset. /// The third type in the recordset. /// The fourth type in the recordset. /// The combined type to return. /// The connection to query on. /// The SQL to execute for this query. /// The function to map row types to the return type. /// The parameters to use for this query. /// The transaction to use for this query. /// Whether to buffer the results in memory. /// The field we should split and read the second object from (default: "Id"). /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object? param = null, IDbTransaction? transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); /// /// Perform a multi-mapping query with 5 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. /// The second type in the recordset. /// The third type in the recordset. /// The fourth type in the recordset. /// The fifth type in the recordset. /// The combined type to return. /// The connection to query on. /// The SQL to execute for this query. /// The function to map row types to the return type. /// The parameters to use for this query. /// The transaction to use for this query. /// Whether to buffer the results in memory. /// The field we should split and read the second object from (default: "Id"). /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object? param = null, IDbTransaction? transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); /// /// Perform a multi-mapping query with 6 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. /// The second type in the recordset. /// The third type in the recordset. /// The fourth type in the recordset. /// The fifth type in the recordset. /// The sixth type in the recordset. /// The combined type to return. /// The connection to query on. /// The SQL to execute for this query. /// The function to map row types to the return type. /// The parameters to use for this query. /// The transaction to use for this query. /// Whether to buffer the results in memory. /// The field we should split and read the second object from (default: "Id"). /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object? param = null, IDbTransaction? transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); /// /// Perform a multi-mapping query with 7 input types. If you need more types -> use Query with Type[] parameter. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. /// The second type in the recordset. /// The third type in the recordset. /// The fourth type in the recordset. /// The fifth type in the recordset. /// The sixth type in the recordset. /// The seventh type in the recordset. /// The combined type to return. /// The connection to query on. /// The SQL to execute for this query. /// The function to map row types to the return type. /// The parameters to use for this query. /// The transaction to use for this query. /// Whether to buffer the results in memory. /// The field we should split and read the second object from (default: "Id"). /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object? param = null, IDbTransaction? transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); /// /// Perform a multi-mapping query with an arbitrary number of input types. /// This returns a single type, combined from the raw types via . /// /// The combined type to return. /// The connection to query on. /// The SQL to execute for this query. /// Array of types in the recordset. /// The function to map row types to the return type. /// The parameters to use for this query. /// The transaction to use for this query. /// Whether to buffer the results in memory. /// The field we should split and read the second object from (default: "Id"). /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static IEnumerable Query(this IDbConnection cnn, string sql, Type[] types, Func map, object? param = null, IDbTransaction? transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None); var results = MultiMapImpl(cnn, command, types, map, splitOn, null, null, true); return buffered ? results.ToList() : results; } private static IEnumerable MultiMap( this IDbConnection cnn, string sql, Delegate map, object? param, IDbTransaction? transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None); var results = MultiMapImpl(cnn, command, map, splitOn, null, null, true); return buffered ? results.ToList() : results; } private static IEnumerable MultiMapImpl(this IDbConnection? cnn, CommandDefinition command, Delegate map, string splitOn, DbDataReader? reader, Identity? identity, bool finalize) { object? param = command.Parameters; identity ??= new Identity(command.CommandText, command.CommandTypeDirect, cnn!, typeof(TFirst), param?.GetType()); CacheInfo cinfo = GetCacheInfo(identity, param, command.AddToCache); IDbCommand? ownedCommand = null; DbDataReader? ownedReader = null; bool wasClosed = cnn?.State == ConnectionState.Closed; try { if (reader is null) { ownedCommand = command.SetupCommand(cnn!, cinfo.ParamReader); if (wasClosed) cnn!.Open(); ownedReader = ExecuteReaderWithFlagsFallback(ownedCommand, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult); reader = ownedReader; } var deserializer = default(DeserializerState); Func[]? otherDeserializers; int hash = GetColumnHash(reader); if ((deserializer = cinfo.Deserializer).Func is null || (otherDeserializers = cinfo.OtherDeserializers) is null || hash != deserializer.Hash) { var deserializers = GenerateDeserializers(identity, splitOn, reader); deserializer = cinfo.Deserializer = new DeserializerState(hash, deserializers[0]); otherDeserializers = cinfo.OtherDeserializers = deserializers.Skip(1).ToArray(); if (command.AddToCache) SetQueryCache(identity, cinfo); } Func mapIt = GenerateMapper(deserializer.Func, otherDeserializers, map); if (mapIt is not null) { while (reader.Read()) { yield return mapIt(reader); } if (finalize) { while (reader.NextResult()) { /* ignore remaining result sets */ } command.OnCompleted(); } } } finally { try { ownedReader?.Dispose(); } finally { ownedCommand?.Parameters.Clear(); ownedCommand?.Dispose(); if (wasClosed) cnn!.Close(); } } } private static CommandBehavior GetBehavior(bool close, CommandBehavior @default) { return (close ? (@default | CommandBehavior.CloseConnection) : @default) & Settings.AllowedCommandBehaviors; } private static IEnumerable MultiMapImpl(this IDbConnection? cnn, CommandDefinition command, Type[] types, Func map, string splitOn, DbDataReader? reader, Identity? identity, bool finalize) { if (types.Length < 1) { throw new ArgumentException("you must provide at least one type to deserialize"); } object? param = command.Parameters; identity ??= new IdentityWithTypes(command.CommandText, command.CommandTypeDirect, cnn!, types[0], param?.GetType(), types); CacheInfo cinfo = GetCacheInfo(identity, param, command.AddToCache); IDbCommand? ownedCommand = null; DbDataReader? ownedReader = null; bool wasClosed = cnn?.State == ConnectionState.Closed; try { if (reader is null) { ownedCommand = command.SetupCommand(cnn!, cinfo.ParamReader); if (wasClosed) cnn!.Open(); ownedReader = ExecuteReaderWithFlagsFallback(ownedCommand, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult); reader = ownedReader; } DeserializerState deserializer; Func[]? otherDeserializers; int hash = GetColumnHash(reader); if ((deserializer = cinfo.Deserializer).Func is null || (otherDeserializers = cinfo.OtherDeserializers) is null || hash != deserializer.Hash) { var deserializers = GenerateDeserializers(identity, splitOn, reader); deserializer = cinfo.Deserializer = new DeserializerState(hash, deserializers[0]); otherDeserializers = cinfo.OtherDeserializers = deserializers.Skip(1).ToArray(); if (command.AddToCache) SetQueryCache(identity, cinfo); } Func mapIt = GenerateMapper(types.Length, deserializer.Func, otherDeserializers, map); if (mapIt is not null) { while (reader.Read()) { yield return mapIt(reader); } if (finalize) { while (reader.NextResult()) { /* ignore subsequent result sets */ } command.OnCompleted(); } } } finally { try { ownedReader?.Dispose(); } finally { ownedCommand?.Parameters.Clear(); ownedCommand?.Dispose(); if (wasClosed) cnn!.Close(); } } } private static Func GenerateMapper(Func deserializer, Func[] otherDeserializers, object map) => otherDeserializers.Length switch { 1 => r => ((Func)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r)), 2 => r => ((Func)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r)), 3 => r => ((Func)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r)), 4 => r => ((Func)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r), (TFifth)otherDeserializers[3](r)), 5 => r => ((Func)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r), (TFifth)otherDeserializers[3](r), (TSixth)otherDeserializers[4](r)), 6 => r => ((Func)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r), (TFifth)otherDeserializers[3](r), (TSixth)otherDeserializers[4](r), (TSeventh)otherDeserializers[5](r)), _ => throw new NotSupportedException(), }; private static Func GenerateMapper(int length, Func deserializer, Func[] otherDeserializers, Func map) { return r => { var objects = new object[length]; objects[0] = deserializer(r); for (var i = 1; i < length; ++i) { objects[i] = otherDeserializers[i - 1](r); } return map(objects); }; } private static Func[] GenerateDeserializers(Identity identity, string splitOn, DbDataReader reader) { var deserializers = new List>(); var splits = splitOn.Split(',').Select(s => s.Trim()).ToArray(); bool isMultiSplit = splits.Length > 1; int typeCount = identity.TypeCount; if (identity.GetType(0) == typeof(object)) { // we go left to right for dynamic multi-mapping so that the madness of TestMultiMappingVariations // is supported bool first = true; int currentPos = 0; int splitIdx = 0; string currentSplit = splits[splitIdx]; for (int i = 0; i < typeCount; i++) { Type type = identity.GetType(i); if (type == typeof(DontMap)) { break; } int splitPoint = GetNextSplitDynamic(currentPos, currentSplit, reader); if (isMultiSplit && splitIdx < splits.Length - 1) { currentSplit = splits[++splitIdx]; } deserializers.Add(GetDeserializer(type, reader, currentPos, splitPoint - currentPos, !first)); currentPos = splitPoint; first = false; } } else { // in this we go right to left through the data reader in order to cope with properties that are // named the same as a subsequent primary key that we split on int currentPos = reader.FieldCount; int splitIdx = splits.Length - 1; var currentSplit = splits[splitIdx]; for (var typeIdx = typeCount - 1; typeIdx >= 0; --typeIdx) { var type = identity.GetType(typeIdx); if (type == typeof(DontMap)) { continue; } int splitPoint = 0; if (typeIdx > 0) { splitPoint = GetNextSplit(currentPos, currentSplit, reader); if (isMultiSplit && splitIdx > 0) { currentSplit = splits[--splitIdx]; } } deserializers.Add(GetDeserializer(type, reader, splitPoint, currentPos - splitPoint, typeIdx > 0)); currentPos = splitPoint; } deserializers.Reverse(); } return deserializers.ToArray(); } private static int GetNextSplitDynamic(int startIdx, string splitOn, DbDataReader reader) { if (startIdx == reader.FieldCount) { throw MultiMapException(reader, splitOn); } if (splitOn == "*") { return ++startIdx; } for (var i = startIdx + 1; i < reader.FieldCount; ++i) { if (string.Equals(splitOn, reader.GetName(i), StringComparison.OrdinalIgnoreCase)) { return i; } } return reader.FieldCount; } private static int GetNextSplit(int startIdx, string splitOn, DbDataReader reader) { if (splitOn == "*") { return --startIdx; } for (var i = startIdx - 1; i > 0; --i) { if (string.Equals(splitOn, reader.GetName(i), StringComparison.OrdinalIgnoreCase)) { return i; } } throw MultiMapException(reader, splitOn); } private static CacheInfo GetCacheInfo(Identity identity, object? exampleParameters, bool addToCache) { if (!TryGetQueryCache(identity, out CacheInfo? info)) { if (GetMultiExec(exampleParameters) is not null) { throw new InvalidOperationException("An enumerable sequence of parameters (arrays, lists, etc) is not allowed in this context"); } info = new CacheInfo(); if (identity.ParametersType is not null) { Action reader; if (exampleParameters is IDynamicParameters) { reader = (cmd, obj) => ((IDynamicParameters)obj!).AddParameters(cmd, identity); } else if (exampleParameters is IEnumerable>) { reader = (cmd, obj) => { IDynamicParameters mapped = new DynamicParameters(obj!); mapped.AddParameters(cmd, identity); }; } else { var literals = GetLiteralTokens(identity.Sql); reader = CreateParamInfoGenerator(identity, false, true, literals); } if ((identity.CommandType is null || identity.CommandType == CommandType.Text) && ShouldPassByPosition(identity.Sql)) { var tail = reader; reader = (cmd, obj) => { tail(cmd, obj); PassByPosition(cmd); }; } info.ParamReader = reader; } if (addToCache) SetQueryCache(identity, info); } return info; } private static bool ShouldPassByPosition(string sql) { return sql?.IndexOf('?') >= 0 && CompiledRegex.PseudoPositional.IsMatch(sql); } private static void PassByPosition(IDbCommand cmd) { if (cmd.Parameters.Count == 0) return; Dictionary parameters = new(StringComparer.Ordinal); foreach (IDbDataParameter param in cmd.Parameters) { if (!string.IsNullOrEmpty(param.ParameterName)) parameters[param.ParameterName] = param; } var consumed = new HashSet(StringComparer.Ordinal); bool firstMatch = true; int index = 0; // use this to spoof names; in most pseudo-positional cases, the name is ignored, however: // for "snowflake", the name needs to be incremental i.e. "1", "2", "3" cmd.CommandText = CompiledRegex.PseudoPositional.Replace(cmd.CommandText, match => { string key = match.Groups[1].Value; if (!consumed.Add(key)) { throw new InvalidOperationException("When passing parameters by position, each parameter can only be referenced once"); } else if (parameters.TryGetValue(key, out IDbDataParameter? param)) { if (firstMatch) { firstMatch = false; cmd.Parameters.Clear(); // only clear if we are pretty positive that we've found this pattern successfully } // if found, return the anonymous token "?" if (Settings.UseIncrementalPseudoPositionalParameterNames) { param.ParameterName = (++index).ToString(); } cmd.Parameters.Add(param); parameters.Remove(key); consumed.Add(key); return "?"; } else { // otherwise, leave alone for simple debugging return match.Value; } }); } static DbDataReader GetDbDataReader(IDataReader reader) { return reader as DbDataReader ?? new WrappedBasicReader(reader); } private static Func GetDeserializer(Type type, DbDataReader reader, int startBound, int length, bool returnNullIfFirstMissing) { // dynamic is passed in as Object ... by c# design if (type == typeof(object) || type == typeof(DapperRow)) { return GetDapperRowDeserializer(reader, startBound, length, returnNullIfFirstMissing); } Type? underlyingType = null; bool useGetFieldValue = false; if (typeMap.TryGetValue(type, out var mapEntry)) { useGetFieldValue = (mapEntry.Flags & TypeMapEntryFlags.UseGetFieldValue) != 0; } else if (!(type.IsEnum || type.IsArray || type.FullName == LinqBinary || (type.IsValueType && (underlyingType = Nullable.GetUnderlyingType(type)) is not null && underlyingType.IsEnum))) { if (typeHandlers.TryGetValue(type, out ITypeHandler? handler)) { return GetHandlerDeserializer(handler, type, startBound); } return GetTypeDeserializer(type, reader, startBound, length, returnNullIfFirstMissing); } return GetSimpleValueDeserializer(type, underlyingType ?? type, startBound, useGetFieldValue); } private static Func GetHandlerDeserializer(ITypeHandler handler, Type type, int startBound) { return reader => handler.Parse(type, reader.GetValue(startBound))!; } private static Exception MultiMapException(IDataRecord reader, string? splitOn = null) { bool hasFields = false; try { hasFields = reader is not null && reader.FieldCount != 0; } catch { /* don't throw when trying to throw */ } if (hasFields) { return new ArgumentException( string.IsNullOrEmpty(splitOn) ? "When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id" : $"Multi-map error: splitOn column '{splitOn}' was not found - please ensure your splitOn parameter is set and in the correct order", nameof(splitOn)); } else { return new InvalidOperationException("No columns were selected"); } } internal static Func GetDapperRowDeserializer(DbDataReader reader, int startBound, int length, bool returnNullIfFirstMissing) { var fieldCount = reader.FieldCount; if (length == -1) { length = fieldCount - startBound; } if (fieldCount <= startBound) { throw MultiMapException(reader); } var effectiveFieldCount = Math.Min(fieldCount - startBound, length); DapperTable? table = null; return r => { if (table is null) { string[] names = new string[effectiveFieldCount]; for (int i = 0; i < effectiveFieldCount; i++) { names[i] = r.GetName(i + startBound); } table = new DapperTable(names); } var values = new object[effectiveFieldCount]; if (returnNullIfFirstMissing) { values[0] = r.GetValue(startBound); if (values[0] is DBNull) { return null!; } } if (startBound == 0) { for (int i = 0; i < values.Length; i++) { object val = r.GetValue(i); values[i] = val is DBNull ? null! : val; } } else { var begin = returnNullIfFirstMissing ? 1 : 0; for (var iter = begin; iter < effectiveFieldCount; ++iter) { object obj = r.GetValue(iter + startBound); values[iter] = obj is DBNull ? null! : obj; } } return new DapperRow(table, values); }; } /// /// Internal use only. /// /// The object to convert to a character. [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete(ObsoleteInternalUsageOnly, false)] public static char ReadChar(object value) { if (value is null || value is DBNull) throw new ArgumentNullException(nameof(value)); if (value is string s && s.Length == 1) return s[0]; if (value is char c) return c; throw new ArgumentException("A single-character was expected", nameof(value)); } /// /// Internal use only. /// /// The object to convert to a character. [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete(ObsoleteInternalUsageOnly, false)] public static char? ReadNullableChar(object value) { if (value is null || value is DBNull) return null; if (value is string s && s.Length == 1) return s[0]; if (value is char c) return c; throw new ArgumentException("A single-character was expected", nameof(value)); } /// /// Internal use only. /// /// The parameter collection to search in. /// The command for this fetch. /// The name of the parameter to get. [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete(ObsoleteInternalUsageOnly, true)] public static IDbDataParameter FindOrAddParameter(IDataParameterCollection parameters, IDbCommand command, string name) { IDbDataParameter result; if (parameters.Contains(name)) { result = (IDbDataParameter)parameters[name]; } else { result = command.CreateParameter(); result.ParameterName = name; parameters.Add(result); } return result; } internal static int GetListPaddingExtraCount(int count) { switch (count) { case 0: case 1: case 2: case 3: case 4: case 5: return 0; // no padding } if (count < 0) return 0; int padFactor; if (count <= 150) padFactor = 10; else if (count <= 750) padFactor = 50; else if (count <= 2000) padFactor = 100; // note: max param count for SQL Server else if (count <= 2070) padFactor = 10; // try not to over-pad as we approach that limit else if (count <= 2100) return 0; // just don't pad between 2070 and 2100, to minimize the crazy else padFactor = 200; // above that, all bets are off! // if we have 17, factor = 10; 17 % 10 = 7, we need 3 more int intoBlock = count % padFactor; return intoBlock == 0 ? 0 : (padFactor - intoBlock); } private static string GetInListRegex(string name, bool byPosition) => byPosition ? (@"(\?)" + Regex.Escape(name) + @"\?(?!\w)(\s+(?i)unknown(?-i))?") : ("([?@:$]" + Regex.Escape(name) + @")(?!\w)(\s+(?i)unknown(?-i))?"); /// /// Internal use only. /// /// The command to pack parameters for. /// The name prefix for these parameters. /// The parameter value can be an [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete(ObsoleteInternalUsageOnly, false)] public static void PackListParameters(IDbCommand command, string namePrefix, object? value) { // initially we tried TVP, however it performs quite poorly. // keep in mind SQL support up to 2000 params easily in sp_executesql, needing more is rare if (FeatureSupport.Get(command.Connection).Arrays) { var arrayParm = command.CreateParameter(); arrayParm.Value = SanitizeParameterValue(value); arrayParm.ParameterName = namePrefix; command.Parameters.Add(arrayParm); } else { bool byPosition = ShouldPassByPosition(command.CommandText); var list = value as IEnumerable; var count = 0; bool isString = value is IEnumerable; bool isDbString = value is IEnumerable; DbType? dbType = null; int splitAt = SqlMapper.Settings.InListStringSplitCount; bool viaSplit = splitAt >= 0 && TryStringSplit(ref list, splitAt, namePrefix, command, byPosition); if (list is not null && !viaSplit) { object? lastValue = null; foreach (var item in list) { if (++count == 1) // first item: fetch some type info { if (item is null) { throw new NotSupportedException("The first item in a list-expansion cannot be null"); } if (!isDbString) { dbType = LookupDbType(item.GetType(), "", true, out var handler); } } var nextName = namePrefix + count.ToString(); if (isDbString && item is DbString str) { str.AddParameter(command, nextName); } else { var listParam = command.CreateParameter(); listParam.ParameterName = nextName; if (isString) { listParam.Size = DbString.DefaultLength; if (item is not null && ((string)item).Length > DbString.DefaultLength) { listParam.Size = -1; } } var tmp = listParam.Value = SanitizeParameterValue(item); if (tmp is not null && tmp is not DBNull) lastValue = tmp; // only interested in non-trivial values for padding if (DynamicParameters.ShouldSetDbType(dbType) && listParam.DbType != dbType.GetValueOrDefault()) { listParam.DbType = dbType.GetValueOrDefault(); } command.Parameters.Add(listParam); } } if (Settings.PadListExpansions && !isDbString && lastValue is not null) { int padCount = GetListPaddingExtraCount(count); for (int i = 0; i < padCount; i++) { count++; var padParam = command.CreateParameter(); padParam.ParameterName = namePrefix + count.ToString(); if (isString) padParam.Size = DbString.DefaultLength; if (DynamicParameters.ShouldSetDbType(dbType)) { padParam.DbType = dbType.GetValueOrDefault(); } padParam.Value = lastValue; command.Parameters.Add(padParam); } } } if (viaSplit) { // already done } else { var regexIncludingUnknown = GetInListRegex(namePrefix, byPosition); if (count == 0) { command.CommandText = Regex.Replace(command.CommandText, regexIncludingUnknown, match => { var variableName = match.Groups[1].Value; if (match.Groups[2].Success) { // looks like an optimize hint; leave it alone! return match.Value; } else { return "(SELECT " + variableName + " WHERE 1 = 0)"; } }, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant); var dummyParam = command.CreateParameter(); dummyParam.ParameterName = namePrefix; dummyParam.Value = DBNull.Value; command.Parameters.Add(dummyParam); } else { command.CommandText = Regex.Replace(command.CommandText, regexIncludingUnknown, match => { var variableName = match.Groups[1].Value; if (match.Groups[2].Success) { // looks like an optimize hint; expand it var suffix = match.Groups[2].Value; var sb = GetStringBuilder().Append(variableName).Append(1).Append(suffix); for (int i = 2; i <= count; i++) { sb.Append(',').Append(variableName).Append(i).Append(suffix); } return sb.ToStringRecycle(); } else { var sb = GetStringBuilder().Append('(').Append(variableName); if (!byPosition) sb.Append(1); else sb.Append(namePrefix).Append(1).Append(variableName); for (int i = 2; i <= count; i++) { sb.Append(',').Append(variableName); if (!byPosition) sb.Append(i); else sb.Append(namePrefix).Append(i).Append(variableName); } return sb.Append(')').ToStringRecycle(); } }, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant); } } } } private static bool TryStringSplit(ref IEnumerable? list, int splitAt, string namePrefix, IDbCommand command, bool byPosition) { if (list is null || splitAt < 0) return false; return list switch { IEnumerable l => TryStringSplit(ref l, splitAt, namePrefix, command, "int", byPosition, (sb, i) => sb.Append(i.ToString(CultureInfo.InvariantCulture))), IEnumerable l => TryStringSplit(ref l, splitAt, namePrefix, command, "bigint", byPosition, (sb, i) => sb.Append(i.ToString(CultureInfo.InvariantCulture))), IEnumerable l => TryStringSplit(ref l, splitAt, namePrefix, command, "smallint", byPosition, (sb, i) => sb.Append(i.ToString(CultureInfo.InvariantCulture))), IEnumerable l => TryStringSplit(ref l, splitAt, namePrefix, command, "tinyint", byPosition, (sb, i) => sb.Append(i.ToString(CultureInfo.InvariantCulture))), _ => false, }; } private static bool TryStringSplit(ref IEnumerable list, int splitAt, string namePrefix, IDbCommand command, string colType, bool byPosition, Action append) { if (list is not ICollection typed) { typed = list.ToList(); list = typed; // because we still need to be able to iterate it, even if we fail here } if (typed.Count < splitAt) return false; string? varName = null; var regexIncludingUnknown = GetInListRegex(namePrefix, byPosition); var sql = Regex.Replace(command.CommandText, regexIncludingUnknown, match => { var variableName = match.Groups[1].Value; if (match.Groups[2].Success) { // looks like an optimize hint; leave it alone! return match.Value; } else { varName = variableName; return "(select cast([value] as " + colType + ") from string_split(" + variableName + ",','))"; } }, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant); if (varName is null) return false; // couldn't resolve the var! command.CommandText = sql; var concatenatedParam = command.CreateParameter(); concatenatedParam.ParameterName = namePrefix; concatenatedParam.DbType = DbType.AnsiString; concatenatedParam.Size = -1; string val; using (var iter = typed.GetEnumerator()) { if (iter.MoveNext()) { var sb = GetStringBuilder(); append(sb, iter.Current); while (iter.MoveNext()) { append(sb.Append(','), iter.Current); } val = sb.ToString(); } else { val = ""; } } concatenatedParam.Value = val; command.Parameters.Add(concatenatedParam); return true; } /// /// OBSOLETE: For internal usage only. Sanitizes the parameter value with proper type casting. /// /// The value to sanitize. [Obsolete(ObsoleteInternalUsageOnly, false)] public static object SanitizeParameterValue(object? value) { if (value is null) return DBNull.Value; if (value is Enum) { TypeCode typeCode = value is IConvertible convertible ? convertible.GetTypeCode() : Type.GetTypeCode(Enum.GetUnderlyingType(value.GetType())); switch (typeCode) { case TypeCode.Byte: return (byte)value; case TypeCode.SByte: return (sbyte)value; case TypeCode.Int16: return (short)value; case TypeCode.Int32: return (int)value; case TypeCode.Int64: return (long)value; case TypeCode.UInt16: return (ushort)value; case TypeCode.UInt32: return (uint)value; case TypeCode.UInt64: return (ulong)value; } } return value; } private static IEnumerable FilterParameters(IEnumerable parameters, string sql) { var list = new List(16); foreach (var p in parameters) { if (Regex.IsMatch(sql, @"[?@:$]" + p.Name + @"([^\p{L}\p{N}_]+|$)", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant)) list.Add(p); } return list; } /// /// Replace all literal tokens with their text form. /// /// The parameter lookup to do replacements with. /// The command to replace parameters in. public static void ReplaceLiterals(this IParameterLookup parameters, IDbCommand command) { var tokens = GetLiteralTokens(command.CommandText); if (tokens.Count != 0) ReplaceLiterals(parameters, command, tokens); } internal static readonly MethodInfo format = typeof(SqlMapper).GetMethod("Format", BindingFlags.Public | BindingFlags.Static)!; /// /// Convert numeric values to their string form for SQL literal purposes. /// /// The value to get a string for. [Obsolete(ObsoleteInternalUsageOnly)] public static string Format(object? value) { if (value is null) { return "null"; } else { switch (Type.GetTypeCode(value.GetType())) { case TypeCode.DBNull: return "null"; case TypeCode.Boolean: return ((bool)value) ? "1" : "0"; case TypeCode.Byte: return ((byte)value).ToString(CultureInfo.InvariantCulture); case TypeCode.SByte: return ((sbyte)value).ToString(CultureInfo.InvariantCulture); case TypeCode.UInt16: return ((ushort)value).ToString(CultureInfo.InvariantCulture); case TypeCode.Int16: return ((short)value).ToString(CultureInfo.InvariantCulture); case TypeCode.UInt32: return ((uint)value).ToString(CultureInfo.InvariantCulture); case TypeCode.Int32: return ((int)value).ToString(CultureInfo.InvariantCulture); case TypeCode.UInt64: return ((ulong)value).ToString(CultureInfo.InvariantCulture); case TypeCode.Int64: return ((long)value).ToString(CultureInfo.InvariantCulture); case TypeCode.Single: return ((float)value).ToString(CultureInfo.InvariantCulture); case TypeCode.Double: return ((double)value).ToString(CultureInfo.InvariantCulture); case TypeCode.Decimal: return ((decimal)value).ToString(CultureInfo.InvariantCulture); default: var multiExec = GetMultiExec(value); if (multiExec is not null) { StringBuilder? sb = null; bool first = true; foreach (object subval in multiExec) { if (first) { sb = GetStringBuilder().Append('('); first = false; } else { sb!.Append(','); } sb.Append(Format(subval)); } if (first) { return "(select null where 1=0)"; } else { return sb!.Append(')').ToStringRecycle(); } } throw new NotSupportedException($"The type '{value.GetType().Name}' is not supported for SQL literals."); } } } internal static void ReplaceLiterals(IParameterLookup parameters, IDbCommand command, IList tokens) { var sql = command.CommandText; foreach (var token in tokens) { object? value = parameters[token.Member]; #pragma warning disable 0618 string text = Format(value); #pragma warning restore 0618 sql = sql.Replace(token.Token, text); } command.CommandText = sql; } [SuppressMessage("Style", "IDE0220:Add explicit cast", Justification = "Regex matches are Match")] internal static IList GetLiteralTokens(string sql) { if (string.IsNullOrEmpty(sql)) return LiteralToken.None; if (!CompiledRegex.LiteralTokens.IsMatch(sql)) return LiteralToken.None; var matches = CompiledRegex.LiteralTokens.Matches(sql); var found = new HashSet(StringComparer.Ordinal); var list = new List(matches.Count); foreach (Match match in matches) { string token = match.Value; if (found.Add(match.Value)) { list.Add(new LiteralToken(token, match.Groups[1].Value)); } } return list.Count == 0 ? LiteralToken.None : list; } /// /// Internal use only. /// /// The identity of the generator. /// Whether to check for duplicates. /// Whether to remove unused parameters. public static Action CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused) => CreateParamInfoGenerator(identity, checkForDuplicates, removeUnused, GetLiteralTokens(identity.Sql)); private static bool IsValueTuple(Type? type) => (type?.IsValueType == true && type.FullName?.StartsWith("System.ValueTuple`", StringComparison.Ordinal) == true) || (type is not null && IsValueTuple(Nullable.GetUnderlyingType(type))); internal static Action CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused, IList literals) { Type type = identity.ParametersType!; if (IsValueTuple(type)) { throw new NotSupportedException("ValueTuple should not be used for parameters - the language-level names are not available to use as parameter names, and it adds unnecessary boxing"); } bool filterParams = removeUnused && identity.CommandType.GetValueOrDefault(CommandType.Text) == CommandType.Text; if (filterParams && Settings.SupportLegacyParameterTokens) { filterParams = !CompiledRegex.LegacyParameter.IsMatch(identity.Sql); } var dm = new DynamicMethod("ParamInfo" + Guid.NewGuid().ToString(), null, [typeof(IDbCommand), typeof(object)], type, true); var il = dm.GetILGenerator(); bool isStruct = type.IsValueType; var _sizeLocal = (LocalBuilder?)null; LocalBuilder GetSizeLocal() => _sizeLocal ??= il.DeclareLocal(typeof(int)); il.Emit(OpCodes.Ldarg_1); // stack is now [untyped-param] LocalBuilder typedParameterLocal; if (isStruct) { typedParameterLocal = il.DeclareLocal(type.MakeByRefType()); // note: ref-local il.Emit(OpCodes.Unbox, type); // stack is now [typed-param] } else { typedParameterLocal = il.DeclareLocal(type); il.Emit(OpCodes.Castclass, type); // stack is now [typed-param] } il.Emit(OpCodes.Stloc, typedParameterLocal); // stack is now empty il.Emit(OpCodes.Ldarg_0); // stack is now [command] il.EmitCall(OpCodes.Callvirt, typeof(IDbCommand).GetProperty(nameof(IDbCommand.Parameters))!.GetGetMethod()!, null); // stack is now [parameters] var allTypeProps = type.GetProperties(); var propsList = new List(allTypeProps.Length); for (int i = 0; i < allTypeProps.Length; ++i) { var p = allTypeProps[i]; if (p.GetIndexParameters().Length == 0) propsList.Add(p); } var ctors = type.GetConstructors(); ParameterInfo[] ctorParams; IEnumerable? props = null; // try to detect tuple patterns, e.g. anon-types, and use that to choose the order // otherwise: alphabetical if (ctors.Length == 1 && propsList.Count == (ctorParams = ctors[0].GetParameters()).Length) { // check if reflection was kind enough to put everything in the right order for us bool ok = true; for (int i = 0; i < propsList.Count; i++) { if (!string.Equals(propsList[i].Name, ctorParams[i].Name, StringComparison.OrdinalIgnoreCase)) { ok = false; break; } } if (ok) { // pre-sorted; the reflection gods have smiled upon us props = propsList; } else { // might still all be accounted for; check the hard way var positionByName = new Dictionary(ctorParams.Length, StringComparer.OrdinalIgnoreCase); foreach (var param in ctorParams) { positionByName[param.Name!] = param.Position; } if (positionByName.Count == propsList.Count) { int[] positions = new int[propsList.Count]; ok = true; for (int i = 0; i < propsList.Count; i++) { if (!positionByName.TryGetValue(propsList[i].Name, out int pos)) { ok = false; break; } positions[i] = pos; } if (ok) { props = propsList.ToArray(); Array.Sort(positions, (PropertyInfo[])props); } } } } if (props is null) { propsList.Sort(new PropertyInfoByNameComparer()); props = propsList; } if (filterParams) { props = FilterParameters(props, identity.Sql); } var callOpCode = isStruct ? OpCodes.Call : OpCodes.Callvirt; foreach (var prop in props) { if (typeof(ICustomQueryParameter).IsAssignableFrom(prop.PropertyType)) { il.Emit(OpCodes.Ldloc, typedParameterLocal); // stack is now [parameters] [typed-param] il.Emit(callOpCode, prop.GetGetMethod()!); // stack is [parameters] [custom] if (!prop.PropertyType.IsValueType) { // throw if null var notNull = il.DefineLabel(); il.Emit(OpCodes.Dup); // stack is [parameters] [custom] [custom] il.Emit(OpCodes.Brtrue_S, notNull); // stack is [parameters] [custom] il.Emit(OpCodes.Ldstr, prop.Name); // stack is [parameters] [custom] [name] il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod(nameof(ThrowNullCustomQueryParameter))!, null); // stack is [parameters] [custom] il.MarkLabel(notNull); } il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [custom] [command] il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [custom] [command] [name] il.EmitCall(OpCodes.Callvirt, prop.PropertyType.GetMethod(nameof(ICustomQueryParameter.AddParameter))!, null); // stack is now [parameters] continue; } #pragma warning disable 618 DbType? dbType = LookupDbType(prop.PropertyType, prop.Name, true, out ITypeHandler? handler); #pragma warning restore 618 if (dbType == DynamicParameters.EnumerableMultiParameter) { // this actually represents special handling for list types; il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [command] il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [command] [name] il.Emit(OpCodes.Ldloc, typedParameterLocal); // stack is now [parameters] [command] [name] [typed-param] il.Emit(callOpCode, prop.GetGetMethod()!); // stack is [parameters] [command] [name] [typed-value] if (prop.PropertyType.IsValueType) { il.Emit(OpCodes.Box, prop.PropertyType); // stack is [parameters] [command] [name] [boxed-value] } il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod(nameof(SqlMapper.PackListParameters))!, null); // stack is [parameters] continue; } il.Emit(OpCodes.Dup); // stack is now [parameters] [parameters] il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [parameters] [command] if (checkForDuplicates) { // need to be a little careful about adding; use a utility method il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [parameters] [command] [name] il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod(nameof(SqlMapper.FindOrAddParameter))!, null); // stack is [parameters] [parameter] } else { // no risk of duplicates; just blindly add il.EmitCall(OpCodes.Callvirt, typeof(IDbCommand).GetMethod(nameof(IDbCommand.CreateParameter))!, null);// stack is now [parameters] [parameters] [parameter] il.Emit(OpCodes.Dup);// stack is now [parameters] [parameters] [parameter] [parameter] il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [parameters] [parameter] [parameter] [name] il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty(nameof(IDataParameter.ParameterName))!.GetSetMethod()!, null);// stack is now [parameters] [parameters] [parameter] } if (DynamicParameters.ShouldSetDbType(dbType) && dbType != DbType.Time && handler is null) // https://connect.microsoft.com/VisualStudio/feedback/details/381934/sqlparameter-dbtype-dbtype-time-sets-the-parameter-to-sqldbtype-datetime-instead-of-sqldbtype-time { il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter] if (dbType.GetValueOrDefault() == DbType.Object && prop.PropertyType == typeof(object)) // includes dynamic { // look it up from the param value il.Emit(OpCodes.Ldloc, typedParameterLocal); // stack is now [parameters] [[parameters]] [parameter] [parameter] [typed-param] il.Emit(callOpCode, prop.GetGetMethod()!); // stack is [parameters] [[parameters]] [parameter] [parameter] [object-value] il.Emit(OpCodes.Call, typeof(SqlMapper).GetMethod(nameof(SqlMapper.SetDbType), BindingFlags.Static | BindingFlags.Public)!); // stack is now [parameters] [[parameters]] [parameter] } else { // constant value; nice and simple EmitInt32(il, (int)dbType.GetValueOrDefault());// stack is now [parameters] [[parameters]] [parameter] [parameter] [db-type] il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty(nameof(IDataParameter.DbType))!.GetSetMethod()!, null);// stack is now [parameters] [[parameters]] [parameter] } } il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter] EmitInt32(il, (int)ParameterDirection.Input);// stack is now [parameters] [[parameters]] [parameter] [parameter] [dir] il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty(nameof(IDataParameter.Direction))!.GetSetMethod()!, null);// stack is now [parameters] [[parameters]] [parameter] il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter] il.Emit(OpCodes.Ldloc, typedParameterLocal); // stack is now [parameters] [[parameters]] [parameter] [parameter] [typed-param] il.Emit(callOpCode, prop.GetGetMethod()!); // stack is [parameters] [[parameters]] [parameter] [parameter] [typed-value] bool checkForNull; if (prop.PropertyType.IsValueType) { var propType = prop.PropertyType; var nullType = Nullable.GetUnderlyingType(propType); bool callSanitize = false; if ((nullType ?? propType).IsEnum) { if (nullType is not null) { // Nullable; we want to box as the underlying type; that's just *hard*; for // simplicity, box as Nullable and call SanitizeParameterValue callSanitize = checkForNull = true; } else { checkForNull = false; // non-nullable enum; we can do that! just box to the wrong type! (no, really) switch (Type.GetTypeCode(Enum.GetUnderlyingType(propType))) { case TypeCode.Byte: propType = typeof(byte); break; case TypeCode.SByte: propType = typeof(sbyte); break; case TypeCode.Int16: propType = typeof(short); break; case TypeCode.Int32: propType = typeof(int); break; case TypeCode.Int64: propType = typeof(long); break; case TypeCode.UInt16: propType = typeof(ushort); break; case TypeCode.UInt32: propType = typeof(uint); break; case TypeCode.UInt64: propType = typeof(ulong); break; } } } else { checkForNull = nullType is not null; } il.Emit(OpCodes.Box, propType); // stack is [parameters] [[parameters]] [parameter] [parameter] [boxed-value] if (callSanitize) { checkForNull = false; // handled by sanitize il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod(nameof(SanitizeParameterValue))!, null); // stack is [parameters] [[parameters]] [parameter] [parameter] [boxed-value] } } else { checkForNull = true; // if not a value-type, need to check } if (checkForNull) { // relative stack: [boxed value] il.Emit(OpCodes.Dup);// relative stack: [boxed value] [boxed value] Label notNull = il.DefineLabel(); Label? allDone = (dbType == DbType.String || dbType == DbType.AnsiString) ? il.DefineLabel() : (Label?)null; il.Emit(OpCodes.Brtrue_S, notNull); // relative stack [boxed value = null] il.Emit(OpCodes.Pop); // relative stack empty il.Emit(OpCodes.Ldsfld, typeof(DBNull).GetField(nameof(DBNull.Value))!); // relative stack [DBNull] if (dbType == DbType.String || dbType == DbType.AnsiString) { EmitInt32(il, 0); il.Emit(OpCodes.Stloc, GetSizeLocal()); } if (allDone is not null) il.Emit(OpCodes.Br_S, allDone.Value); il.MarkLabel(notNull); if (prop.PropertyType == typeof(string)) { il.Emit(OpCodes.Dup); // [string] [string] il.EmitCall(OpCodes.Callvirt, typeof(string).GetProperty(nameof(string.Length))!.GetGetMethod()!, null); // [string] [length] EmitInt32(il, DbString.DefaultLength); // [string] [length] [4000] il.Emit(OpCodes.Cgt); // [string] [0 or 1] Label isLong = il.DefineLabel(), lenDone = il.DefineLabel(); il.Emit(OpCodes.Brtrue_S, isLong); EmitInt32(il, DbString.DefaultLength); // [string] [4000] il.Emit(OpCodes.Br_S, lenDone); il.MarkLabel(isLong); EmitInt32(il, -1); // [string] [-1] il.MarkLabel(lenDone); il.Emit(OpCodes.Stloc, GetSizeLocal()); // [string] } if (prop.PropertyType.FullName == LinqBinary) { il.EmitCall(OpCodes.Callvirt, prop.PropertyType.GetMethod("ToArray", BindingFlags.Public | BindingFlags.Instance)!, null); } if (allDone is not null) il.MarkLabel(allDone.Value); // relative stack [boxed value or DBNull] } if (handler is not null) { #pragma warning disable 618 il.Emit(OpCodes.Call, typeof(TypeHandlerCache<>).MakeGenericType(prop.PropertyType).GetMethod(nameof(TypeHandlerCache.SetValue))!); // stack is now [parameters] [[parameters]] [parameter] #pragma warning restore 618 } else { il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty(nameof(IDataParameter.Value))!.GetSetMethod()!, null);// stack is now [parameters] [[parameters]] [parameter] } if (prop.PropertyType == typeof(string)) { var endOfSize = il.DefineLabel(); var sizeLocal = GetSizeLocal(); // don't set if 0 il.Emit(OpCodes.Ldloc, sizeLocal); // [parameters] [[parameters]] [parameter] [size] il.Emit(OpCodes.Brfalse_S, endOfSize); // [parameters] [[parameters]] [parameter] il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter] il.Emit(OpCodes.Ldloc, sizeLocal); // stack is now [parameters] [[parameters]] [parameter] [parameter] [size] il.EmitCall(OpCodes.Callvirt, typeof(IDbDataParameter).GetProperty(nameof(IDbDataParameter.Size))!.GetSetMethod()!, null); // stack is now [parameters] [[parameters]] [parameter] il.MarkLabel(endOfSize); } if (checkForDuplicates) { // stack is now [parameters] [parameter] il.Emit(OpCodes.Pop); // don't need parameter any more } else { // stack is now [parameters] [parameters] [parameter] // blindly add il.EmitCall(OpCodes.Callvirt, typeof(IList).GetMethod(nameof(IList.Add))!, null); // stack is now [parameters] il.Emit(OpCodes.Pop); // IList.Add returns the new index (int); we don't care } } // stack is currently [parameters] il.Emit(OpCodes.Pop); // stack is now empty if (literals.Count != 0 && propsList is not null) { il.Emit(OpCodes.Ldarg_0); // command il.Emit(OpCodes.Ldarg_0); // command, command var cmdText = typeof(IDbCommand).GetProperty(nameof(IDbCommand.CommandText))!; il.EmitCall(OpCodes.Callvirt, cmdText.GetGetMethod()!, null); // command, sql Dictionary? locals = null; LocalBuilder? local = null; foreach (var literal in literals) { // find the best member, preferring case-sensitive PropertyInfo? exact = null, fallback = null; string huntName = literal.Member; for (int i = 0; i < propsList.Count; i++) { string thisName = propsList[i].Name; if (string.Equals(thisName, huntName, StringComparison.OrdinalIgnoreCase)) { fallback = propsList[i]; if (string.Equals(thisName, huntName, StringComparison.Ordinal)) { exact = fallback; break; } } } var prop = exact ?? fallback; if (prop is not null) { il.Emit(OpCodes.Ldstr, literal.Token); il.Emit(OpCodes.Ldloc, typedParameterLocal); // command, sql, typed parameter il.EmitCall(callOpCode, prop.GetGetMethod()!, null); // command, sql, typed value Type propType = prop.PropertyType; var typeCode = Type.GetTypeCode(propType); switch (typeCode) { case TypeCode.Boolean: Label ifTrue = il.DefineLabel(), allDone = il.DefineLabel(); il.Emit(OpCodes.Brtrue_S, ifTrue); il.Emit(OpCodes.Ldstr, "0"); il.Emit(OpCodes.Br_S, allDone); il.MarkLabel(ifTrue); il.Emit(OpCodes.Ldstr, "1"); il.MarkLabel(allDone); break; case TypeCode.Byte: case TypeCode.SByte: case TypeCode.UInt16: case TypeCode.Int16: case TypeCode.UInt32: case TypeCode.Int32: case TypeCode.UInt64: case TypeCode.Int64: case TypeCode.Single: case TypeCode.Double: case TypeCode.Decimal: // need to stloc, ldloca, call // re-use existing locals (both the last known, and via a dictionary) var convert = GetToString(typeCode)!; if (local is null || local.LocalType != propType) { if (locals is null) { locals = []; local = null; } else { if (!locals.TryGetValue(propType, out local)) local = null; } if (local is null) { local = il.DeclareLocal(propType); locals.Add(propType, local); } } il.Emit(OpCodes.Stloc, local); // command, sql il.Emit(OpCodes.Ldloca, local); // command, sql, ref-to-value il.EmitCall(OpCodes.Call, InvariantCulture, null); // command, sql, ref-to-value, culture il.EmitCall(OpCodes.Call, convert, null); // command, sql, string value break; default: if (propType.IsValueType) il.Emit(OpCodes.Box, propType); // command, sql, object value il.EmitCall(OpCodes.Call, format, null); // command, sql, string value break; } il.EmitCall(OpCodes.Callvirt, StringReplace, null); } } il.EmitCall(OpCodes.Callvirt, cmdText.GetSetMethod()!, null); // empty } il.Emit(OpCodes.Ret); return (Action)dm.CreateDelegate(typeof(Action)); } private static readonly Dictionary toStrings = new[] { typeof(bool), typeof(sbyte), typeof(byte), typeof(ushort), typeof(short), typeof(uint), typeof(int), typeof(ulong), typeof(long), typeof(float), typeof(double), typeof(decimal) }.ToDictionary(x => Type.GetTypeCode(x), x => x.GetPublicInstanceMethod(nameof(object.ToString), [typeof(IFormatProvider)])!); private static MethodInfo? GetToString(TypeCode typeCode) { return toStrings.TryGetValue(typeCode, out MethodInfo? method) ? method : null; } private static readonly MethodInfo StringReplace = typeof(string).GetPublicInstanceMethod(nameof(string.Replace), [typeof(string), typeof(string)])!, InvariantCulture = typeof(CultureInfo).GetProperty(nameof(CultureInfo.InvariantCulture), BindingFlags.Public | BindingFlags.Static)!.GetGetMethod()!; private static int ExecuteCommand(IDbConnection cnn, ref CommandDefinition command, Action? paramReader) { IDbCommand? cmd = null; bool wasClosed = cnn.State == ConnectionState.Closed; try { cmd = command.SetupCommand(cnn, paramReader); if (wasClosed) cnn.Open(); int result = cmd.ExecuteNonQuery(); command.OnCompleted(); return result; } finally { if (wasClosed) cnn.Close(); cmd?.Parameters.Clear(); cmd?.Dispose(); } } private static T? ExecuteScalarImpl(IDbConnection cnn, ref CommandDefinition command) { Action? paramReader = null; object? param = command.Parameters; if (param is not null) { var identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, null, param.GetType()); paramReader = GetCacheInfo(identity, command.Parameters, command.AddToCache).ParamReader; } IDbCommand? cmd = null; bool wasClosed = cnn.State == ConnectionState.Closed; object? result; try { cmd = command.SetupCommand(cnn, paramReader); if (wasClosed) cnn.Open(); result = cmd.ExecuteScalar(); command.OnCompleted(); } finally { if (wasClosed) cnn.Close(); cmd?.Parameters.Clear(); cmd?.Dispose(); } return Parse(result); } private static DbDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefinition command, CommandBehavior commandBehavior, out IDbCommand? cmd) { Action? paramReader = GetParameterReader(cnn, ref command); cmd = null; bool wasClosed = cnn.State == ConnectionState.Closed, disposeCommand = true; try { cmd = command.SetupCommand(cnn, paramReader); if (wasClosed) cnn.Open(); var reader = ExecuteReaderWithFlagsFallback(cmd, wasClosed, commandBehavior); wasClosed = false; // don't dispose before giving it to them! disposeCommand = false; // note: command.FireOutputCallbacks(); would be useless here; parameters come at the **end** of the TDS stream return reader; } finally { if (wasClosed) cnn.Close(); if (cmd is not null && disposeCommand) { cmd.Parameters.Clear(); cmd.Dispose(); } } } private static Action? GetParameterReader(IDbConnection cnn, ref CommandDefinition command) { object? param = command.Parameters; IEnumerable? multiExec = GetMultiExec(param); CacheInfo? info = null; if (multiExec is not null) { throw new NotSupportedException("MultiExec is not supported by ExecuteReader"); } // nice and simple if (param is not null) { var identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, null, param.GetType()); info = GetCacheInfo(identity, param, command.AddToCache); } var paramReader = info?.ParamReader; return paramReader; } private static Func GetSimpleValueDeserializer(Type type, Type effectiveType, int index, bool useGetFieldValue) { // no point using special per-type handling here; it boils down to the same, plus not all are supported anyway (see: SqlDataReader.GetChar - not supported!) #pragma warning disable 618 if (type == typeof(char)) { // this *does* need special handling, though return r => ReadChar(r.GetValue(index)); } if (type == typeof(char?)) { return r => ReadNullableChar(r.GetValue(index))!; } if (type.FullName == LinqBinary) { return r => Activator.CreateInstance(type, r.GetValue(index))!; } #pragma warning restore 618 if (effectiveType.IsEnum) { // assume the value is returned as the correct type (int/byte/etc), but box back to the typed enum return r => { var val = r.GetValue(index); if (val is float || val is double || val is decimal) { val = Convert.ChangeType(val, Enum.GetUnderlyingType(effectiveType), CultureInfo.InvariantCulture); } return val is DBNull ? null! : Enum.ToObject(effectiveType, val); }; } if (typeHandlers.TryGetValue(type, out var handler)) { return r => { var val = r.GetValue(index); return val is DBNull ? null! : handler.Parse(type, val)!; }; } return useGetFieldValue ? ReadViaGetFieldValueFactory(type, index) : ReadViaGetValue(index); static Func ReadViaGetValue(int index) => reader => { var val = reader.GetValue(index); return val is DBNull ? null! : val; }; } static Func ReadViaGetFieldValueFactory(Type type, int index) { type = Nullable.GetUnderlyingType(type) ?? type; var factory = (Func>?)s_ReadViaGetFieldValueCache[type]; if (factory is null) { factory = (Func>)Delegate.CreateDelegate( typeof(Func>), null, typeof(SqlMapper).GetMethod( nameof(UnderlyingReadViaGetFieldValueFactory), BindingFlags.Static | BindingFlags.NonPublic)! .MakeGenericMethod(type)); lock (s_ReadViaGetFieldValueCache) { s_ReadViaGetFieldValueCache[type] = factory; } } return factory(index); } // cache of ReadViaGetFieldValueFactory for per-value T static readonly Hashtable s_ReadViaGetFieldValueCache = []; static Func UnderlyingReadViaGetFieldValueFactory(int index) => reader => reader.IsDBNull(index) ? null! : reader.GetFieldValue(index)!; static bool UseGetFieldValue(Type type) => typeMap.TryGetValue(type, out var mapEntry) && (mapEntry.Flags & TypeMapEntryFlags.UseGetFieldValue) != 0; private static T Parse(object? value) { if (value is null || value is DBNull) return default!; if (value is T t) return t; var type = typeof(T); type = Nullable.GetUnderlyingType(type) ?? type; if (type.IsEnum) { if (value is float || value is double || value is decimal) { value = Convert.ChangeType(value, Enum.GetUnderlyingType(type), CultureInfo.InvariantCulture); } return (T)Enum.ToObject(type, value); } if (typeHandlers.TryGetValue(type, out ITypeHandler? handler)) { return (T)handler.Parse(type, value)!; } return (T)Convert.ChangeType(value, type, CultureInfo.InvariantCulture); } private static readonly MethodInfo enumParse = typeof(Enum).GetMethod(nameof(Enum.Parse), [typeof(Type), typeof(string), typeof(bool)])!, getItem = typeof(DbDataReader).GetProperties(BindingFlags.Instance | BindingFlags.Public) .Where(p => p.GetIndexParameters().Length > 0 && p.GetIndexParameters()[0].ParameterType == typeof(int)) .Select(p => p.GetGetMethod()).First()!, getFieldValueT = typeof(DbDataReader).GetMethod(nameof(DbDataReader.GetFieldValue), BindingFlags.Instance | BindingFlags.Public, null, [typeof(int)], null)!, isDbNull = typeof(DbDataReader).GetMethod(nameof(DbDataReader.IsDBNull), BindingFlags.Instance | BindingFlags.Public, null, [typeof(int)], null)!; /// /// Gets type-map for the given type /// /// Type map instance, default is to create new instance of DefaultTypeMap #pragma warning disable CA2211 // Non-constant fields should not be visible - I agree with you, but we can't do that until we break the API public static Func TypeMapProvider = (Type type) => new DefaultTypeMap(type); #pragma warning restore CA2211 // Non-constant fields should not be visible /// /// Gets type-map for the given . /// /// The type to get a map for. /// Type map implementation, DefaultTypeMap instance if no override present public static ITypeMap GetTypeMap(Type type) { if (type is null) throw new ArgumentNullException(nameof(type)); var map = (ITypeMap?)_typeMaps[type]; if (map is null) { lock (_typeMaps) { // double-checked; store this to avoid reflection next time we see this type // since multiple queries commonly use the same domain-entity/DTO/view-model type map = (ITypeMap?)_typeMaps[type]; if (map is null) { map = TypeMapProvider(type); _typeMaps[type] = map; } } } return map; } // use Hashtable to get free lockless reading private static readonly Hashtable _typeMaps = []; /// /// Set custom mapping for type deserializers /// /// Entity type to override /// Mapping rules implementation, null to remove custom map public static void SetTypeMap(Type type, ITypeMap? map) { if (type is null) throw new ArgumentNullException(nameof(type)); if (map is null || map is DefaultTypeMap) { lock (_typeMaps) { _typeMaps.Remove(type); } } else { lock (_typeMaps) { _typeMaps[type] = map; } } PurgeQueryCacheByType(type); } /// /// Internal use only /// /// /// /// /// /// /// #if DEBUG // make sure we're not using this internally [Obsolete(nameof(DbDataReader) + " API should be preferred")] #endif [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Func GetTypeDeserializer( Type type, IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false ) { return WrapObjectReader(GetTypeDeserializer(type, GetDbDataReader(reader), startBound, length, returnNullIfFirstMissing)); } private static Func WrapObjectReader(Func dbReader) => reader => dbReader(GetDbDataReader(reader)); // we'll eat the extra layer here; this is not a core API /// /// Internal use only /// /// /// /// /// /// public static Func GetTypeDeserializer( Type type, DbDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false ) { return TypeDeserializerCache.GetReader(type, reader, startBound, length, returnNullIfFirstMissing); } private static LocalBuilder GetTempLocal(ILGenerator il, ref Dictionary? locals, Type type, bool initAndLoad) { if (type is null) throw new ArgumentNullException(nameof(type)); locals ??= []; if (!locals.TryGetValue(type, out LocalBuilder? found)) { found = il.DeclareLocal(type); locals.Add(type, found); } if (initAndLoad) { il.Emit(OpCodes.Ldloca, found); il.Emit(OpCodes.Initobj, type); il.Emit(OpCodes.Ldloca, found); il.Emit(OpCodes.Ldobj, type); } return found; } private static Func GetTypeDeserializerImpl( Type type, DbDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false ) { if (length == -1) { length = reader.FieldCount - startBound; } if (reader.FieldCount <= startBound) { throw MultiMapException(reader); } var returnType = type.IsValueType ? typeof(object) : type; var dm = new DynamicMethod("Deserialize" + Guid.NewGuid().ToString(), returnType, [typeof(DbDataReader)], type, true); var il = dm.GetILGenerator(); if (IsValueTuple(type)) { GenerateValueTupleDeserializer(type, reader, startBound, length, il); } else { GenerateDeserializerFromMap(type, reader, startBound, length, returnNullIfFirstMissing, il); } var funcType = System.Linq.Expressions.Expression.GetFuncType(typeof(DbDataReader), returnType); return (Func)dm.CreateDelegate(funcType); } private static void GenerateValueTupleDeserializer(Type valueTupleType, DbDataReader reader, int startBound, int length, ILGenerator il) { var nullableUnderlyingType = Nullable.GetUnderlyingType(valueTupleType); var currentValueTupleType = nullableUnderlyingType ?? valueTupleType; var constructors = new List(); var languageTupleElementTypes = new List(); while (true) { var arity = int.Parse(currentValueTupleType.Name.Substring("ValueTuple`".Length), CultureInfo.InvariantCulture); var constructorParameterTypes = new Type[arity]; var restField = (FieldInfo?)null; foreach (var field in currentValueTupleType.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)) { if (field.Name == "Rest") { restField = field; } else if (field.Name.StartsWith("Item", StringComparison.Ordinal)) { var elementNumber = int.Parse(field.Name.Substring("Item".Length), CultureInfo.InvariantCulture); constructorParameterTypes[elementNumber - 1] = field.FieldType; } } var itemFieldCount = constructorParameterTypes.Length; if (restField is not null) itemFieldCount--; for (var i = 0; i < itemFieldCount; i++) { languageTupleElementTypes.Add(constructorParameterTypes[i]); } if (restField is not null) { constructorParameterTypes[constructorParameterTypes.Length - 1] = restField.FieldType; } constructors.Add(currentValueTupleType.GetConstructor(constructorParameterTypes)!); if (restField is null) break; currentValueTupleType = restField.FieldType; if (!IsValueTuple(currentValueTupleType)) { throw new InvalidOperationException("The Rest field of a ValueTuple must contain a nested ValueTuple of arity 1 or greater."); } } var stringEnumLocal = (LocalBuilder?)null; for (var i = 0; i < languageTupleElementTypes.Count; i++) { var targetType = languageTupleElementTypes[i]; if (i < length) { LoadReaderValueOrBranchToDBNullLabel( il, startBound + i, ref stringEnumLocal, valueCopyLocal: null, reader.GetFieldType(startBound + i), targetType, out var isDbNullLabel, out bool popWhenNull); var finishLabel = il.DefineLabel(); il.Emit(OpCodes.Br_S, finishLabel); il.MarkLabel(isDbNullLabel); if (popWhenNull) { il.Emit(OpCodes.Pop); } LoadDefaultValue(il, targetType); il.MarkLabel(finishLabel); } else { LoadDefaultValue(il, targetType); } } for (var i = constructors.Count - 1; i >= 0; i--) { il.Emit(OpCodes.Newobj, constructors[i]); } if (nullableUnderlyingType is not null) { var nullableTupleConstructor = valueTupleType.GetConstructor([nullableUnderlyingType]); il.Emit(OpCodes.Newobj, nullableTupleConstructor!); } il.Emit(OpCodes.Box, valueTupleType); il.Emit(OpCodes.Ret); } private static void GenerateDeserializerFromMap(Type type, DbDataReader reader, int startBound, int length, bool returnNullIfFirstMissing, ILGenerator il) { var currentIndexDiagnosticLocal = il.DeclareLocal(typeof(int)); var returnValueLocal = il.DeclareLocal(type); il.Emit(OpCodes.Ldc_I4_0); il.Emit(OpCodes.Stloc, currentIndexDiagnosticLocal); var names = Enumerable.Range(startBound, length).Select(i => reader.GetName(i)).ToArray(); ITypeMap typeMap = GetTypeMap(type); int index = startBound; ConstructorInfo? specializedConstructor = null; bool supportInitialize = false; Dictionary? structLocals = null; if (type.IsValueType) { il.Emit(OpCodes.Ldloca, returnValueLocal); il.Emit(OpCodes.Initobj, type); } else { var types = new Type[length]; for (int i = startBound; i < startBound + length; i++) { types[i - startBound] = reader.GetFieldType(i); } var explicitConstr = typeMap.FindExplicitConstructor(); if (explicitConstr is not null) { var consPs = explicitConstr.GetParameters(); foreach (var p in consPs) { if (!p.ParameterType.IsValueType) { il.Emit(OpCodes.Ldnull); } else { GetTempLocal(il, ref structLocals, p.ParameterType, true); } } il.Emit(OpCodes.Newobj, explicitConstr); il.Emit(OpCodes.Stloc, returnValueLocal); supportInitialize = typeof(ISupportInitialize).IsAssignableFrom(type); if (supportInitialize) { il.Emit(OpCodes.Ldloc, returnValueLocal); il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod(nameof(ISupportInitialize.BeginInit))!, null); } } else { var ctor = typeMap.FindConstructor(names, types); if (ctor is null) { string proposedTypes = "(" + string.Join(", ", types.Select((t, i) => t.FullName + " " + names[i]).ToArray()) + ")"; throw new InvalidOperationException($"A parameterless default constructor or one matching signature {proposedTypes} is required for {type.FullName} materialization"); } if (ctor.GetParameters().Length == 0) { il.Emit(OpCodes.Newobj, ctor); il.Emit(OpCodes.Stloc, returnValueLocal); supportInitialize = typeof(ISupportInitialize).IsAssignableFrom(type); if (supportInitialize) { il.Emit(OpCodes.Ldloc, returnValueLocal); il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod(nameof(ISupportInitialize.BeginInit))!, null); } } else { specializedConstructor = ctor; } } } il.BeginExceptionBlock(); if (type.IsValueType) { il.Emit(OpCodes.Ldloca, returnValueLocal); // [target] } else if (specializedConstructor is null) { il.Emit(OpCodes.Ldloc, returnValueLocal); // [target] } var members = (specializedConstructor is not null ? names.Select(n => typeMap.GetConstructorParameter(specializedConstructor, n)) : names.Select(n => typeMap.GetMember(n))).ToList(); // stack is now [target] bool first = true; var allDone = il.DefineLabel(); var stringEnumLocal = (LocalBuilder?)null; var valueCopyDiagnosticLocal = il.DeclareLocal(typeof(object)); bool applyNullSetting = Settings.ApplyNullValues; foreach (var item in members) { if (item is not null) { if (specializedConstructor is null) il.Emit(OpCodes.Dup); // stack is now [target][target] Label finishLabel = il.DefineLabel(); Type memberType = item.MemberType; // Save off the current index for access if an exception is thrown EmitInt32(il, index); il.Emit(OpCodes.Stloc, currentIndexDiagnosticLocal); LoadReaderValueOrBranchToDBNullLabel(il, index, ref stringEnumLocal, valueCopyDiagnosticLocal, reader.GetFieldType(index), memberType, out var isDbNullLabel, out bool popWhenNull); if (specializedConstructor is null) { // Store the value in the property/field if (item.Property is not null) { il.Emit(type.IsValueType ? OpCodes.Call : OpCodes.Callvirt, DefaultTypeMap.GetPropertySetterOrThrow(item.Property, type)); } else { il.Emit(OpCodes.Stfld, item.Field!); // stack is now [target] } } il.Emit(OpCodes.Br_S, finishLabel); // stack is now [target] il.MarkLabel(isDbNullLabel); // incoming stack: [target][target][(and possibly value)] if (popWhenNull) il.Emit(OpCodes.Pop); // stack is now [target][target] if (specializedConstructor is not null) { LoadDefaultValue(il, item.MemberType); } else if (applyNullSetting && (!memberType.IsValueType || Nullable.GetUnderlyingType(memberType) is not null)) { // can load a null with this value if (memberType.IsValueType) { // must be Nullable for some T GetTempLocal(il, ref structLocals, memberType, true); // stack is now [target][target][null] } else { // regular reference-type il.Emit(OpCodes.Ldnull); // stack is now [target][target][null] } // Store the value in the property/field if (item.Property is not null) { il.Emit(type.IsValueType ? OpCodes.Call : OpCodes.Callvirt, DefaultTypeMap.GetPropertySetterOrThrow(item.Property, type)); // stack is now [target] } else { il.Emit(OpCodes.Stfld, item.Field!); // stack is now [target] } } else { il.Emit(OpCodes.Pop); // stack is now [target] } if (first && returnNullIfFirstMissing) { il.Emit(OpCodes.Pop); if (!type.IsValueType) // for struct, the retval is already initialized as default { il.Emit(OpCodes.Ldnull); // stack is now [null] il.Emit(OpCodes.Stloc, returnValueLocal); } il.Emit(OpCodes.Br, allDone); } il.MarkLabel(finishLabel); } first = false; index++; } if (type.IsValueType) { il.Emit(OpCodes.Pop); } else { if (specializedConstructor is not null) { il.Emit(OpCodes.Newobj, specializedConstructor); } il.Emit(OpCodes.Stloc, returnValueLocal); // stack is empty if (supportInitialize) { il.Emit(OpCodes.Ldloc, returnValueLocal); il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod(nameof(ISupportInitialize.EndInit))!, null); } } il.MarkLabel(allDone); il.BeginCatchBlock(typeof(Exception)); // stack is Exception il.Emit(OpCodes.Ldloc, currentIndexDiagnosticLocal); // stack is Exception, index il.Emit(OpCodes.Ldarg_0); // stack is Exception, index, reader il.Emit(OpCodes.Ldloc, valueCopyDiagnosticLocal); // stack is Exception, index, reader, value il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod(nameof(SqlMapper.ThrowDataException))!, null); il.EndExceptionBlock(); il.Emit(OpCodes.Ldloc, returnValueLocal); // stack is [rval] if (type.IsValueType) { il.Emit(OpCodes.Box, type); } il.Emit(OpCodes.Ret); } private static void LoadDefaultValue(ILGenerator il, Type type) { if (type.IsValueType) { var local = il.DeclareLocal(type); il.Emit(OpCodes.Ldloca, local); il.Emit(OpCodes.Initobj, type); il.Emit(OpCodes.Ldloc, local); } else { il.Emit(OpCodes.Ldnull); } } private static void LoadReaderValueViaGetFieldValue(ILGenerator il, int index, Type memberType, LocalBuilder? valueCopyLocal, Label isDbNullLabel, out bool popWhenNull) { popWhenNull = false; var underlyingType = Nullable.GetUnderlyingType(memberType) ?? memberType; // for consistency, always do a null check (the GetValue approach always tests for DbNull and jumps) il.Emit(OpCodes.Ldarg_0); // stack is now [...][reader] EmitInt32(il, index); // stack is now [...][reader][index] il.Emit(OpCodes.Callvirt, isDbNull); // stack is now [...][bool] il.Emit(OpCodes.Brtrue_S, isDbNullLabel); // DB reports not null; read the value il.Emit(OpCodes.Ldarg_0); // stack is now [...][reader] EmitInt32(il, index); // stack is now [...][reader][index] il.Emit(OpCodes.Callvirt, getFieldValueT.MakeGenericMethod(underlyingType)); // stack is now [...][T] if (valueCopyLocal is not null) { il.Emit(OpCodes.Dup); // stack is now [...][T][T] if (underlyingType.IsValueType) { il.Emit(OpCodes.Box, underlyingType); // stack is now [...][T][value-as-object] } il.Emit(OpCodes.Stloc, valueCopyLocal); // stack is now [...][T] } if (underlyingType != memberType) { // Nullable; wrap it il.Emit(OpCodes.Newobj, memberType.GetConstructor([underlyingType])!); // stack is now [...][T?] } } private static void LoadReaderValueOrBranchToDBNullLabel(ILGenerator il, int index, ref LocalBuilder? stringEnumLocal, LocalBuilder? valueCopyLocal, Type colType, Type memberType, out Label isDbNullLabel, out bool popWhenNull) { isDbNullLabel = il.DefineLabel(); if (UseGetFieldValue(memberType)) { LoadReaderValueViaGetFieldValue(il, index, memberType, valueCopyLocal, isDbNullLabel, out popWhenNull); return; } popWhenNull = true; il.Emit(OpCodes.Ldarg_0); // stack is now [...][reader] EmitInt32(il, index); // stack is now [...][reader][index] // default impl: use GetValue il.Emit(OpCodes.Callvirt, getItem); // stack is now [...][value-as-object] if (valueCopyLocal is not null) { il.Emit(OpCodes.Dup); // stack is now [...][value-as-object][value-as-object] il.Emit(OpCodes.Stloc, valueCopyLocal); // stack is now [...][value-as-object] } if (memberType == typeof(char) || memberType == typeof(char?)) { il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod( memberType == typeof(char) ? nameof(SqlMapper.ReadChar) : nameof(SqlMapper.ReadNullableChar), BindingFlags.Static | BindingFlags.Public)!, null); // stack is now [...][typed-value] } else { il.Emit(OpCodes.Dup); // stack is now [...][value-as-object][value-as-object] il.Emit(OpCodes.Isinst, typeof(DBNull)); // stack is now [...][value-as-object][DBNull or null] il.Emit(OpCodes.Brtrue_S, isDbNullLabel); // stack is now [...][value-as-object] // unbox nullable enums as the primitive, i.e. byte etc var nullUnderlyingType = Nullable.GetUnderlyingType(memberType); var unboxType = nullUnderlyingType?.IsEnum == true ? nullUnderlyingType : memberType; if (unboxType.IsEnum) { Type numericType = Enum.GetUnderlyingType(unboxType); if (colType == typeof(string)) { stringEnumLocal ??= il.DeclareLocal(typeof(string)); il.Emit(OpCodes.Castclass, typeof(string)); // stack is now [...][string] il.Emit(OpCodes.Stloc, stringEnumLocal); // stack is now [...] il.Emit(OpCodes.Ldtoken, unboxType); // stack is now [...][enum-type-token] il.EmitCall(OpCodes.Call, typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle))!, null);// stack is now [...][enum-type] il.Emit(OpCodes.Ldloc, stringEnumLocal); // stack is now [...][enum-type][string] il.Emit(OpCodes.Ldc_I4_1); // stack is now [...][enum-type][string][true] il.EmitCall(OpCodes.Call, enumParse, null); // stack is now [...][enum-as-object] il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [...][typed-value] } else { FlexibleConvertBoxedFromHeadOfStack(il, colType, unboxType, numericType); } if (nullUnderlyingType is not null) { il.Emit(OpCodes.Newobj, memberType.GetConstructor([nullUnderlyingType])!); // stack is now [...][typed-value] } } else if (memberType.FullName == LinqBinary) { il.Emit(OpCodes.Unbox_Any, typeof(byte[])); // stack is now [...][byte-array] il.Emit(OpCodes.Newobj, memberType.GetConstructor([typeof(byte[])])!);// stack is now [...][binary] } else { TypeCode dataTypeCode = Type.GetTypeCode(colType), unboxTypeCode = Type.GetTypeCode(unboxType); bool hasTypeHandler; if ((hasTypeHandler = typeHandlers.ContainsKey(unboxType)) || colType == unboxType || dataTypeCode == unboxTypeCode || dataTypeCode == Type.GetTypeCode(nullUnderlyingType)) { if (hasTypeHandler) { #pragma warning disable 618 il.EmitCall(OpCodes.Call, typeof(TypeHandlerCache<>).MakeGenericType(unboxType).GetMethod(nameof(TypeHandlerCache.Parse))!, null); // stack is now [...][typed-value] #pragma warning restore 618 } else { il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [...][typed-value] } } else { // not a direct match; need to tweak the unbox FlexibleConvertBoxedFromHeadOfStack(il, colType, nullUnderlyingType ?? unboxType, null); if (nullUnderlyingType is not null) { il.Emit(OpCodes.Newobj, unboxType.GetConstructor([nullUnderlyingType])!); // stack is now [...][typed-value] } } } } } private static void FlexibleConvertBoxedFromHeadOfStack(ILGenerator il, Type from, Type to, Type? via) { MethodInfo? op; if (from == (via ?? to)) { il.Emit(OpCodes.Unbox_Any, to); // stack is now [target][target][typed-value] } else if ((op = GetOperator(from, to)) is not null) { // this is handy for things like decimal <===> double il.Emit(OpCodes.Unbox_Any, from); // stack is now [target][target][data-typed-value] il.Emit(OpCodes.Call, op); // stack is now [target][target][typed-value] } else { bool handled = false; OpCode opCode = default; switch (Type.GetTypeCode(from)) { case TypeCode.Boolean: case TypeCode.Byte: case TypeCode.SByte: case TypeCode.Int16: case TypeCode.UInt16: case TypeCode.Int32: case TypeCode.UInt32: case TypeCode.Int64: case TypeCode.UInt64: case TypeCode.Single: case TypeCode.Double: handled = true; switch (Type.GetTypeCode(via ?? to)) { case TypeCode.Byte: opCode = OpCodes.Conv_Ovf_U1_Un; break; case TypeCode.SByte: opCode = OpCodes.Conv_Ovf_I1; break; case TypeCode.UInt16: opCode = OpCodes.Conv_Ovf_U2_Un; break; case TypeCode.Int16: opCode = OpCodes.Conv_Ovf_I2; break; case TypeCode.UInt32: opCode = OpCodes.Conv_Ovf_U4_Un; break; case TypeCode.Boolean: // boolean is basically an int, at least at this level case TypeCode.Int32: opCode = OpCodes.Conv_Ovf_I4; break; case TypeCode.UInt64: opCode = OpCodes.Conv_Ovf_U8_Un; break; case TypeCode.Int64: opCode = OpCodes.Conv_Ovf_I8; break; case TypeCode.Single: opCode = OpCodes.Conv_R4; break; case TypeCode.Double: opCode = OpCodes.Conv_R8; break; default: handled = false; break; } break; } if (handled) { il.Emit(OpCodes.Unbox_Any, from); // stack is now [target][target][col-typed-value] il.Emit(opCode); // stack is now [target][target][typed-value] if (to == typeof(bool)) { // compare to zero; I checked "csc" - this is the trick it uses; nice il.Emit(OpCodes.Ldc_I4_0); il.Emit(OpCodes.Ceq); il.Emit(OpCodes.Ldc_I4_0); il.Emit(OpCodes.Ceq); } } else { il.Emit(OpCodes.Ldtoken, via ?? to); // stack is now [target][target][value][member-type-token] il.EmitCall(OpCodes.Call, typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle))!, null); // stack is now [target][target][value][member-type] il.EmitCall(OpCodes.Call, InvariantCulture, null); // stack is now [target][target][value][member-type][culture] il.EmitCall(OpCodes.Call, typeof(Convert).GetMethod(nameof(Convert.ChangeType), [typeof(object), typeof(Type), typeof(IFormatProvider)])!, null); // stack is now [target][target][boxed-member-type-value] il.Emit(OpCodes.Unbox_Any, to); // stack is now [target][target][typed-value] } } } private static MethodInfo? GetOperator(Type from, Type to) { if (to is null) return null; MethodInfo[] fromMethods, toMethods; return ResolveOperator(fromMethods = from.GetMethods(BindingFlags.Static | BindingFlags.Public), from, to, "op_Implicit") ?? ResolveOperator(toMethods = to.GetMethods(BindingFlags.Static | BindingFlags.Public), from, to, "op_Implicit") ?? ResolveOperator(fromMethods, from, to, "op_Explicit") ?? ResolveOperator(toMethods, from, to, "op_Explicit"); } private static MethodInfo? ResolveOperator(MethodInfo[] methods, Type from, Type to, string name) { for (int i = 0; i < methods.Length; i++) { if (methods[i].Name != name || methods[i].ReturnType != to) continue; var args = methods[i].GetParameters(); if (args.Length != 1 || args[0].ParameterType != from) continue; return methods[i]; } return null; } /// /// For internal use only /// [Obsolete(ObsoleteInternalUsageOnly, false)] [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] public static void ThrowNullCustomQueryParameter(string name) => throw new InvalidOperationException($"Member '{name}' is an {nameof(ICustomQueryParameter)} and cannot be null"); /// /// Throws a data exception, only used internally /// /// The exception to throw. /// The index the exception occurred at. /// The reader the exception occurred in. /// The value that caused the exception. [Obsolete(ObsoleteInternalUsageOnly, false)] [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] public static void ThrowDataException(Exception ex, int index, IDataReader reader, object? value) { Exception toThrow; try { string name = "(n/a)", formattedValue = "(n/a)"; if (reader is not null && index >= 0 && index < reader.FieldCount) { name = reader.GetName(index); if (name == string.Empty) { // Otherwise we throw (=value) below, which isn't intuitive name = "(Unnamed Column)"; } try { if (value is null && ex is InvalidCastException) { formattedValue = "n/a - " + ex.Message; // provide some context } else if (value is null || value is DBNull) { formattedValue = ""; } else { formattedValue = Convert.ToString(value) + " - " + Identify(value.GetType()); } static string Identify(Type type) { var tc = Type.GetTypeCode(type); return tc == TypeCode.Object ? type.Name : tc.ToString(); } } catch (Exception valEx) { formattedValue = valEx.Message; } } toThrow = new DataException($"Error parsing column {index} ({name}={formattedValue})", ex); } catch { // throw the **original** exception, wrapped as DataException toThrow = new DataException(ex.Message, ex); } throw toThrow; } private static void EmitInt32(ILGenerator il, int value) { switch (value) { case -1: il.Emit(OpCodes.Ldc_I4_M1); break; case 0: il.Emit(OpCodes.Ldc_I4_0); break; case 1: il.Emit(OpCodes.Ldc_I4_1); break; case 2: il.Emit(OpCodes.Ldc_I4_2); break; case 3: il.Emit(OpCodes.Ldc_I4_3); break; case 4: il.Emit(OpCodes.Ldc_I4_4); break; case 5: il.Emit(OpCodes.Ldc_I4_5); break; case 6: il.Emit(OpCodes.Ldc_I4_6); break; case 7: il.Emit(OpCodes.Ldc_I4_7); break; case 8: il.Emit(OpCodes.Ldc_I4_8); break; default: if (value >= -128 && value <= 127) { il.Emit(OpCodes.Ldc_I4_S, (sbyte)value); } else { il.Emit(OpCodes.Ldc_I4, value); } break; } } /// /// How should connection strings be compared for equivalence? Defaults to StringComparer.Ordinal. /// Providing a custom implementation can be useful for allowing multi-tenancy databases with identical /// schema to share strategies. Note that usual equivalence rules apply: any equivalent connection strings /// MUST yield the same hash-code. /// public static IEqualityComparer ConnectionStringComparer { get { return connectionStringComparer; } set { connectionStringComparer = value ?? StringComparer.Ordinal; } } private static IEqualityComparer connectionStringComparer = StringComparer.Ordinal; /// /// Key used to indicate the type name associated with a DataTable. /// private const string DataTableTypeNameKey = "dapper:TypeName"; /// /// Used to pass a DataTable as a . /// /// The to create this parameter for. /// The name of the type this parameter is for. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static ICustomQueryParameter AsTableValuedParameter(this DataTable table, string? typeName = null) => new TableValuedParameter(table, typeName); /// /// Associate a DataTable with a type name. /// /// The that does with the . /// The name of the type this table is for. public static void SetTypeName(this DataTable table, string typeName) { if (table is not null) { if (string.IsNullOrEmpty(typeName)) table.ExtendedProperties.Remove(DataTableTypeNameKey); else table.ExtendedProperties[DataTableTypeNameKey] = typeName; } } /// /// Fetch the type name associated with a . /// /// The that has a type name associated with it. public static string? GetTypeName(this DataTable table) => table?.ExtendedProperties[DataTableTypeNameKey] as string; /// /// Used to pass a IEnumerable<SqlDataRecord> as a TableValuedParameter. /// /// The list of records to convert to TVPs. /// The sql parameter type name. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static ICustomQueryParameter AsTableValuedParameter(this IEnumerable list, string? typeName = null) where T : IDataRecord => new SqlDataRecordListTVPParameter(list, typeName!); // one per thread [ThreadStatic] private static StringBuilder? perThreadStringBuilderCache; private static StringBuilder GetStringBuilder() { var tmp = perThreadStringBuilderCache; if (tmp is not null) { perThreadStringBuilderCache = null; tmp.Length = 0; return tmp; } return new StringBuilder(); } private static string ToStringRecycle(this StringBuilder obj) { if (obj is null) return ""; var s = obj.ToString(); perThreadStringBuilderCache ??= obj; return s; } } } ================================================ FILE: Dapper/TableValuedParameter.cs ================================================ using System.Data; namespace Dapper { /// /// Used to pass a DataTable as a TableValuedParameter /// internal sealed class TableValuedParameter : SqlMapper.ICustomQueryParameter { private readonly DataTable table; private readonly string? typeName; /// /// Create a new instance of . /// /// The to create this parameter for public TableValuedParameter(DataTable table) : this(table, null) { /* run base */ } /// /// Create a new instance of . /// /// The to create this parameter for. /// The name of the type this parameter is for. public TableValuedParameter(DataTable table, string? typeName) { this.table = table; this.typeName = typeName; } void SqlMapper.ICustomQueryParameter.AddParameter(IDbCommand command, string name) { var param = command.CreateParameter(); param.ParameterName = name; Set(param, table, typeName); command.Parameters.Add(param); } internal static void Set(IDbDataParameter parameter, DataTable? table, string? typeName) { #pragma warning disable 0618 parameter.Value = SqlMapper.SanitizeParameterValue(table); #pragma warning restore 0618 if (string.IsNullOrEmpty(typeName) && table is not null) { typeName = table.GetTypeName(); } if (!string.IsNullOrEmpty(typeName)) StructuredHelper.ConfigureTVP(parameter, typeName); } } } ================================================ FILE: Dapper/TypeExtensions.cs ================================================ using System; using System.Reflection; namespace Dapper { internal static class TypeExtensions { public static MethodInfo? GetPublicInstanceMethod(this Type type, string name, Type[] types) => type.GetMethod(name, BindingFlags.Instance | BindingFlags.Public, null, types, null); } } ================================================ FILE: Dapper/UdtTypeHandler.cs ================================================ using System; using System.Data; namespace Dapper { public static partial class SqlMapper { /// /// A type handler for data-types that are supported by the underlying provider, but which need /// a well-known UdtTypeName to be specified /// public class UdtTypeHandler : ITypeHandler { private readonly string udtTypeName; /// /// Creates a new instance of UdtTypeHandler with the specified . /// /// The user defined type name. public UdtTypeHandler(string udtTypeName) { if (string.IsNullOrEmpty(udtTypeName)) throw new ArgumentException("Cannot be null or empty", udtTypeName); this.udtTypeName = udtTypeName; } object? ITypeHandler.Parse(Type destinationType, object value) { return value is DBNull ? null : value; } void ITypeHandler.SetValue(IDbDataParameter parameter, object value) { #pragma warning disable 0618 parameter.Value = SanitizeParameterValue(value); #pragma warning restore 0618 if(!(value is DBNull)) StructuredHelper.ConfigureUDT(parameter, udtTypeName); } } } } ================================================ FILE: Dapper/WrappedDataReader.cs ================================================ using System.Data; namespace Dapper { /// /// Describes a reader that controls the lifetime of both a command and a reader, /// exposing the downstream command/reader as properties. /// public interface IWrappedDataReader : IDataReader { /// /// Obtain the underlying reader /// IDataReader Reader { get; } /// /// Obtain the underlying command /// IDbCommand Command { get; } } } ================================================ FILE: Dapper/WrappedReader.cs ================================================ using System; using System.Collections; using System.Collections.ObjectModel; using System.Data; using System.Data.Common; using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; namespace Dapper { internal sealed class DisposedReader : DbDataReader { internal static readonly DisposedReader Instance = new(); private DisposedReader() { } public override int Depth => 0; public override int FieldCount => 0; public override bool IsClosed => true; public override bool HasRows => false; public override int RecordsAffected => -1; public override int VisibleFieldCount => 0; [MethodImpl(MethodImplOptions.NoInlining)] private static T ThrowDisposed() => throw new ObjectDisposedException(nameof(DbDataReader)); [MethodImpl(MethodImplOptions.NoInlining)] private async static Task ThrowDisposedAsync() { var result = ThrowDisposed(); await Task.Yield(); // will never hit this - already thrown and handled return result; } public override void Close() { } public override DataTable GetSchemaTable() => ThrowDisposed(); #if NET5_0_OR_GREATER [Obsolete("This Remoting API is not supported and throws PlatformNotSupportedException.", DiagnosticId = "SYSLIB0010", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] #endif public override object InitializeLifetimeService() => ThrowDisposed(); protected override void Dispose(bool disposing) { } public override bool GetBoolean(int ordinal) => ThrowDisposed(); public override long GetBytes(int ordinal, long dataOffset, byte[]? buffer, int bufferOffset, int length) => ThrowDisposed(); public override float GetFloat(int ordinal) => ThrowDisposed(); public override short GetInt16(int ordinal) => ThrowDisposed(); public override byte GetByte(int ordinal) => ThrowDisposed(); public override char GetChar(int ordinal) => ThrowDisposed(); public override long GetChars(int ordinal, long dataOffset, char[]? buffer, int bufferOffset, int length) => ThrowDisposed(); public override string GetDataTypeName(int ordinal) => ThrowDisposed(); public override DateTime GetDateTime(int ordinal) => ThrowDisposed(); protected override DbDataReader GetDbDataReader(int ordinal) => ThrowDisposed(); public override decimal GetDecimal(int ordinal) => ThrowDisposed(); public override double GetDouble(int ordinal) => ThrowDisposed(); public override IEnumerator GetEnumerator() => ThrowDisposed(); public override Type GetFieldType(int ordinal) => ThrowDisposed(); public override T GetFieldValue(int ordinal) => ThrowDisposed(); public override Task GetFieldValueAsync(int ordinal, CancellationToken cancellationToken) => ThrowDisposedAsync(); public override Guid GetGuid(int ordinal) => ThrowDisposed(); public override int GetInt32(int ordinal) => ThrowDisposed(); public override long GetInt64(int ordinal) => ThrowDisposed(); public override string GetName(int ordinal) => ThrowDisposed(); public override int GetOrdinal(string name) => ThrowDisposed(); public override Type GetProviderSpecificFieldType(int ordinal) => ThrowDisposed(); public override object GetProviderSpecificValue(int ordinal) => ThrowDisposed(); public override int GetProviderSpecificValues(object[] values) => ThrowDisposed(); public override Stream GetStream(int ordinal) => ThrowDisposed(); public override string GetString(int ordinal) => ThrowDisposed(); public override TextReader GetTextReader(int ordinal) => ThrowDisposed(); public override object GetValue(int ordinal) => ThrowDisposed(); public override int GetValues(object[] values) => ThrowDisposed(); public override bool IsDBNull(int ordinal) => ThrowDisposed(); public override Task IsDBNullAsync(int ordinal, CancellationToken cancellationToken) => ThrowDisposedAsync(); public override bool NextResult() => ThrowDisposed(); public override bool Read() => ThrowDisposed(); public override Task NextResultAsync(CancellationToken cancellationToken) => ThrowDisposedAsync(); public override Task ReadAsync(CancellationToken cancellationToken) => ThrowDisposedAsync(); public override object this[int ordinal] => ThrowDisposed(); public override object this[string name] => ThrowDisposed(); } internal sealed class DbWrappedReader : DbDataReader, IWrappedDataReader { // the purpose of wrapping here is to allow closing a reader to *also* close // the command, without having to explicitly hand the command back to the // caller public static DbDataReader Create(IDbCommand? cmd, DbDataReader reader) { if (cmd is null) return reader; // no need to wrap if no command if (reader is not null) return new DbWrappedReader(cmd, reader); cmd.Dispose(); return null!; // GIGO } private DbDataReader _reader; private IDbCommand _cmd; IDataReader IWrappedDataReader.Reader => _reader; IDbCommand IWrappedDataReader.Command => _cmd; public DbWrappedReader(IDbCommand cmd, DbDataReader reader) { _cmd = cmd; _reader = reader; } public override bool HasRows => _reader.HasRows; public override void Close() => _reader.Close(); public override DataTable? GetSchemaTable() => _reader.GetSchemaTable(); #if NET5_0_OR_GREATER [Obsolete("This Remoting API is not supported and throws PlatformNotSupportedException.", DiagnosticId = "SYSLIB0010", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] #endif public override object InitializeLifetimeService() => _reader.InitializeLifetimeService(); public override int Depth => _reader.Depth; public override bool IsClosed => _reader.IsClosed; public override bool NextResult() => _reader.NextResult(); public override bool Read() => _reader.Read(); public override int RecordsAffected => _reader.RecordsAffected; protected override void Dispose(bool disposing) { if (disposing) { _reader.Dispose(); _reader = DisposedReader.Instance; // all future ops are no-ops _cmd?.Dispose(); _cmd = null!; } } public override int FieldCount => _reader.FieldCount; public override bool GetBoolean(int i) => _reader.GetBoolean(i); public override byte GetByte(int i) => _reader.GetByte(i); public override long GetBytes(int i, long fieldOffset, byte[]? buffer, int bufferoffset, int length) => _reader.GetBytes(i, fieldOffset, buffer, bufferoffset, length); public override char GetChar(int i) => _reader.GetChar(i); public override long GetChars(int i, long fieldoffset, char[]? buffer, int bufferoffset, int length) => _reader.GetChars(i, fieldoffset, buffer, bufferoffset, length); public override string GetDataTypeName(int i) => _reader.GetDataTypeName(i); public override DateTime GetDateTime(int i) => _reader.GetDateTime(i); public override decimal GetDecimal(int i) => _reader.GetDecimal(i); public override double GetDouble(int i) => _reader.GetDouble(i); public override Type GetFieldType(int i) => _reader.GetFieldType(i); public override float GetFloat(int i) => _reader.GetFloat(i); public override Guid GetGuid(int i) => _reader.GetGuid(i); public override short GetInt16(int i) => _reader.GetInt16(i); public override int GetInt32(int i) => _reader.GetInt32(i); public override long GetInt64(int i) => _reader.GetInt64(i); public override string GetName(int i) => _reader.GetName(i); public override int GetOrdinal(string name) => _reader.GetOrdinal(name); public override string GetString(int i) => _reader.GetString(i); public override object GetValue(int i) => _reader.GetValue(i); public override int GetValues(object[] values) => _reader.GetValues(values); public override bool IsDBNull(int i) => _reader.IsDBNull(i); public override object this[string name] => _reader[name]; public override object this[int i] => _reader[i]; public override T GetFieldValue(int ordinal) => _reader.GetFieldValue(ordinal); public override Task GetFieldValueAsync(int ordinal, CancellationToken cancellationToken) => _reader.GetFieldValueAsync(ordinal, cancellationToken); public override IEnumerator GetEnumerator() => _reader.GetEnumerator(); public override Type GetProviderSpecificFieldType(int ordinal) => _reader.GetProviderSpecificFieldType(ordinal); public override object GetProviderSpecificValue(int ordinal) => _reader.GetProviderSpecificValue(ordinal); public override int GetProviderSpecificValues(object[] values) => _reader.GetProviderSpecificValues(values); public override Stream GetStream(int ordinal) => _reader.GetStream(ordinal); public override TextReader GetTextReader(int ordinal) => _reader.GetTextReader(ordinal); public override Task IsDBNullAsync(int ordinal, CancellationToken cancellationToken) => _reader.IsDBNullAsync(ordinal, cancellationToken); public override Task NextResultAsync(CancellationToken cancellationToken) => _reader.NextResultAsync(cancellationToken); public override Task ReadAsync(CancellationToken cancellationToken) => _reader.ReadAsync(cancellationToken); public override int VisibleFieldCount => _reader.VisibleFieldCount; protected override DbDataReader GetDbDataReader(int ordinal) => _reader.GetData(ordinal); #if NET5_0_OR_GREATER public override Task CloseAsync() => _reader.CloseAsync(); public override ValueTask DisposeAsync() => _reader.DisposeAsync(); public override Task> GetColumnSchemaAsync(CancellationToken cancellationToken = default) => _reader.GetColumnSchemaAsync(cancellationToken); public override Task GetSchemaTableAsync(CancellationToken cancellationToken = default) => base.GetSchemaTableAsync(cancellationToken); #endif } internal sealed class WrappedBasicReader : DbDataReader { private IDataReader _reader; public WrappedBasicReader(IDataReader reader) { Debug.Assert(reader is not DbDataReader); // or we wouldn't be here! _reader = reader ?? throw new ArgumentNullException(nameof(reader)); } public override bool HasRows => true; // have to assume that we do public override void Close() => _reader.Close(); public override DataTable? GetSchemaTable() => _reader.GetSchemaTable(); #if NET5_0_OR_GREATER [Obsolete("This Remoting API is not supported and throws PlatformNotSupportedException.", DiagnosticId = "SYSLIB0010", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] #endif public override object InitializeLifetimeService() => throw new NotSupportedException(); public override int Depth => _reader.Depth; public override bool IsClosed => _reader.IsClosed; public override bool NextResult() => _reader.NextResult(); public override bool Read() => _reader.Read(); public override int RecordsAffected => _reader.RecordsAffected; protected override void Dispose(bool disposing) { if (disposing) { _reader.Dispose(); _reader = DisposedReader.Instance; // all future ops are no-ops } } public override int FieldCount => _reader.FieldCount; public override bool GetBoolean(int i) => _reader.GetBoolean(i); public override byte GetByte(int i) => _reader.GetByte(i); public override long GetBytes(int i, long fieldOffset, byte[]? buffer, int bufferoffset, int length) => _reader.GetBytes(i, fieldOffset, buffer!, bufferoffset, length); public override char GetChar(int i) => _reader.GetChar(i); public override long GetChars(int i, long fieldoffset, char[]? buffer, int bufferoffset, int length) => _reader.GetChars(i, fieldoffset, buffer!, bufferoffset, length); public override string GetDataTypeName(int i) => _reader.GetDataTypeName(i); public override DateTime GetDateTime(int i) => _reader.GetDateTime(i); public override decimal GetDecimal(int i) => _reader.GetDecimal(i); public override double GetDouble(int i) => _reader.GetDouble(i); public override Type GetFieldType(int i) => _reader.GetFieldType(i); public override float GetFloat(int i) => _reader.GetFloat(i); public override Guid GetGuid(int i) => _reader.GetGuid(i); public override short GetInt16(int i) => _reader.GetInt16(i); public override int GetInt32(int i) => _reader.GetInt32(i); public override long GetInt64(int i) => _reader.GetInt64(i); public override string GetName(int i) => _reader.GetName(i); public override int GetOrdinal(string name) => _reader.GetOrdinal(name); public override string GetString(int i) => _reader.GetString(i); public override object GetValue(int i) => _reader.GetValue(i); public override int GetValues(object[] values) => _reader.GetValues(values); public override bool IsDBNull(int i) => _reader.IsDBNull(i); public override object this[string name] => _reader[name]; public override object this[int i] => _reader[i]; public override T GetFieldValue(int ordinal) { var value = _reader.GetValue(ordinal); if (value is DBNull) { value = null; } return (T)value!; } public override Task GetFieldValueAsync(int ordinal, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); return Task.FromResult(GetFieldValue(ordinal)); } public override IEnumerator GetEnumerator() => _reader is IEnumerable e ? e.GetEnumerator() : throw new NotImplementedException(); public override Type GetProviderSpecificFieldType(int ordinal) => _reader.GetFieldType(ordinal); public override object GetProviderSpecificValue(int ordinal) => _reader.GetValue(ordinal); public override int GetProviderSpecificValues(object[] values) => _reader.GetValues(values); public override Stream GetStream(int ordinal) => throw new NotSupportedException(); public override TextReader GetTextReader(int ordinal) => throw new NotSupportedException(); public override Task IsDBNullAsync(int ordinal, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); return Task.FromResult(_reader.IsDBNull(ordinal)); } public override Task NextResultAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); return Task.FromResult(_reader.NextResult()); } public override Task ReadAsync(CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); return Task.FromResult(_reader.Read()); } public override int VisibleFieldCount => _reader.FieldCount; protected override DbDataReader GetDbDataReader(int ordinal) => throw new NotSupportedException(); #if NET5_0_OR_GREATER public override Task CloseAsync() { _reader.Close(); return Task.CompletedTask; } public override ValueTask DisposeAsync() { _reader.Dispose(); return default; } public override Task> GetColumnSchemaAsync(CancellationToken cancellationToken = default) => throw new NotSupportedException(); public override Task GetSchemaTableAsync(CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); return Task.FromResult(_reader.GetSchemaTable()); } #endif } } ================================================ FILE: Dapper/XmlHandlers.cs ================================================ using System.Data; using System.Xml; using System.Xml.Linq; namespace Dapper { internal abstract class XmlTypeHandler : SqlMapper.StringTypeHandler { public override void SetValue(IDbDataParameter parameter, T? value) { base.SetValue(parameter, value); parameter.DbType = DbType.Xml; } } internal sealed class XmlDocumentHandler : XmlTypeHandler { protected override XmlDocument Parse(string xml) { var doc = new XmlDocument(); doc.LoadXml(xml); return doc; } protected override string Format(XmlDocument xml) => xml.OuterXml; } internal sealed class XDocumentHandler : XmlTypeHandler { protected override XDocument Parse(string xml) => XDocument.Parse(xml); protected override string Format(XDocument xml) => xml.ToString(); } internal sealed class XElementHandler : XmlTypeHandler { protected override XElement Parse(string xml) => XElement.Parse(xml); protected override string Format(XElement xml) => xml.ToString(); } } ================================================ FILE: Dapper.EntityFramework/Dapper.EntityFramework.csproj ================================================  Dapper.EntityFramework Extension handlers for entity framework Dapper entity framework type handlers 1.50.2 Marc Gravell;Nick Craver net461 orm;sql;micro-orm enable all runtime; build; native; contentfiles; analyzers ================================================ FILE: Dapper.EntityFramework/DbGeographyHandler.cs ================================================ using Microsoft.SqlServer.Types; using System; using System.Data; using System.Data.Entity.Spatial; using System.Data.SqlClient; using System.Data.SqlTypes; namespace Dapper.EntityFramework { /// /// Type-handler for the DbGeography spatial type. /// public class DbGeographyHandler : SqlMapper.TypeHandler { /// /// Create a new handler instance. /// protected DbGeographyHandler() { /* create new */ } /// /// Default handler instance /// public static readonly DbGeographyHandler Default = new DbGeographyHandler(); /// /// Assign the value of a parameter before a command executes. /// /// The parameter to configure. /// Parameter value. public override void SetValue(IDbDataParameter parameter, DbGeography? value) { object? parsed = null; if (value is not null) { parsed = SqlGeography.STGeomFromWKB(new SqlBytes(value.AsBinary()), value.CoordinateSystemId); } parameter.Value = parsed ?? DBNull.Value; if (parameter is SqlParameter sqlParameter) { sqlParameter.UdtTypeName = "geography"; } } /// /// Parse a database value back to a typed value. /// /// The value from the database. /// The typed value. public override DbGeography? Parse(object? value) { if (value is null || value is DBNull) return null; if (value is SqlGeography geo) { return DbGeography.FromBinary(geo.STAsBinary().Value, geo.STSrid.Value); } return DbGeography.FromText(value.ToString()); } } } ================================================ FILE: Dapper.EntityFramework/DbGeometryHandler.cs ================================================ using Microsoft.SqlServer.Types; using System; using System.Data; using System.Data.Entity.Spatial; using System.Data.SqlClient; using System.Data.SqlTypes; namespace Dapper.EntityFramework { /// /// Type-handler for the DbGeometry spatial type. /// public class DbGeometryHandler : SqlMapper.TypeHandler { /// /// Create a new handler instance. /// protected DbGeometryHandler() { /* create new */ } /// /// Default handler instance. /// public static readonly DbGeometryHandler Default = new DbGeometryHandler(); /// /// Assign the value of a parameter before a command executes. /// /// The parameter to configure. /// Parameter value. public override void SetValue(IDbDataParameter parameter, DbGeometry? value) { object? parsed = null; if (value is not null) { parsed = SqlGeometry.STGeomFromWKB(new SqlBytes(value.AsBinary()), value.CoordinateSystemId); } parameter.Value = parsed ?? DBNull.Value; if (parameter is SqlParameter sqlP) { sqlP.UdtTypeName = "geometry"; } } /// /// Parse a database value back to a typed value. /// /// The value from the database. /// The typed value. public override DbGeometry? Parse(object? value) { if (value is null || value is DBNull) return null; if (value is SqlGeometry geo) { return DbGeometry.FromBinary(geo.STAsBinary().Value, geo.STSrid.Value); } return DbGeometry.FromText(value.ToString()); } } } ================================================ FILE: Dapper.EntityFramework/Handlers.cs ================================================ namespace Dapper.EntityFramework { /// /// Acts on behalf of all type-handlers in this package /// public static class Handlers { /// /// Register all type-handlers in this package /// public static void Register() { SqlMapper.AddTypeHandler(DbGeographyHandler.Default); SqlMapper.AddTypeHandler(DbGeometryHandler.Default); } } } ================================================ FILE: Dapper.EntityFramework/PublicAPI.Shipped.txt ================================================ #nullable enable Dapper.EntityFramework.DbGeographyHandler Dapper.EntityFramework.DbGeographyHandler.DbGeographyHandler() -> void Dapper.EntityFramework.DbGeometryHandler Dapper.EntityFramework.DbGeometryHandler.DbGeometryHandler() -> void Dapper.EntityFramework.Handlers override Dapper.EntityFramework.DbGeographyHandler.Parse(object? value) -> System.Data.Entity.Spatial.DbGeography? override Dapper.EntityFramework.DbGeographyHandler.SetValue(System.Data.IDbDataParameter! parameter, System.Data.Entity.Spatial.DbGeography? value) -> void override Dapper.EntityFramework.DbGeometryHandler.Parse(object? value) -> System.Data.Entity.Spatial.DbGeometry? override Dapper.EntityFramework.DbGeometryHandler.SetValue(System.Data.IDbDataParameter! parameter, System.Data.Entity.Spatial.DbGeometry? value) -> void static Dapper.EntityFramework.Handlers.Register() -> void static readonly Dapper.EntityFramework.DbGeographyHandler.Default -> Dapper.EntityFramework.DbGeographyHandler! static readonly Dapper.EntityFramework.DbGeometryHandler.Default -> Dapper.EntityFramework.DbGeometryHandler! ================================================ FILE: Dapper.EntityFramework/PublicAPI.Unshipped.txt ================================================ #nullable enable ================================================ FILE: Dapper.EntityFramework.StrongName/Dapper.EntityFramework.StrongName.csproj ================================================  Dapper.EntityFramework.StrongName Dapper: Entity Framework type handlers (with a strong name) Extension handlers for entity framework Marc Gravell;Nick Craver net461 ../Dapper.snk true true Dapper.EntityFramework.StrongName orm;sql;micro-orm enable ================================================ FILE: Dapper.ProviderTools/BulkCopy.cs ================================================ using System; using System.Collections.Concurrent; using System.Data; using System.Data.Common; using System.Linq.Expressions; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Dapper.ProviderTools.Internal; namespace Dapper.ProviderTools { /// /// Provides provider-agnostic access to bulk-copy services /// public abstract class BulkCopy : IDisposable { /// /// Attempt to create a BulkCopy instance for the connection provided /// public static BulkCopy? TryCreate(DbConnection connection) { if (connection is null) return null; var type = connection.GetType(); if (!s_bcpFactory.TryGetValue(type, out var func)) { s_bcpFactory[type] = func = CreateBcpFactory(type); } var obj = func?.Invoke(connection); return DynamicBulkCopy.Create(obj); } /// /// Create a BulkCopy instance for the connection provided /// public static BulkCopy Create(DbConnection connection) { var bcp = TryCreate(connection); if (bcp is null) { if (connection is null) throw new ArgumentNullException(nameof(connection)); throw new NotSupportedException("Unable to create BulkCopy for " + connection.GetType().FullName); } return bcp; } ///// ///// Provide an external registration for a given connection type ///// //public static void Register(Type type, Func factory) //{ // throw new NotImplementedException(); //} private static readonly ConcurrentDictionary?> s_bcpFactory = new ConcurrentDictionary?>(); internal static Func? CreateBcpFactory(Type connectionType) { try { var match = Regex.Match(connectionType.Name, "^(.+)Connection$"); if (match.Success) { var prefix = match.Groups[1].Value; var bcpType = connectionType.Assembly.GetType($"{connectionType.Namespace}.{prefix}BulkCopy"); if (bcpType is not null) { var ctor = bcpType.GetConstructor(new[] { connectionType }); if (ctor is null) return null; var p = Expression.Parameter(typeof(DbConnection), "conn"); var body = Expression.New(ctor, Expression.Convert(p, connectionType)); return Expression.Lambda>(body, p).Compile(); } } } catch { } return null; } /// /// Name of the destination table on the server. /// public abstract string DestinationTableName { get; set; } /// /// Write a set of data to the server /// public abstract void WriteToServer(DataTable source); /// /// Write a set of data to the server /// public abstract void WriteToServer(DataRow[] source); /// /// Write a set of data to the server /// public abstract void WriteToServer(IDataReader source); /// /// Write a set of data to the server /// [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public abstract Task WriteToServerAsync(DbDataReader source, CancellationToken cancellationToken = default); /// /// Write a set of data to the server /// [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public abstract Task WriteToServerAsync(DataTable source, CancellationToken cancellationToken = default); /// /// Write a set of data to the server /// [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public abstract Task WriteToServerAsync(DataRow[] source, CancellationToken cancellationToken = default); /// /// Add a mapping between two columns by name /// public abstract void AddColumnMapping(string sourceColumn, string destinationColumn); /// /// Add a mapping between two columns by position /// public abstract void AddColumnMapping(int sourceColumn, int destinationColumn); /// /// The underlying untyped object providing the bulk-copy service /// public abstract object Wrapped { get; } /// /// Enables or disables streaming from a data-reader /// public bool EnableStreaming { get; set; } /// /// Number of rows in each batch /// public int BatchSize { get; set; } /// /// Number of seconds for the operation to complete before it times out. /// public int BulkCopyTimeout { get; set; } /// /// Release any resources associated with this instance /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Release any resources associated with this instance /// protected abstract void Dispose(bool disposing); } } ================================================ FILE: Dapper.ProviderTools/Dapper.ProviderTools.csproj ================================================  Dapper.ProviderTools orm;sql;micro-orm Dapper Provider Tools Provider-agnostic ADO.NET helper utilities Marc Gravell net461;netstandard2.0;net8.0 true enable all runtime; build; native; contentfiles; analyzers ================================================ FILE: Dapper.ProviderTools/DbConnectionExtensions.cs ================================================ using System; using System.Collections.Concurrent; using System.Data.Common; using System.Linq.Expressions; using System.Reflection; namespace Dapper.ProviderTools { /// /// Helper utilities for working with database connections /// public static class DbConnectionExtensions { /// /// Attempt to get the client connection id for a given connection /// public static bool TryGetClientConnectionId(this DbConnection connection, out Guid clientConnectionId) { clientConnectionId = default; return connection is not null && ByTypeHelpers.Get(connection.GetType()).TryGetClientConnectionId( connection, out clientConnectionId); } /// /// Clear all pools associated with the provided connection type /// public static bool TryClearAllPools(this DbConnection connection) => connection is not null && ByTypeHelpers.Get(connection.GetType()).TryClearAllPools(); /// /// Clear the pools associated with the provided connection /// public static bool TryClearPool(this DbConnection connection) => connection is not null && ByTypeHelpers.Get(connection.GetType()).TryClearPool(connection); private sealed class ByTypeHelpers { private static readonly ConcurrentDictionary s_byType = new ConcurrentDictionary(); private readonly Func? _getClientConnectionId; private readonly Action? _clearPool; private readonly Action? _clearAllPools; public bool TryGetClientConnectionId(DbConnection connection, out Guid clientConnectionId) { if (_getClientConnectionId is null) { clientConnectionId = default; return false; } clientConnectionId = _getClientConnectionId(connection); return true; } public bool TryClearPool(DbConnection connection) { if (_clearPool is null) return false; _clearPool(connection); return true; } public bool TryClearAllPools() { if (_clearAllPools is null) return false; _clearAllPools(); return true; } public static ByTypeHelpers Get(Type type) { if (!s_byType.TryGetValue(type, out var value)) { s_byType[type] = value = new ByTypeHelpers(type); } return value; } private ByTypeHelpers(Type type) { _getClientConnectionId = TryGetInstanceProperty("ClientConnectionId", type); try { var clearAllPools = type.GetMethod("ClearAllPools", BindingFlags.Public | BindingFlags.Static, null, Type.EmptyTypes, null); if (clearAllPools is not null) { _clearAllPools = (Action)Delegate.CreateDelegate(typeof(Action), clearAllPools); } } catch { } try { var clearPool = type.GetMethod("ClearPool", BindingFlags.Public | BindingFlags.Static, null, new[] { type }, null); if (clearPool is not null) { var p = Expression.Parameter(typeof(DbConnection), "connection"); var body = Expression.Call(clearPool, Expression.Convert(p, type)); var lambda = Expression.Lambda>(body, p); _clearPool = lambda.Compile(); } } catch { } } private static Func? TryGetInstanceProperty(string name, Type type) { try { var prop = type.GetProperty(name, BindingFlags.Public | BindingFlags.Instance); if (prop is null || !prop.CanRead) return null; if (prop.PropertyType != typeof(T)) return null; var p = Expression.Parameter(typeof(DbConnection), "connection"); var body = Expression.Property(Expression.Convert(p, type), prop); var lambda = Expression.Lambda>(body, p); return lambda.Compile(); } catch { return null; } } } } } ================================================ FILE: Dapper.ProviderTools/DbExceptionExtensions.cs ================================================ using System; using System.Collections.Concurrent; using System.Data.Common; using System.Linq.Expressions; using System.Reflection; namespace Dapper.ProviderTools { /// /// Helper utilities for working with database exceptions /// public static class DbExceptionExtensions { /// /// Indicates whether the provided exception has an integer Number property with the supplied value /// public static bool IsNumber(this DbException exception, int number) => exception is not null && ByTypeHelpers.Get(exception.GetType()).IsNumber(exception, number); private sealed class ByTypeHelpers { private static readonly ConcurrentDictionary s_byType = new ConcurrentDictionary(); private readonly Func? _getNumber; public bool IsNumber(DbException exception, int number) => _getNumber is not null && _getNumber(exception) == number; public static ByTypeHelpers Get(Type type) { if (!s_byType.TryGetValue(type, out var value)) { s_byType[type] = value = new ByTypeHelpers(type); } return value; } private ByTypeHelpers(Type type) { _getNumber = TryGetInstanceProperty("Number", type); } private static Func? TryGetInstanceProperty(string name, Type type) { try { var prop = type.GetProperty(name, BindingFlags.Public | BindingFlags.Instance); if (prop is null || !prop.CanRead) return null; if (prop.PropertyType != typeof(T)) return null; var p = Expression.Parameter(typeof(DbException), "exception"); var body = Expression.Property(Expression.Convert(p, type), prop); var lambda = Expression.Lambda>(body, p); return lambda.Compile(); } catch { return null; } } } } } ================================================ FILE: Dapper.ProviderTools/Internal/DynamicBulkCopy.cs ================================================ using System; using System.Data; using System.Data.Common; using System.Threading; using System.Threading.Tasks; #nullable enable namespace Dapper.ProviderTools.Internal { internal sealed class DynamicBulkCopy : BulkCopy { internal static BulkCopy? Create(object? wrapped) => wrapped is null ? null : new DynamicBulkCopy(wrapped); private DynamicBulkCopy(object wrapped) => _wrapped = wrapped; private readonly dynamic _wrapped; public override string DestinationTableName { get => _wrapped.DestinationTableName; set => _wrapped.DestinationTableName = value; } public override object Wrapped => _wrapped; public override void AddColumnMapping(string sourceColumn, string destinationColumn) => _wrapped.ColumnMappings.Add(sourceColumn, destinationColumn); public override void AddColumnMapping(int sourceColumn, int destinationColumn) => _wrapped.ColumnMappings.Add(sourceColumn, destinationColumn); public override void WriteToServer(DataTable source) => _wrapped.WriteToServer(source); public override void WriteToServer(DataRow[] source) => _wrapped.WriteToServer(source); public override void WriteToServer(IDataReader source) => _wrapped.WriteToServer(source); public override Task WriteToServerAsync(DbDataReader source, CancellationToken cancellationToken) => _wrapped.WriteToServer(source, cancellationToken); public override Task WriteToServerAsync(DataTable source, CancellationToken cancellationToken) => _wrapped.WriteToServer(source, cancellationToken); public override Task WriteToServerAsync(DataRow[] source, CancellationToken cancellationToken) => _wrapped.WriteToServer(source, cancellationToken); protected override void Dispose(bool disposing) { if (disposing) { if (_wrapped is IDisposable d) { try { d.Dispose(); } catch { } } } } } } ================================================ FILE: Dapper.ProviderTools/PublicAPI.Shipped.txt ================================================ #nullable enable abstract Dapper.ProviderTools.BulkCopy.AddColumnMapping(int sourceColumn, int destinationColumn) -> void abstract Dapper.ProviderTools.BulkCopy.AddColumnMapping(string! sourceColumn, string! destinationColumn) -> void abstract Dapper.ProviderTools.BulkCopy.DestinationTableName.get -> string! abstract Dapper.ProviderTools.BulkCopy.DestinationTableName.set -> void abstract Dapper.ProviderTools.BulkCopy.Dispose(bool disposing) -> void abstract Dapper.ProviderTools.BulkCopy.Wrapped.get -> object! abstract Dapper.ProviderTools.BulkCopy.WriteToServer(System.Data.DataRow![]! source) -> void abstract Dapper.ProviderTools.BulkCopy.WriteToServer(System.Data.DataTable! source) -> void abstract Dapper.ProviderTools.BulkCopy.WriteToServer(System.Data.IDataReader! source) -> void abstract Dapper.ProviderTools.BulkCopy.WriteToServerAsync(System.Data.Common.DbDataReader! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! abstract Dapper.ProviderTools.BulkCopy.WriteToServerAsync(System.Data.DataRow![]! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! abstract Dapper.ProviderTools.BulkCopy.WriteToServerAsync(System.Data.DataTable! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! Dapper.ProviderTools.BulkCopy Dapper.ProviderTools.BulkCopy.BatchSize.get -> int Dapper.ProviderTools.BulkCopy.BatchSize.set -> void Dapper.ProviderTools.BulkCopy.BulkCopy() -> void Dapper.ProviderTools.BulkCopy.BulkCopyTimeout.get -> int Dapper.ProviderTools.BulkCopy.BulkCopyTimeout.set -> void Dapper.ProviderTools.BulkCopy.Dispose() -> void Dapper.ProviderTools.BulkCopy.EnableStreaming.get -> bool Dapper.ProviderTools.BulkCopy.EnableStreaming.set -> void Dapper.ProviderTools.DbConnectionExtensions Dapper.ProviderTools.DbExceptionExtensions static Dapper.ProviderTools.BulkCopy.Create(System.Data.Common.DbConnection! connection) -> Dapper.ProviderTools.BulkCopy! static Dapper.ProviderTools.BulkCopy.TryCreate(System.Data.Common.DbConnection! connection) -> Dapper.ProviderTools.BulkCopy? static Dapper.ProviderTools.DbConnectionExtensions.TryClearAllPools(this System.Data.Common.DbConnection! connection) -> bool static Dapper.ProviderTools.DbConnectionExtensions.TryClearPool(this System.Data.Common.DbConnection! connection) -> bool static Dapper.ProviderTools.DbConnectionExtensions.TryGetClientConnectionId(this System.Data.Common.DbConnection! connection, out System.Guid clientConnectionId) -> bool static Dapper.ProviderTools.DbExceptionExtensions.IsNumber(this System.Data.Common.DbException! exception, int number) -> bool ================================================ FILE: Dapper.ProviderTools/PublicAPI.Unshipped.txt ================================================ #nullable enable ================================================ FILE: Dapper.Rainbow/Dapper.Rainbow.csproj ================================================  Dapper.Rainbow orm;sql;micro-orm Dapper.Rainbow Trivial micro-orm implemented on Dapper, provides with CRUD helpers. Sam Saffron 2017 Sam Saffron net461;netstandard2.0;net5.0 false ================================================ FILE: Dapper.Rainbow/Database.Async.cs ================================================ using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Dapper { public abstract partial class Database where TDatabase : Database, new() { public partial class Table { /// /// Insert a row into the db asynchronously. /// /// Either DynamicParameters or an anonymous type or concrete type. /// The Id of the inserted row. public virtual async Task InsertAsync(dynamic data) { var o = (object)data; List paramNames = GetParamNames(o); paramNames.Remove("Id"); string cols = string.Join(",", paramNames); string colsParams = string.Join(",", paramNames.Select(p => "@" + p)); var sql = "set nocount on insert " + TableName + " (" + cols + ") values (" + colsParams + ") select cast(scope_identity() as int)"; return (await database.QueryAsync(sql, o).ConfigureAwait(false)).Single(); } /// /// Update a record in the DB asynchronously. /// /// The Id of the record to update. /// The new record. /// The number of affected rows. public Task UpdateAsync(TId id, dynamic data) { List paramNames = GetParamNames((object)data); var builder = new StringBuilder(); builder.Append("update ").Append(TableName).Append(" set "); builder.AppendLine(string.Join(",", paramNames.Where(n => n != "Id").Select(p => p + "= @" + p))); builder.Append("where Id = @Id"); var parameters = new DynamicParameters(data); parameters.Add("Id", id); return database.ExecuteAsync(builder.ToString(), parameters); } /// /// Asynchronously deletes a record for the DB. /// /// The Id of the record to delete. /// The number of rows affected. public async Task DeleteAsync(TId id) => (await database.ExecuteAsync("delete from " + TableName + " where Id = @id", new { id }).ConfigureAwait(false)) > 0; /// /// Asynchronously gets a record with a particular Id from the DB. /// /// The primary key of the table to fetch. /// The record with the specified Id. public Task GetAsync(TId id) => database.QueryFirstOrDefaultAsync("select * from " + TableName + " where Id = @id", new { id }); /// /// Asynchronously gets the first row from this table (order determined by the database provider). /// /// Data from the first table row. public virtual Task FirstAsync() => database.QueryFirstOrDefaultAsync("select top 1 * from " + TableName); /// /// Asynchronously gets the all rows from this table. /// /// Data from all table rows. public Task> AllAsync() => database.QueryAsync("select * from " + TableName); } /// /// Asynchronously executes SQL against the current database. /// /// The SQL to execute. /// The parameters to use. /// The number of rows affected. public Task ExecuteAsync(string sql, dynamic param = null) => _connection.ExecuteAsync(sql, param as object, Transaction, _commandTimeout); /// /// Asynchronously queries the current database. /// /// The type to return. /// The SQL to execute. /// The parameters to use. /// An enumerable of for the rows fetched. public Task> QueryAsync(string sql, dynamic param = null) => _connection.QueryAsync(sql, param as object, Transaction, _commandTimeout); /// /// Asynchronously queries the current database for a single record. /// /// The type to return. /// The SQL to execute. /// The parameters to use. /// An enumerable of for the rows fetched. public Task QueryFirstOrDefaultAsync(string sql, dynamic param = null) => _connection.QueryFirstOrDefaultAsync(sql, param as object, Transaction, _commandTimeout); /// /// Perform an asynchronous multi-mapping query with 2 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. /// The second type in the recordset. /// The combined type to return. /// The SQL to execute for this query. /// The function to map row types to the return type. /// The parameters to use for this query. /// The transaction to use for this query. /// Whether to buffer the results in memory. /// The field we should split and read the second object from (default: "Id"). /// Number of seconds before command execution timeout. /// An enumerable of . public Task> QueryAsync(string sql, Func map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null) => _connection.QueryAsync(sql, map, param as object, transaction, buffered, splitOn, commandTimeout); /// /// Perform an asynchronous multi-mapping query with 3 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. /// The second type in the recordset. /// The third type in the recordset. /// The combined type to return. /// The SQL to execute for this query. /// The function to map row types to the return type. /// The parameters to use for this query. /// The transaction to use for this query. /// Whether to buffer the results in memory. /// The field we should split and read the second object from (default: "Id"). /// Number of seconds before command execution timeout. /// An enumerable of . public Task> QueryAsync(string sql, Func map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null) => _connection.QueryAsync(sql, map, param as object, transaction, buffered, splitOn, commandTimeout); /// /// Perform an asynchronous multi-mapping query with 4 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. /// The second type in the recordset. /// The third type in the recordset. /// The fourth type in the recordset. /// The combined type to return. /// The SQL to execute for this query. /// The function to map row types to the return type. /// The parameters to use for this query. /// The transaction to use for this query. /// Whether to buffer the results in memory. /// The field we should split and read the second object from (default: "Id"). /// Number of seconds before command execution timeout. /// An enumerable of . public Task> QueryAsync(string sql, Func map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null) => _connection.QueryAsync(sql, map, param as object, transaction, buffered, splitOn, commandTimeout); /// /// Perform an asynchronous multi-mapping query with 5 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. /// The second type in the recordset. /// The third type in the recordset. /// The fourth type in the recordset. /// The fifth type in the recordset. /// The combined type to return. /// The SQL to execute for this query. /// The function to map row types to the return type. /// The parameters to use for this query. /// The transaction to use for this query. /// Whether to buffer the results in memory. /// The field we should split and read the second object from (default: "Id"). /// Number of seconds before command execution timeout. /// An enumerable of . public Task> QueryAsync(string sql, Func map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null) => _connection.QueryAsync(sql, map, param as object, transaction, buffered, splitOn, commandTimeout); /// /// Execute a query asynchronously using Task. /// /// The SQL to execute. /// The parameters to use. /// Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object> public Task> QueryAsync(string sql, dynamic param = null) => _connection.QueryAsync(sql, param as object, Transaction); /// /// Execute a command that returns multiple result sets, and access each in turn. /// /// The SQL to execute for this query. /// The parameters to use for this query. /// The transaction to use for this query. /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? public Task QueryMultipleAsync(string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => SqlMapper.QueryMultipleAsync(_connection, sql, param, transaction, commandTimeout, commandType); } } ================================================ FILE: Dapper.Rainbow/Database.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Data; using System.Collections.Concurrent; using System.Reflection; using System.Text; using System.Data.Common; using System.Reflection.Emit; namespace Dapper { /// /// Represents a database, assumes all the tables have an Id column named Id /// /// The type of database this represents. public abstract partial class Database : IDisposable where TDatabase : Database, new() { /// /// A database table of type and primary key of type . /// /// The type of object in this table. /// The type of the primary key for this table. public partial class Table { internal Database database; internal string tableName; internal string likelyTableName; /// /// Creates a table in the specified database with a given name. /// /// The database this table belongs in. /// The name for this table. public Table(Database database, string likelyTableName) { this.database = database; this.likelyTableName = likelyTableName; } /// /// The name for this table. /// public string TableName { get { tableName ??= database.DetermineTableName(likelyTableName); return tableName; } } /// /// Insert a row into the db /// /// Either DynamicParameters or an anonymous type or concrete type /// public virtual int? Insert(dynamic data) { var o = (object)data; List paramNames = GetParamNames(o); paramNames.Remove("Id"); string cols = string.Join(",", paramNames); string colsParams = string.Join(",", paramNames.Select(p => "@" + p)); var sql = "set nocount on insert " + TableName + " (" + cols + ") values (" + colsParams + ") select cast(scope_identity() as int)"; return database.Query(sql, o).Single(); } /// /// Update a record in the database. /// /// The primary key of the row to update. /// The new object data. /// The number of rows affected. public int Update(TId id, dynamic data) { List paramNames = GetParamNames((object)data); var builder = new StringBuilder(); builder.Append("update ").Append(TableName).Append(" set "); builder.AppendLine(string.Join(",", paramNames.Where(n => n != "Id").Select(p => p + "= @" + p))); builder.Append("where Id = @Id"); DynamicParameters parameters = new DynamicParameters(data); parameters.Add("Id", id); return database.Execute(builder.ToString(), parameters); } /// /// Delete a record for the DB /// /// /// public bool Delete(TId id) { return database.Execute("delete from " + TableName + " where Id = @id", new { id }) > 0; } /// /// Gets a record with a particular Id from the DB /// /// The primary key of the table to fetch. /// The record with the specified Id. public T Get(TId id) { return database.QueryFirstOrDefault("select * from " + TableName + " where Id = @id", new { id }); } /// /// Gets the first row from this table (order determined by the database provider). /// /// Data from the first table row. public virtual T First() { return database.QueryFirstOrDefault("select top 1 * from " + TableName); } /// /// Gets the all rows from this table. /// /// Data from all table rows. public IEnumerable All() { return database.Query("select * from " + TableName); } private static readonly ConcurrentDictionary> paramNameCache = new ConcurrentDictionary>(); internal static List GetParamNames(object o) { if (o is DynamicParameters parameters) { return parameters.ParameterNames.ToList(); } if (!paramNameCache.TryGetValue(o.GetType(), out List paramNames)) { paramNames = new List(); foreach (var prop in o.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(p => p.GetGetMethod(false) != null)) { var attribs = prop.GetCustomAttributes(typeof(IgnorePropertyAttribute), true); #pragma warning disable IDE0019 // Use pattern matching - complex enough here var attr = attribs.FirstOrDefault() as IgnorePropertyAttribute; #pragma warning restore IDE0019 // Use pattern matching if (attr == null || (!attr.Value)) { paramNames.Add(prop.Name); } } paramNameCache[o.GetType()] = paramNames; } return paramNames; } } /// /// A database table of type and primary key of type . /// /// The type of object in this table. public class Table : Table { /// /// Creates a table in the specified database with a given name. /// /// The database this table belongs in. /// The name for this table. public Table(Database database, string likelyTableName) : base(database, likelyTableName) { } } private DbConnection _connection; private int _commandTimeout; /// /// Get access to the underlying transaction /// public DbTransaction Transaction { get; private set; } /// /// Get underlying database connection. /// public DbConnection Connection => _connection; /// /// Initializes the database. /// /// The connection to use. /// The timeout to use (in seconds). /// public static TDatabase Init(DbConnection connection, int commandTimeout) { TDatabase db = new TDatabase(); db.InitDatabase(connection, commandTimeout); return db; } internal static Action tableConstructor; internal void InitDatabase(DbConnection connection, int commandTimeout) { _connection = connection; _commandTimeout = commandTimeout; tableConstructor ??= CreateTableConstructorForTable(); tableConstructor(this as TDatabase); } internal virtual Action CreateTableConstructorForTable() { return CreateTableConstructor(typeof(Table<>), typeof(Table<,>)); } /// /// Begins a transaction in this database. /// /// The isolation level to use. /// The transaction created public DbTransaction BeginTransaction(IsolationLevel isolation = IsolationLevel.ReadCommitted) { Transaction = _connection.BeginTransaction(isolation); return Transaction; } /// /// Commits the current transaction in this database. /// public void CommitTransaction() { Transaction.Commit(); Transaction = null; } /// /// Rolls back the current transaction in this database. /// public void RollbackTransaction() { Transaction.Rollback(); Transaction = null; } /// /// Gets a table creation function for the specified type. /// /// The object type to create a table for. /// The function to create the table. protected Action CreateTableConstructor(Type tableType) { return CreateTableConstructor(new[] { tableType }); } /// /// Gets a table creation function for the specified types. /// /// The object types to create a table for. /// The function to create the tables. protected Action CreateTableConstructor(params Type[] tableTypes) { var dm = new DynamicMethod("ConstructInstances", null, new[] { typeof(TDatabase) }, true); var il = dm.GetILGenerator(); var setters = GetType().GetProperties() .Where(p => p.PropertyType.IsGenericType && tableTypes.Contains(p.PropertyType.GetGenericTypeDefinition())) .Select(p => Tuple.Create( p.GetSetMethod(true), p.PropertyType.GetConstructor(new[] { typeof(TDatabase), typeof(string) }), p.Name, p.DeclaringType )); foreach (var setter in setters) { il.Emit(OpCodes.Ldarg_0); // [db] il.Emit(OpCodes.Ldstr, setter.Item3); // [db, likelyname] il.Emit(OpCodes.Newobj, setter.Item2); // [table] var table = il.DeclareLocal(setter.Item2.DeclaringType); il.Emit(OpCodes.Stloc, table); // [] il.Emit(OpCodes.Ldarg_0); // [db] il.Emit(OpCodes.Castclass, setter.Item4); // [db cast to container] il.Emit(OpCodes.Ldloc, table); // [db cast to container, table] il.Emit(OpCodes.Callvirt, setter.Item1); // [] } il.Emit(OpCodes.Ret); return (Action)dm.CreateDelegate(typeof(Action)); } private static readonly ConcurrentDictionary tableNameMap = new ConcurrentDictionary(); private string DetermineTableName(string likelyTableName) { if (!tableNameMap.TryGetValue(typeof(T), out string name)) { name = likelyTableName; if (!TableExists(name)) { name = "[" + typeof(T).Name + "]"; } tableNameMap[typeof(T)] = name; } return name; } private bool TableExists(string name) { string schemaName = null; name = name.Replace("[", ""); name = name.Replace("]", ""); if (name.IndexOf('.') > 0) { var parts = name.Split('.'); if (parts.Length == 2) { schemaName = parts[0]; name = parts[1]; } } var builder = new StringBuilder("select 1 from INFORMATION_SCHEMA.TABLES where "); if (!string.IsNullOrEmpty(schemaName)) builder.Append("TABLE_SCHEMA = @schemaName AND "); builder.Append("TABLE_NAME = @name"); return _connection.Query(builder.ToString(), new { schemaName, name }, Transaction).Count() == 1; } /// /// Executes SQL against the current database. /// /// The SQL to execute. /// The parameters to use. /// The number of rows affected. public int Execute(string sql, dynamic param = null) => _connection.Execute(sql, param as object, Transaction, _commandTimeout); /// /// Queries the current database. /// /// The type to return. /// The SQL to execute. /// The parameters to use. /// Whether to buffer the results. /// An enumerable of for the rows fetched. public IEnumerable Query(string sql, dynamic param = null, bool buffered = true) => _connection.Query(sql, param as object, Transaction, buffered, _commandTimeout); /// /// Queries the current database for a single record. /// /// The type to return. /// The SQL to execute. /// The parameters to use. /// An enumerable of for the rows fetched. public T QueryFirstOrDefault(string sql, dynamic param = null) => _connection.QueryFirstOrDefault(sql, param as object, Transaction, _commandTimeout); /// /// Perform a multi-mapping query with 2 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. /// The second type in the recordset. /// The combined type to return. /// The SQL to execute for this query. /// The function to map row types to the return type. /// The parameters to use for this query. /// The transaction to use for this query. /// Whether to buffer the results in memory. /// The field we should split and read the second object from (default: "Id"). /// Number of seconds before command execution timeout. /// An enumerable of . public IEnumerable Query(string sql, Func map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null) => _connection.Query(sql, map, param as object, transaction, buffered, splitOn, commandTimeout); /// /// Perform a multi-mapping query with 3 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. /// The second type in the recordset. /// The third type in the recordset. /// The combined type to return. /// The SQL to execute for this query. /// The function to map row types to the return type. /// The parameters to use for this query. /// The transaction to use for this query. /// Whether to buffer the results in memory. /// The field we should split and read the second object from (default: "Id"). /// Number of seconds before command execution timeout. /// An enumerable of . public IEnumerable Query(string sql, Func map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null) => _connection.Query(sql, map, param as object, transaction, buffered, splitOn, commandTimeout); /// /// Perform a multi-mapping query with 4 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. /// The second type in the recordset. /// The third type in the recordset. /// The fourth type in the recordset. /// The combined type to return. /// The SQL to execute for this query. /// The function to map row types to the return type. /// The parameters to use for this query. /// The transaction to use for this query. /// Whether to buffer the results in memory. /// The field we should split and read the second object from (default: "Id"). /// Number of seconds before command execution timeout. /// An enumerable of . public IEnumerable Query(string sql, Func map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null) => _connection.Query(sql, map, param as object, transaction, buffered, splitOn, commandTimeout); /// /// Perform a multi-mapping query with 5 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. /// The second type in the recordset. /// The third type in the recordset. /// The fourth type in the recordset. /// The fifth type in the recordset. /// The combined type to return. /// The SQL to execute for this query. /// The function to map row types to the return type. /// The parameters to use for this query. /// The transaction to use for this query. /// Whether to buffer the results in memory. /// The field we should split and read the second object from (default: "Id"). /// Number of seconds before command execution timeout. /// An enumerable of . public IEnumerable Query(string sql, Func map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null) => _connection.Query(sql, map, param as object, transaction, buffered, splitOn, commandTimeout); /// /// Return a sequence of dynamic objects with properties matching the columns /// /// The SQL to execute. /// The parameters to use. /// Whether the results should be buffered in memory. /// Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object> public IEnumerable Query(string sql, dynamic param = null, bool buffered = true) => _connection.Query(sql, param as object, Transaction, buffered); /// /// Execute a command that returns multiple result sets, and access each in turn. /// /// The SQL to execute for this query. /// The parameters to use for this query. /// The transaction to use for this query. /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? public SqlMapper.GridReader QueryMultiple(string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => SqlMapper.QueryMultiple(_connection, sql, param, transaction, commandTimeout, commandType); /// /// Disposes the current database, rolling back current transactions. /// public virtual void Dispose() { var connection = _connection; if (connection.State != ConnectionState.Closed) { _connection = null; Transaction = null; connection?.Close(); } GC.SuppressFinalize(this); } } } ================================================ FILE: Dapper.Rainbow/IgnorePropertyAttribute.cs ================================================ using System; namespace Dapper { /// /// Specifies whether a property should be ignored for database operations. /// [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] public class IgnorePropertyAttribute : Attribute { /// /// Specifies whether a property should be ignored for database operations. /// /// Whether to ignore this property. public IgnorePropertyAttribute(bool ignore) { Value = ignore; } /// /// Whether to ignore this property. /// public bool Value { get; set; } } } ================================================ FILE: Dapper.Rainbow/Snapshotter.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Reflection.Emit; namespace Dapper { /// /// Snapshots an object for comparison later. /// public static class Snapshotter { /// /// Starts the snapshot of an object by making a copy of its current state. /// /// The type of object to snapshot. /// The object to snapshot. /// The snapshot of the object. public static Snapshot Start(T obj) { return new Snapshot(obj); } /// /// A snapshot of an object's state. /// /// public class Snapshot { private static Func cloner; private static Func> differ; private readonly T memberWiseClone; private readonly T trackedObject; /// /// Creates a snapshot from an object. /// /// The original object to snapshot. public Snapshot(T original) { memberWiseClone = Clone(original); trackedObject = original; } /// /// A holder for listing new values of changes fields and properties. /// public class Change { /// /// The name of the field or property that changed. /// public string Name { get; set; } /// /// The new value of the field or property. /// public object NewValue { get; set; } } /// /// Does a diff between the original object and the current state. /// /// The list of the fields changes in the object. public DynamicParameters Diff() { return Diff(memberWiseClone, trackedObject); } private static T Clone(T myObject) { cloner ??= GenerateCloner(); return cloner(myObject); } private static DynamicParameters Diff(T original, T current) { var dm = new DynamicParameters(); differ ??= GenerateDiffer(); foreach (var pair in differ(original, current)) { dm.Add(pair.Name, pair.NewValue); } return dm; } private static List RelevantProperties() { return typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) .Where(p => p.GetSetMethod(true) != null && p.GetGetMethod(true) != null && (p.PropertyType == typeof(string) || p.PropertyType.IsValueType || (p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))) ).ToList(); } private static bool AreEqual(U first, U second) { if (EqualityComparer.Default.Equals(first, default) && EqualityComparer.Default.Equals(second, default)) return true; if (EqualityComparer.Default.Equals(first, default)) return false; return first.Equals(second); } private static Func> GenerateDiffer() { var dm = new DynamicMethod("DoDiff", typeof(List), new[] { typeof(T), typeof(T) }, true); var il = dm.GetILGenerator(); // change list var list = il.DeclareLocal(typeof(List)); var change = il.DeclareLocal(typeof(Change)); var boxed = il.DeclareLocal(typeof(object)); // boxed change il.Emit(OpCodes.Newobj, typeof(List).GetConstructor(Type.EmptyTypes)); // [list] il.Emit(OpCodes.Stloc, list); foreach (var prop in RelevantProperties()) { // [] il.Emit(OpCodes.Ldarg_0); // [original] il.Emit(OpCodes.Callvirt, prop.GetGetMethod(true)); // [original prop val] il.Emit(OpCodes.Ldarg_1); // [original prop val, current] il.Emit(OpCodes.Callvirt, prop.GetGetMethod(true)); // [original prop val, current prop val] il.Emit(OpCodes.Dup); // [original prop val, current prop val, current prop val] if (prop.PropertyType != typeof(string)) { il.Emit(OpCodes.Box, prop.PropertyType); // [original prop val, current prop val, current prop val boxed] } il.Emit(OpCodes.Stloc, boxed); // [original prop val, current prop val] il.EmitCall(OpCodes.Call, typeof(Snapshot).GetMethod(nameof(AreEqual), BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod(new Type[] { prop.PropertyType }), null); // [result] Label skip = il.DefineLabel(); il.Emit(OpCodes.Brtrue_S, skip); // [] il.Emit(OpCodes.Newobj, typeof(Change).GetConstructor(Type.EmptyTypes)); // [change] il.Emit(OpCodes.Dup); // [change,change] il.Emit(OpCodes.Stloc, change); // [change] il.Emit(OpCodes.Ldstr, prop.Name); // [change, name] il.Emit(OpCodes.Callvirt, typeof(Change).GetMethod("set_Name")); // [] il.Emit(OpCodes.Ldloc, change); // [change] il.Emit(OpCodes.Ldloc, boxed); // [change, boxed] il.Emit(OpCodes.Callvirt, typeof(Change).GetMethod("set_NewValue")); // [] il.Emit(OpCodes.Ldloc, list); // [change list] il.Emit(OpCodes.Ldloc, change); // [change list, change] il.Emit(OpCodes.Callvirt, typeof(List).GetMethod("Add")); // [] il.MarkLabel(skip); } il.Emit(OpCodes.Ldloc, list); // [change list] il.Emit(OpCodes.Ret); return (Func>)dm.CreateDelegate(typeof(Func>)); } // adapted from https://stackoverflow.com/a/966466/17174 private static Func GenerateCloner() { var dm = new DynamicMethod("DoClone", typeof(T), new Type[] { typeof(T) }, true); var ctor = typeof(T).GetConstructor(Type.EmptyTypes); var il = dm.GetILGenerator(); var typed = il.DeclareLocal(typeof(T)); il.Emit(OpCodes.Newobj, ctor); il.Emit(OpCodes.Stloc, typed); foreach (var prop in RelevantProperties()) { il.Emit(OpCodes.Ldloc, typed); // [clone] il.Emit(OpCodes.Ldarg_0); // [clone, source] il.Emit(OpCodes.Callvirt, prop.GetGetMethod(true)); // [clone, source val] il.Emit(OpCodes.Callvirt, prop.GetSetMethod(true)); // [] } // Load new constructed obj on eval stack -> 1 item on stack il.Emit(OpCodes.Ldloc, typed); // Return constructed object. --> 0 items on stack il.Emit(OpCodes.Ret); var myExec = dm.CreateDelegate(typeof(Func)); return (Func)myExec; } } } } ================================================ FILE: Dapper.Rainbow/SqlCompactDatabase.cs ================================================ using System; using System.Collections.Generic; using System.Data.Common; using System.Linq; namespace Dapper { /// /// A SQL Compact specific implementation. /// /// The type of database. public abstract class SqlCompactDatabase : Database where TDatabase : Database, new() { /// /// A SQL Compact specific table, which handles the syntax correctly across operations. /// /// The type in the table. public class SqlCompactTable : Table { /// /// Creates a table for a SQL Compact database. /// /// /// public SqlCompactTable(Database database, string likelyTableName) : base(database, likelyTableName) { } /// /// Insert a row into the db /// /// Either DynamicParameters or an anonymous type or concrete type /// public override int? Insert(dynamic data) { var o = (object)data; List paramNames = GetParamNames(o); paramNames.Remove("Id"); string cols = string.Join(",", paramNames); string colsParams = string.Join(",", paramNames.Select(p => "@" + p)); var sql = "insert " + TableName + " (" + cols + ") values (" + colsParams + ")"; if (database.Execute(sql, o) != 1) { return null; } return (int)database.Query("SELECT @@IDENTITY AS LastInsertedId").Single(); } } /// /// Initializes the databases. /// /// The connection to use. /// The newly created database. public static TDatabase Init(DbConnection connection) { var db = new TDatabase(); db.InitDatabase(connection, 0); return db; } internal override Action CreateTableConstructorForTable() { return CreateTableConstructor(typeof(SqlCompactTable<>)); } } } ================================================ FILE: Dapper.Rainbow/readme.md ================================================ # Using Dapper.Rainbow in C# for CRUD Operations This guide outlines how to use `Dapper.Rainbow` in C# for CRUD operations. ## 1. Setting Up Add Dapper and Dapper.Rainbow to your project via NuGet: ```powershell Install-Package Dapper -Version x.x.x Install-Package Dapper.Rainbow -Version x.x.x ``` *Replace `x.x.x` with the latest version numbers.* ## 2. Database Setup and Requirements For `Dapper.Rainbow` to function correctly, ensure each table has a primary key column named `Id`. Example `Users` table schema: ```sql CREATE TABLE Users ( Id INT IDENTITY(1,1) PRIMARY KEY, Name VARCHAR(100), Email VARCHAR(100) ); ``` ## 3. Establishing Database Connection Open a connection to your database: ```csharp using System.Data.SqlClient; var connectionString = "your_connection_string_here"; using var connection = new SqlConnection(connectionString); connection.Open(); // Open the connection ``` ## 4. Defining Your Database Context Define a class for your database context: ```csharp using Dapper; using System.Data; public class MyDatabase : Database { public Table Users { get; set; } } public class User { public int Id { get; set; } public string Name { get; set; } public string Email { get; set; } } ``` ## 5. Performing CRUD Operations ### Insert ```csharp var db = new MyDatabase { Connection = connection }; var newUser = new User { Name = "John Doe", Email = "john.doe@example.com" }; var insertedUser = db.Users.Insert(newUser); ``` ### Select Fetch users by ID or all users: ```csharp var user = db.Users.Get(id); // Single user by ID var users = connection.Query("SELECT * FROM Users"); // All users ``` ### Update ```csharp var userToUpdate = db.Users.Get(id); userToUpdate.Email = "new.email@example.com"; db.Users.Update(userToUpdate); ``` ### Delete ```csharp db.Users.Delete(id); ``` ## 6. Working with Foreign Keys Example schema for a `Posts` table with a foreign key to `Users`: ```sql CREATE TABLE Posts ( Id INT IDENTITY(1,1) PRIMARY KEY, UserId INT, Content VARCHAR(255), FOREIGN KEY (UserId) REFERENCES Users(Id) ); ``` Inserting a parent (`User`) and a child (`Post`) row: ```csharp var newUser = new User { Name = "Jane Doe", Email = "jane.doe@example.com" }; var userId = db.Users.Insert(newUser); var newPost = new Post { UserId = userId, Content = "Hello, World!" }; db.Connection.Insert(newPost); // Using Dapper for the child table ``` ================================================ FILE: Dapper.SqlBuilder/Dapper.SqlBuilder.csproj ================================================  Dapper.SqlBuilder orm;sql;micro-orm;query;sql-builder Dapper SqlBuilder component The Dapper SqlBuilder component, for building SQL queries dynamically. Sam Saffron, Johan Danforth net461;netstandard2.0;net8.0 false false enable all runtime; build; native; contentfiles; analyzers ================================================ FILE: Dapper.SqlBuilder/PublicAPI.Shipped.txt ================================================ #nullable enable Dapper.SqlBuilder Dapper.SqlBuilder.AddClause(string! name, string! sql, object? parameters, string! joiner, string! prefix = "", string! postfix = "", bool isInclusive = false) -> Dapper.SqlBuilder! Dapper.SqlBuilder.AddParameters(dynamic! parameters) -> Dapper.SqlBuilder! Dapper.SqlBuilder.AddTemplate(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder.Template! Dapper.SqlBuilder.GroupBy(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder! Dapper.SqlBuilder.Having(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder! Dapper.SqlBuilder.InnerJoin(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder! Dapper.SqlBuilder.Intersect(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder! Dapper.SqlBuilder.Join(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder! Dapper.SqlBuilder.LeftJoin(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder! Dapper.SqlBuilder.OrderBy(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder! Dapper.SqlBuilder.OrWhere(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder! Dapper.SqlBuilder.RightJoin(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder! Dapper.SqlBuilder.Select(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder! Dapper.SqlBuilder.Set(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder! Dapper.SqlBuilder.SqlBuilder() -> void Dapper.SqlBuilder.Template Dapper.SqlBuilder.Template.Parameters.get -> object? Dapper.SqlBuilder.Template.RawSql.get -> string! Dapper.SqlBuilder.Template.Template(Dapper.SqlBuilder! builder, string! sql, dynamic? parameters) -> void Dapper.SqlBuilder.Where(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder! ================================================ FILE: Dapper.SqlBuilder/PublicAPI.Unshipped.txt ================================================ #nullable enable ================================================ FILE: Dapper.SqlBuilder/Readme.md ================================================ Dapper.SqlBuilder - a simple sql formatter for .Net ======================================== [![Build status](https://ci.appveyor.com/api/projects/status/1w448i6nfxd14w75?svg=true)](https://ci.appveyor.com/project/StackExchange/dapper-SqlBuilder) Packages -------- MyGet Pre-release feed: https://www.myget.org/gallery/dapper | Package | NuGet Stable | NuGet Pre-release | Downloads | MyGet | | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | | [Dapper.SqlBuilder](https://www.nuget.org/packages/Dapper.SqlBuilder/) | [![Dapper.SqlBuilder](https://img.shields.io/nuget/v/Dapper.SqlBuilder.svg)](https://www.nuget.org/packages/Dapper.SqlBuilder/) | [![Dapper.SqlBuilder](https://img.shields.io/nuget/vpre/Dapper.SqlBuilder.svg)](https://www.nuget.org/packages/Dapper.SqlBuilder/) | [![Dapper.SqlBuilder](https://img.shields.io/nuget/dt/Dapper.SqlBuilder.svg)](https://www.nuget.org/packages/Dapper.SqlBuilder/) | [![Dapper.SqlBuilder MyGet](https://img.shields.io/myget/dapper/vpre/Dapper.SqlBuilder.svg)](https://www.myget.org/feed/dapper/package/nuget/Dapper.SqlBuilder) | Features -------- Dapper.SqlBuilder contains a number of helper methods for generating sql. The list of extension methods in Dapper.SqlBuilder right now are: ```csharp SqlBuilder AddParameters(dynamic parameters); SqlBuilder Select(string sql, dynamic parameters = null); SqlBuilder Where(string sql, dynamic parameters = null); SqlBuilder OrWhere(string sql, dynamic parameters = null); SqlBuilder OrderBy(string sql, dynamic parameters = null); SqlBuilder GroupBy(string sql, dynamic parameters = null); SqlBuilder Having(string sql, dynamic parameters = null); SqlBuilder Set(string sql, dynamic parameters = null); SqlBuilder Join(string sql, dynamic parameters = null); SqlBuilder InnerJoin(string sql, dynamic parameters = null); SqlBuilder LeftJoin(string sql, dynamic parameters = null); SqlBuilder RightJoin(string sql, dynamic parameters = null); SqlBuilder Intersect(string sql, dynamic parameters = null); ``` Template -------- SqlBuilder allows you to generate N SQL templates from a composed query, it can easily format sql when you are attaching parameters and how, e.g: ```csharp var builder = new SqlBuilder() .Where("a = @a", new { a = 1 }) .Where("b = @b", new { b = 2 }) .OrderBy("a") .OrderBy("b"); var counter = builder.AddTemplate("select count(*) from table /**where**/"); var selector = builder.AddTemplate("select * from table /**where**/ /**orderby**/"); var count = cnn.Query(counter.RawSql, counter.Parameters).Single(); var rows = cnn.Query(selector.RawSql, selector.Parameters); ``` it's same as ```csharp var count = cnn.Query("select count(*) from table where a = @a and b = @b", new { a = 1, b = 1 }); var rows = cnn.Query("select * from table where a = @a and b = @b order by a, b", new { a = 1, b = 1 }); ``` Dynamic Filter Paging Example ---------- ```csharp var builder = new SqlBuilder(); var selectTemplate = builder.AddTemplate(@"select X.* from ( select us.*, ROW_NUMBER() OVER (/**orderby**/) AS RowNumber from Users us /**where**/ ) as X where RowNumber between @start and @finish", new { start, finish }); var countTemplate = builder.AddTemplate(@"select count(*) from Users /**where**/"); if (userId.HasValue()) builder.Where($"t.userId = @{nameof(userId)}", new { userId }); if (isCancel) builder.Where($"t.isCancel = @{nameof(isCancel)}", new { isCancel }); builder.OrderBy(string.Format("t.id {0}", orderDesc ? "desc" : "asc")); var users = conn.Query(selectTemplate.RawSql, selectTemplate.Parameters); var count = conn.ExecuteScalar(countTemplate.RawSql, countTemplate.Parameters); //..etc.. ``` Limitations and caveats -------- ### Combining the Where and OrWhere methods The OrWhere method currently groups all `and` and `or` clauses by type, then join the groups with `and` or `or` depending on the first call. This may result in possibly unexpected outcomes. See also [issue 647](https://github.com/DapperLib/Dapper/issues/647). #### Example Where first When providing the following clauses ```csharp sql.Where("a = @a1"); sql.OrWhere("b = @b1"); sql.Where("a = @a2"); sql.OrWhere("b = @b2"); ``` SqlBuilder will generate sql ```sql a = @a1 AND a = @a2 AND ( b = @b1 OR b = @b2 ) ``` and not say ```sql a = @a1 OR b = @b1 AND a = @a2 OR b = @b2 ``` #### Example OrWhere first When providing the following clauses ```csharp sql.OrWhere("b = @b1"); sql.Where("a = @a1"); sql.OrWhere("b = @b2"); sql.Where("a = @a2"); ``` SqlBuilder will generate sql ```sql a = @a1 OR a = @a2 OR ( b = @b1 OR b = @b2 ) ``` ================================================ FILE: Dapper.SqlBuilder/SqlBuilder.cs ================================================ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text.RegularExpressions; namespace Dapper { public class SqlBuilder { private readonly Dictionary _data = new Dictionary(); private int _seq; private class Clause { public Clause(string sql, object? parameters, bool isInclusive) { Sql = sql; Parameters = parameters; IsInclusive = isInclusive; } public string Sql { get; } public object? Parameters { get; } public bool IsInclusive { get; } } private class Clauses : List { private readonly string _joiner, _prefix, _postfix; public Clauses(string joiner, string prefix = "", string postfix = "") { _joiner = joiner; _prefix = prefix; _postfix = postfix; } public string ResolveClauses(DynamicParameters p) { foreach (var item in this) { p.AddDynamicParams(item.Parameters); } return this.Any(a => a.IsInclusive) ? _prefix + string.Join(_joiner, this.Where(a => !a.IsInclusive) .Select(c => c.Sql) .Union(new[] { " ( " + string.Join(" OR ", this.Where(a => a.IsInclusive).Select(c => c.Sql).ToArray()) + " ) " }).ToArray()) + _postfix : _prefix + string.Join(_joiner, this.Select(c => c.Sql).ToArray()) + _postfix; } } public class Template { private readonly string _sql; private readonly SqlBuilder _builder; private readonly object? _initParams; private int _dataSeq = -1; // Unresolved public Template(SqlBuilder builder, string sql, dynamic? parameters) { _initParams = parameters; _sql = sql; _builder = builder; } private static readonly Regex _regex = new Regex(@"\/\*\*.+?\*\*\/", RegexOptions.Compiled | RegexOptions.Multiline); private void ResolveSql() { if (_dataSeq != _builder._seq) { var p = new DynamicParameters(_initParams); rawSql = _sql; foreach (var pair in _builder._data) { rawSql = rawSql.Replace("/**" + pair.Key + "**/", pair.Value.ResolveClauses(p)); } parameters = p; // replace all that is left with empty rawSql = _regex.Replace(rawSql, ""); _dataSeq = _builder._seq; } } private string? rawSql; private object? parameters; public string RawSql { get { ResolveSql(); return rawSql!; } } public object? Parameters { get { ResolveSql(); return parameters; } } } public Template AddTemplate(string sql, dynamic? parameters = null) => new Template(this, sql, (object?)parameters); protected SqlBuilder AddClause(string name, string sql, object? parameters, string joiner, string prefix = "", string postfix = "", bool isInclusive = false) { if (!_data.TryGetValue(name, out var clauses)) { clauses = new Clauses(joiner, prefix, postfix); _data[name] = clauses; } clauses.Add(new Clause(sql, parameters, isInclusive)); _seq++; return this; } public SqlBuilder Intersect(string sql, dynamic? parameters = null) => AddClause("intersect", sql, (object?)parameters, "\nINTERSECT\n ", "\n ", "\n", false); public SqlBuilder InnerJoin(string sql, dynamic? parameters = null) => AddClause("innerjoin", sql, (object?)parameters, "\nINNER JOIN ", "\nINNER JOIN ", "\n", false); public SqlBuilder LeftJoin(string sql, dynamic? parameters = null) => AddClause("leftjoin", sql, (object?)parameters, "\nLEFT JOIN ", "\nLEFT JOIN ", "\n", false); public SqlBuilder RightJoin(string sql, dynamic? parameters = null) => AddClause("rightjoin", sql, (object?)parameters, "\nRIGHT JOIN ", "\nRIGHT JOIN ", "\n", false); public SqlBuilder Where(string sql, dynamic? parameters = null) => AddClause("where", sql, (object?)parameters, " AND ", "WHERE ", "\n", false); public SqlBuilder OrWhere(string sql, dynamic? parameters = null) => AddClause("where", sql, (object?)parameters, " OR ", "WHERE ", "\n", true); public SqlBuilder OrderBy(string sql, dynamic? parameters = null) => AddClause("orderby", sql, (object?)parameters, " , ", "ORDER BY ", "\n", false); public SqlBuilder Select(string sql, dynamic? parameters = null) => AddClause("select", sql, (object?)parameters, " , ", "", "\n", false); public SqlBuilder AddParameters(dynamic parameters) => AddClause("--parameters", "", (object?)parameters, "", "", "", false); public SqlBuilder Join(string sql, dynamic? parameters = null) => AddClause("join", sql, (object?)parameters, "\nJOIN ", "\nJOIN ", "\n", false); public SqlBuilder GroupBy(string sql, dynamic? parameters = null) => AddClause("groupby", sql, (object?)parameters, " , ", "\nGROUP BY ", "\n", false); public SqlBuilder Having(string sql, dynamic? parameters = null) => AddClause("having", sql, (object?)parameters, "\nAND ", "HAVING ", "\n", false); public SqlBuilder Set(string sql, dynamic? parameters = null) => AddClause("set", sql, (object?)parameters, " , ", "SET ", "\n", false); } } ================================================ FILE: Dapper.StrongName/Dapper.StrongName.csproj ================================================  Dapper.StrongName orm;sql;micro-orm Dapper (Strong Named) A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc. Major Sponsor: Dapper Plus from ZZZ Projects. Sam Saffron;Marc Gravell;Nick Craver net461;netstandard2.0;net8.0;net10.0 true true enable true $(DefineConstants);STRONG_NAME ================================================ FILE: Dapper.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.7.33906.173 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A34907DF-958A-4E4C-8491-84CF303FD13E}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig appveyor.yml = appveyor.yml Build.csproj = Build.csproj build.ps1 = build.ps1 Dapper.png = Dapper.png Directory.Build.props = Directory.Build.props Directory.Packages.props = Directory.Packages.props global.json = global.json docs\index.md = docs\index.md License.txt = License.txt .github\workflows\main.yml = .github\workflows\main.yml NonCLA.md = NonCLA.md nuget.config = nuget.config Readme.md = Readme.md version.json = version.json EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper", "Dapper\Dapper.csproj", "{FAC24C3F-68F9-4247-A4B9-21D487E99275}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.StrongName", "Dapper.StrongName\Dapper.StrongName.csproj", "{549C51A1-222B-4E12-96F1-3AEFF45A7709}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Tests", "tests\Dapper.Tests\Dapper.Tests.csproj", "{052C0817-DB26-4925-8929-8C5E42D148D5}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.EntityFramework", "Dapper.EntityFramework\Dapper.EntityFramework.csproj", "{BE401F7B-8611-4A1E-AEAA-5CB700128C16}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.SqlBuilder", "Dapper.SqlBuilder\Dapper.SqlBuilder.csproj", "{196928F0-7052-4585-90E8-817BD720F5E3}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Rainbow", "Dapper.Rainbow\Dapper.Rainbow.csproj", "{8A74F0B6-188F-45D2-8A4B-51E4F211805A}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4E956F6B-6BD8-46F5-BC85-49292FF8F9AB}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{568BD46C-1C65-4D44-870C-12CD72563262}" ProjectSection(SolutionItems) = preProject tests\Directory.Build.props = tests\Directory.Build.props tests\Directory.Build.targets = tests\Directory.Build.targets tests\docker-compose.yml = tests\docker-compose.yml EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.EntityFramework.StrongName", "Dapper.EntityFramework.StrongName\Dapper.EntityFramework.StrongName.csproj", "{39D3EEB6-9C05-4F4A-8C01-7B209742A7EB}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Tests.Performance", "benchmarks\Dapper.Tests.Performance\Dapper.Tests.Performance.csproj", "{F017075A-2969-4A8E-8971-26F154EB420F}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.ProviderTools", "Dapper.ProviderTools\Dapper.ProviderTools.csproj", "{B06DB435-0C74-4BD3-BC97-52AF7CF9916B}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{9D960D4D-80A2-4DAC-B386-8F4235EC73E6}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "docs", "docs\docs.csproj", "{C2F722AC-B2D4-4E97-AF8E-C036B3D9211F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {FAC24C3F-68F9-4247-A4B9-21D487E99275}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FAC24C3F-68F9-4247-A4B9-21D487E99275}.Debug|Any CPU.Build.0 = Debug|Any CPU {FAC24C3F-68F9-4247-A4B9-21D487E99275}.Release|Any CPU.ActiveCfg = Release|Any CPU {FAC24C3F-68F9-4247-A4B9-21D487E99275}.Release|Any CPU.Build.0 = Release|Any CPU {549C51A1-222B-4E12-96F1-3AEFF45A7709}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {549C51A1-222B-4E12-96F1-3AEFF45A7709}.Debug|Any CPU.Build.0 = Debug|Any CPU {549C51A1-222B-4E12-96F1-3AEFF45A7709}.Release|Any CPU.ActiveCfg = Release|Any CPU {549C51A1-222B-4E12-96F1-3AEFF45A7709}.Release|Any CPU.Build.0 = Release|Any CPU {052C0817-DB26-4925-8929-8C5E42D148D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {052C0817-DB26-4925-8929-8C5E42D148D5}.Debug|Any CPU.Build.0 = Debug|Any CPU {052C0817-DB26-4925-8929-8C5E42D148D5}.Release|Any CPU.ActiveCfg = Release|Any CPU {052C0817-DB26-4925-8929-8C5E42D148D5}.Release|Any CPU.Build.0 = Release|Any CPU {BE401F7B-8611-4A1E-AEAA-5CB700128C16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BE401F7B-8611-4A1E-AEAA-5CB700128C16}.Debug|Any CPU.Build.0 = Debug|Any CPU {BE401F7B-8611-4A1E-AEAA-5CB700128C16}.Release|Any CPU.ActiveCfg = Release|Any CPU {BE401F7B-8611-4A1E-AEAA-5CB700128C16}.Release|Any CPU.Build.0 = Release|Any CPU {196928F0-7052-4585-90E8-817BD720F5E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {196928F0-7052-4585-90E8-817BD720F5E3}.Debug|Any CPU.Build.0 = Debug|Any CPU {196928F0-7052-4585-90E8-817BD720F5E3}.Release|Any CPU.ActiveCfg = Release|Any CPU {196928F0-7052-4585-90E8-817BD720F5E3}.Release|Any CPU.Build.0 = Release|Any CPU {8A74F0B6-188F-45D2-8A4B-51E4F211805A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8A74F0B6-188F-45D2-8A4B-51E4F211805A}.Debug|Any CPU.Build.0 = Debug|Any CPU {8A74F0B6-188F-45D2-8A4B-51E4F211805A}.Release|Any CPU.ActiveCfg = Release|Any CPU {8A74F0B6-188F-45D2-8A4B-51E4F211805A}.Release|Any CPU.Build.0 = Release|Any CPU {39D3EEB6-9C05-4F4A-8C01-7B209742A7EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {39D3EEB6-9C05-4F4A-8C01-7B209742A7EB}.Debug|Any CPU.Build.0 = Debug|Any CPU {39D3EEB6-9C05-4F4A-8C01-7B209742A7EB}.Release|Any CPU.ActiveCfg = Release|Any CPU {39D3EEB6-9C05-4F4A-8C01-7B209742A7EB}.Release|Any CPU.Build.0 = Release|Any CPU {F017075A-2969-4A8E-8971-26F154EB420F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F017075A-2969-4A8E-8971-26F154EB420F}.Debug|Any CPU.Build.0 = Debug|Any CPU {F017075A-2969-4A8E-8971-26F154EB420F}.Release|Any CPU.ActiveCfg = Release|Any CPU {F017075A-2969-4A8E-8971-26F154EB420F}.Release|Any CPU.Build.0 = Release|Any CPU {B06DB435-0C74-4BD3-BC97-52AF7CF9916B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B06DB435-0C74-4BD3-BC97-52AF7CF9916B}.Debug|Any CPU.Build.0 = Debug|Any CPU {B06DB435-0C74-4BD3-BC97-52AF7CF9916B}.Release|Any CPU.ActiveCfg = Release|Any CPU {B06DB435-0C74-4BD3-BC97-52AF7CF9916B}.Release|Any CPU.Build.0 = Release|Any CPU {C2F722AC-B2D4-4E97-AF8E-C036B3D9211F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C2F722AC-B2D4-4E97-AF8E-C036B3D9211F}.Debug|Any CPU.Build.0 = Debug|Any CPU {C2F722AC-B2D4-4E97-AF8E-C036B3D9211F}.Release|Any CPU.ActiveCfg = Release|Any CPU {C2F722AC-B2D4-4E97-AF8E-C036B3D9211F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {FAC24C3F-68F9-4247-A4B9-21D487E99275} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} {549C51A1-222B-4E12-96F1-3AEFF45A7709} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} {052C0817-DB26-4925-8929-8C5E42D148D5} = {568BD46C-1C65-4D44-870C-12CD72563262} {BE401F7B-8611-4A1E-AEAA-5CB700128C16} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} {196928F0-7052-4585-90E8-817BD720F5E3} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} {8A74F0B6-188F-45D2-8A4B-51E4F211805A} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} {39D3EEB6-9C05-4F4A-8C01-7B209742A7EB} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} {F017075A-2969-4A8E-8971-26F154EB420F} = {568BD46C-1C65-4D44-870C-12CD72563262} {B06DB435-0C74-4BD3-BC97-52AF7CF9916B} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} {C2F722AC-B2D4-4E97-AF8E-C036B3D9211F} = {9D960D4D-80A2-4DAC-B386-8F4235EC73E6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {928A4226-96F3-409A-8A83-9E7444488710} EndGlobalSection EndGlobal ================================================ FILE: Dapper.sln.DotSettings ================================================  CE SQ SQL TVP ================================================ FILE: Directory.Build.props ================================================ 2019 Stack Exchange, Inc. true true ../Dapper.snk $(AssemblyName) https://dapperlib.github.io/Dapper/ https://github.com/DapperLib/Dapper Apache-2.0 Dapper.png git https://github.com/DapperLib/Dapper false $(NOWARN);IDE0056;IDE0057;IDE0079 true embedded en-US false true true 13 false true readme.md true true true true ================================================ FILE: Directory.Build.targets ================================================ $([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)')) ================================================ FILE: Directory.Packages.props ================================================ ================================================ FILE: License.txt ================================================ The Dapper library and tools are licenced under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 The Dapper logo is copyright Marc Gravell 2021 onwards; it is fine to use the Dapper logo when referencing the Dapper library and utilities, but the Dapper logo (including derivatives) must not be used in a way that misrepresents an external product or library as being affiliated or endorsed with Dapper. For example, you must not use the Dapper logo as the package icon on your own external tool (even if it uses Dapper internally), without written permission. If in doubt: ask. ================================================ FILE: NonCLA.md ================================================ # Dapper - the "Non CLA" CLA IANAL. YANAL (I hope). Let's keep this simple; if you want to contribute to Dapper, great! Let's just check a few things for the record. By accepting this agreement, you're saying: ## You're allowed to contribute this code The code needs to be yours (without being owned by some employer, etc), or contributed with the owner's knowledge and permission, or in accordance with a licence that clearly allows the code to be reused in line with this project's licence (http://www.apache.org/licenses/LICENSE-2.0), and in that last case: a cross-reference back to the origin would be nice. (for the pedants: "licence" and "license" to be used interchangeably here; language is fun) ## It isn't "your code" any more Contributed code belongs to the Dapper project, not you the contributor. That means Dapper can use it, not use it, remove it, or edit it in any way - *even changing the spacing and variable names*. I know: shocking. ## No backsies Contributing code to Dapper is permanent; you can't later demand that we remove your code because... well, anything. Even if one of the maintainers wears socks that look *really ugly*. ## For gratis Contributing code to Dapper gets you the bugfix or feature or whatever that you want; you have our thanks and appreciation, but unless we've agreed something separately: that's it. No turning up unannounced at the tri-annual BBQ, or demanding... again, anything. --- That's it. Basically "don't make our life harder". Thanks! ================================================ FILE: Readme.md ================================================ Dapper - a simple object mapper for .Net ======================================== [![Build status](https://ci.appveyor.com/api/projects/status/8rbgoxqio76ynj4h?svg=true)](https://ci.appveyor.com/project/StackExchange/dapper) Release Notes ------------- Located at [https://github.com/DapperLib/Dapper/releases](https://github.com/DapperLib/Dapper/releases/) Packages -------- MyGet Pre-release feed: https://www.myget.org/gallery/dapper | Package | NuGet Stable | NuGet Pre-release | Downloads | MyGet | | ------- | ------------ | ----------------- | --------- | ----- | | [Dapper](https://www.nuget.org/packages/Dapper/) | [![Dapper](https://img.shields.io/nuget/v/Dapper.svg)](https://www.nuget.org/packages/Dapper/) | [![Dapper](https://img.shields.io/nuget/vpre/Dapper.svg)](https://www.nuget.org/packages/Dapper/) | [![Dapper](https://img.shields.io/nuget/dt/Dapper.svg)](https://www.nuget.org/packages/Dapper/) | [![Dapper MyGet](https://img.shields.io/myget/dapper/vpre/Dapper.svg)](https://www.myget.org/feed/dapper/package/nuget/Dapper) | | [Dapper.EntityFramework](https://www.nuget.org/packages/Dapper.EntityFramework/) | [![Dapper.EntityFramework](https://img.shields.io/nuget/v/Dapper.EntityFramework.svg)](https://www.nuget.org/packages/Dapper.EntityFramework/) | [![Dapper.EntityFramework](https://img.shields.io/nuget/vpre/Dapper.EntityFramework.svg)](https://www.nuget.org/packages/Dapper.EntityFramework/) | [![Dapper.EntityFramework](https://img.shields.io/nuget/dt/Dapper.EntityFramework.svg)](https://www.nuget.org/packages/Dapper.EntityFramework/) | [![Dapper.EntityFramework MyGet](https://img.shields.io/myget/dapper/vpre/Dapper.EntityFramework.svg)](https://www.myget.org/feed/dapper/package/nuget/Dapper.EntityFramework) | | [Dapper.EntityFramework.StrongName](https://www.nuget.org/packages/Dapper.EntityFramework.StrongName/) | [![Dapper.EntityFramework.StrongName](https://img.shields.io/nuget/v/Dapper.EntityFramework.StrongName.svg)](https://www.nuget.org/packages/Dapper.EntityFramework.StrongName/) | [![Dapper.EntityFramework.StrongName](https://img.shields.io/nuget/vpre/Dapper.EntityFramework.StrongName.svg)](https://www.nuget.org/packages/Dapper.EntityFramework.StrongName/) | [![Dapper.EntityFramework.StrongName](https://img.shields.io/nuget/dt/Dapper.EntityFramework.StrongName.svg)](https://www.nuget.org/packages/Dapper.EntityFramework.StrongName/) | [![Dapper.EntityFramework.StrongName MyGet](https://img.shields.io/myget/dapper/vpre/Dapper.EntityFramework.StrongName.svg)](https://www.myget.org/feed/dapper/package/nuget/Dapper.EntityFramework.StrongName) | | [Dapper.Rainbow](https://www.nuget.org/packages/Dapper.Rainbow/) | [![Dapper.Rainbow](https://img.shields.io/nuget/v/Dapper.Rainbow.svg)](https://www.nuget.org/packages/Dapper.Rainbow/) | [![Dapper.Rainbow](https://img.shields.io/nuget/vpre/Dapper.Rainbow.svg)](https://www.nuget.org/packages/Dapper.Rainbow/) | [![Dapper.Rainbow](https://img.shields.io/nuget/dt/Dapper.Rainbow.svg)](https://www.nuget.org/packages/Dapper.Rainbow/) | [![Dapper.Rainbow MyGet](https://img.shields.io/myget/dapper/vpre/Dapper.Rainbow.svg)](https://www.myget.org/feed/dapper/package/nuget/Dapper.Rainbow) | | [Dapper.SqlBuilder](https://www.nuget.org/packages/Dapper.SqlBuilder/) | [![Dapper.SqlBuilder](https://img.shields.io/nuget/v/Dapper.SqlBuilder.svg)](https://www.nuget.org/packages/Dapper.SqlBuilder/) | [![Dapper.SqlBuilder](https://img.shields.io/nuget/vpre/Dapper.SqlBuilder.svg)](https://www.nuget.org/packages/Dapper.SqlBuilder/) | [![Dapper.SqlBuilder](https://img.shields.io/nuget/dt/Dapper.SqlBuilder.svg)](https://www.nuget.org/packages/Dapper.SqlBuilder/) | [![Dapper.SqlBuilder MyGet](https://img.shields.io/myget/dapper/vpre/Dapper.SqlBuilder.svg)](https://www.myget.org/feed/dapper/package/nuget/Dapper.SqlBuilder) | | [Dapper.StrongName](https://www.nuget.org/packages/Dapper.StrongName/) | [![Dapper.StrongName](https://img.shields.io/nuget/v/Dapper.StrongName.svg)](https://www.nuget.org/packages/Dapper.StrongName/) | [![Dapper.StrongName](https://img.shields.io/nuget/vpre/Dapper.StrongName.svg)](https://www.nuget.org/packages/Dapper.StrongName/) | [![Dapper.StrongName](https://img.shields.io/nuget/dt/Dapper.StrongName.svg)](https://www.nuget.org/packages/Dapper.StrongName/) | [![Dapper.StrongName MyGet](https://img.shields.io/myget/dapper/vpre/Dapper.StrongName.svg)](https://www.myget.org/feed/dapper/package/nuget/Dapper.StrongName) | Package Purposes: * Dapper * The core library * Dapper.EntityFramework * Extension handlers for EntityFramework * Dapper.EntityFramework.StrongName * Extension handlers for EntityFramework * Dapper.Rainbow * Micro-ORM implemented on Dapper, provides CRUD helpers ([readme](Dapper.Rainbow/readme.md)) * Dapper.SqlBuilder * Component for building SQL queries dynamically and composably Sponsors -------- Dapper was originally developed for and by Stack Overflow, but is F/OSS. Sponsorship is welcome and invited - see the sponsor link at the top of the page. A huge thanks to everyone (individuals or organisations) who have sponsored Dapper, but a massive thanks in particular to: - [Dapper Plus](https://dapper-plus.net/) is a major sponsor and is proud to contribute to the development of Dapper ([read more](https://dapperlib.github.io/Dapper/dapperplus)) - [AWS](https://github.com/aws) who sponsored Dapper from Oct 2023 via the [.NET on AWS Open Source Software Fund](https://github.com/aws/dotnet-foss) Dapper Plus logo Features -------- Dapper is a [NuGet library](https://www.nuget.org/packages/Dapper) that you can add in to your project that will enhance your ADO.NET connections via extension methods on your `DbConnection` instance. This provides a simple and efficient API for invoking SQL, with support for both synchronous and asynchronous data access, and allows both buffered and non-buffered queries. It provides multiple helpers, but the key APIs are: ``` csharp // insert/update/delete etc var count = connection.Execute(sql [, args]); // multi-row query IEnumerable rows = connection.Query(sql [, args]); // single-row query ({Single|First}[OrDefault]) T row = connection.QuerySingle(sql [, args]); ``` where `args` can be (among other things): - a simple POCO (including anonyomous types) for named parameters - a `Dictionary` - a `DynamicParameters` instance Execute a query and map it to a list of typed objects ------------------------------------------------------- ``` csharp public class Dog { public int? Age { get; set; } public Guid Id { get; set; } public string Name { get; set; } public float? Weight { get; set; } public int IgnoredProperty { get { return 1; } } } var guid = Guid.NewGuid(); var dog = connection.Query("select Age = @Age, Id = @Id", new { Age = (int?)null, Id = guid }); Assert.Equal(1,dog.Count()); Assert.Null(dog.First().Age); Assert.Equal(guid, dog.First().Id); ``` Execute a query and map it to a list of dynamic objects ------------------------------------------------------- This method will execute SQL and return a dynamic list. Example usage: ```csharp var rows = connection.Query("select 1 A, 2 B union all select 3, 4").AsList(); Assert.Equal(1, (int)rows[0].A); Assert.Equal(2, (int)rows[0].B); Assert.Equal(3, (int)rows[1].A); Assert.Equal(4, (int)rows[1].B); ``` Execute a Command that returns no results ----------------------------------------- Example usage: ```csharp var count = connection.Execute(@" set nocount on create table #t(i int) set nocount off insert #t select @a a union all select @b set nocount on drop table #t", new {a=1, b=2 }); Assert.Equal(2, count); ``` Execute a Command multiple times -------------------------------- The same signature also allows you to conveniently and efficiently execute a command multiple times (for example to bulk-load data) Example usage: ```csharp var count = connection.Execute(@"insert MyTable(colA, colB) values (@a, @b)", new[] { new { a=1, b=1 }, new { a=2, b=2 }, new { a=3, b=3 } } ); Assert.Equal(3, count); // 3 rows inserted: "1,1", "2,2" and "3,3" ``` Another example usage when you _already_ have an existing collection: ```csharp var foos = new List { { new Foo { A = 1, B = 1 } } { new Foo { A = 2, B = 2 } } { new Foo { A = 3, B = 3 } } }; var count = connection.Execute(@"insert MyTable(colA, colB) values (@a, @b)", foos); Assert.Equal(foos.Count, count); ``` This works for any parameter that implements `IEnumerable` for some T. Performance ----------- A key feature of Dapper is performance. The following metrics show how long it takes to execute a `SELECT` statement against a DB (in various config, each labeled) and map the data returned to objects. The benchmarks can be found in [Dapper.Tests.Performance](https://github.com/DapperLib/Dapper/tree/main/benchmarks/Dapper.Tests.Performance) (contributions welcome!) and can be run via: ```bash dotnet run --project .\benchmarks\Dapper.Tests.Performance\ -c Release -f net8.0 -- -f * --join ``` Output from the latest run is: ``` ini BenchmarkDotNet v0.13.7, Windows 10 (10.0.19045.3693/22H2/2022Update) Intel Core i7-3630QM CPU 2.40GHz (Ivy Bridge), 1 CPU, 8 logical and 4 physical cores .NET SDK 8.0.100 [Host] : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX ShortRun : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX ``` | ORM | Method | Return | Mean | StdDev | Error | Gen0 | Gen1 | Gen2 | Allocated | |-------------------- |------------------------------- |------------- |----------:|----------:|----------:|--------:|-------:|-------:|----------:| | Dapper cache impact | ExecuteParameters_Cache | Void | 96.75 us | 0.668 us | 1.010 us | 0.6250 | - | - | 2184 B | | Dapper cache impact | QueryFirstParameters_Cache | Void | 96.86 us | 0.493 us | 0.746 us | 0.8750 | - | - | 2824 B | | Hand Coded | SqlCommand | Post | 119.70 us | 0.706 us | 1.067 us | 1.3750 | 1.0000 | 0.1250 | 7584 B | | Hand Coded | DataTable | dynamic | 126.64 us | 1.239 us | 1.873 us | 3.0000 | - | - | 9576 B | | SqlMarshal | SqlCommand | Post | 132.36 us | 1.008 us | 1.523 us | 2.0000 | 1.0000 | 0.2500 | 11529 B | | Dapper | QueryFirstOrDefault | Post | 133.73 us | 1.301 us | 2.186 us | 1.7500 | 1.5000 | - | 11608 B | | Mighty | Query | dynamic | 133.92 us | 1.075 us | 1.806 us | 2.0000 | 1.7500 | - | 12710 B | | LINQ to DB | Query | Post | 134.24 us | 1.068 us | 1.614 us | 1.7500 | 1.2500 | - | 10904 B | | RepoDB | ExecuteQuery | Post | 135.83 us | 1.839 us | 3.091 us | 1.7500 | 1.5000 | - | 11649 B | | Dapper | 'Query (buffered)' | Post | 136.14 us | 1.755 us | 2.653 us | 2.0000 | 1.5000 | - | 11888 B | | Mighty | Query | Post | 137.96 us | 1.485 us | 2.244 us | 2.2500 | 1.2500 | - | 12201 B | | Dapper | QueryFirstOrDefault | dynamic | 139.04 us | 1.507 us | 2.279 us | 3.5000 | - | - | 11648 B | | Mighty | SingleFromQuery | dynamic | 139.74 us | 2.521 us | 3.811 us | 2.0000 | 1.7500 | - | 12710 B | | Dapper | 'Query (buffered)' | dynamic | 140.13 us | 1.382 us | 2.090 us | 2.0000 | 1.5000 | - | 11968 B | | ServiceStack | SingleById | Post | 140.76 us | 1.147 us | 2.192 us | 2.5000 | 1.2500 | 0.2500 | 15248 B | | Dapper | 'Contrib Get' | Post | 141.09 us | 1.394 us | 2.108 us | 2.0000 | 1.5000 | - | 12440 B | | Mighty | SingleFromQuery | Post | 141.17 us | 1.941 us | 2.935 us | 1.7500 | 1.5000 | - | 12201 B | | Massive | 'Query (dynamic)' | dynamic | 142.01 us | 4.957 us | 7.494 us | 2.0000 | 1.5000 | - | 12342 B | | LINQ to DB | 'First (Compiled)' | Post | 144.59 us | 1.295 us | 1.958 us | 1.7500 | 1.5000 | - | 12128 B | | RepoDB | QueryField | Post | 148.31 us | 1.742 us | 2.633 us | 2.0000 | 1.5000 | 0.5000 | 13938 B | | Norm | 'Read<> (tuples)' | ValueTuple`8 | 148.58 us | 2.172 us | 3.283 us | 2.0000 | 1.7500 | - | 12745 B | | Norm | 'Read<()> (named tuples)' | ValueTuple`8 | 150.60 us | 0.658 us | 1.106 us | 2.2500 | 2.0000 | 1.2500 | 14562 B | | RepoDB | Query | Post | 152.34 us | 2.164 us | 3.271 us | 2.2500 | 1.5000 | 0.2500 | 14106 B | | RepoDB | QueryDynamic | Post | 154.15 us | 4.108 us | 6.210 us | 2.2500 | 1.7500 | 0.5000 | 13930 B | | RepoDB | QueryWhere | Post | 155.90 us | 1.953 us | 3.282 us | 2.5000 | 0.5000 | - | 14858 B | | Dapper cache impact | ExecuteNoParameters_NoCache | Void | 162.35 us | 1.584 us | 2.394 us | - | - | - | 760 B | | Dapper cache impact | ExecuteNoParameters_Cache | Void | 162.42 us | 2.740 us | 4.142 us | - | - | - | 760 B | | Dapper cache impact | QueryFirstNoParameters_Cache | Void | 164.35 us | 1.206 us | 1.824 us | 0.2500 | - | - | 1520 B | | DevExpress.XPO | FindObject | Post | 165.87 us | 1.012 us | 1.934 us | 8.5000 | - | - | 28099 B | | Dapper cache impact | QueryFirstNoParameters_NoCache | Void | 173.87 us | 1.178 us | 1.781 us | 0.5000 | - | - | 1576 B | | LINQ to DB | First | Post | 175.21 us | 2.292 us | 3.851 us | 2.0000 | 0.5000 | - | 14041 B | | EF 6 | SqlQuery | Post | 175.36 us | 2.259 us | 3.415 us | 4.0000 | 0.7500 | - | 24209 B | | Norm | 'Read<> (class)' | Post | 186.37 us | 1.305 us | 2.496 us | 3.0000 | 0.5000 | - | 17579 B | | DevExpress.XPO | GetObjectByKey | Post | 186.78 us | 3.407 us | 5.151 us | 4.5000 | 1.0000 | - | 30114 B | | Dapper | 'Query (unbuffered)' | dynamic | 194.62 us | 1.335 us | 2.019 us | 1.7500 | 1.5000 | - | 12048 B | | Dapper | 'Query (unbuffered)' | Post | 195.01 us | 0.888 us | 1.343 us | 2.0000 | 1.5000 | - | 12008 B | | DevExpress.XPO | Query | Post | 199.46 us | 5.500 us | 9.243 us | 10.0000 | - | - | 32083 B | | Belgrade | FirstOrDefault | Task`1 | 228.70 us | 2.181 us | 3.665 us | 4.5000 | 0.5000 | - | 20555 B | | EF Core | 'First (Compiled)' | Post | 265.45 us | 17.745 us | 26.828 us | 2.0000 | - | - | 7521 B | | NHibernate | Get | Post | 276.02 us | 8.029 us | 12.139 us | 6.5000 | 1.0000 | - | 29885 B | | NHibernate | HQL | Post | 277.74 us | 13.032 us | 19.703 us | 8.0000 | 1.0000 | - | 31886 B | | NHibernate | Criteria | Post | 300.22 us | 14.908 us | 28.504 us | 13.0000 | 1.0000 | - | 57562 B | | EF 6 | First | Post | 310.55 us | 27.254 us | 45.799 us | 13.0000 | - | - | 43309 B | | EF Core | First | Post | 317.12 us | 1.354 us | 2.046 us | 3.5000 | - | - | 11306 B | | EF Core | SqlQuery | Post | 322.34 us | 23.990 us | 40.314 us | 5.0000 | - | - | 18195 B | | NHibernate | SQL | Post | 325.54 us | 3.937 us | 7.527 us | 22.0000 | 1.0000 | - | 80007 B | | EF 6 | 'First (No Tracking)' | Post | 331.14 us | 27.760 us | 46.649 us | 12.0000 | 1.0000 | - | 50237 B | | EF Core | 'First (No Tracking)' | Post | 337.82 us | 27.814 us | 46.740 us | 3.0000 | 1.0000 | - | 17986 B | | NHibernate | LINQ | Post | 604.74 us | 5.549 us | 10.610 us | 10.0000 | - | - | 46061 B | | Dapper cache impact | ExecuteParameters_NoCache | Void | 623.42 us | 3.978 us | 6.684 us | 3.0000 | 2.0000 | - | 10001 B | | Dapper cache impact | QueryFirstParameters_NoCache | Void | 630.77 us | 3.027 us | 4.576 us | 3.0000 | 2.0000 | - | 10640 B | Feel free to submit patches that include other ORMs - when running benchmarks, be sure to compile in Release and not attach a debugger (Ctrl+F5). Alternatively, you might prefer Frans Bouma's [RawDataAccessBencher](https://github.com/FransBouma/RawDataAccessBencher) test suite or [OrmBenchmark](https://github.com/InfoTechBridge/OrmBenchmark). Parameterized queries --------------------- Parameters are usually passed in as anonymous classes. This allows you to name your parameters easily and gives you the ability to simply cut-and-paste SQL snippets and run them in your db platform's Query analyzer. ```csharp new {A = 1, B = "b"} // A will be mapped to the param @A, B to the param @B ``` Parameters can also be built up dynamically using the DynamicParameters class. This allows for building a dynamic SQL statement while still using parameters for safety and performance. ```csharp var sqlPredicates = new List(); var queryParams = new DynamicParameters(); if (boolExpression) { sqlPredicates.Add("column1 = @param1"); queryParams.Add("param1", dynamicValue1, System.Data.DbType.Guid); } else { sqlPredicates.Add("column2 = @param2"); queryParams.Add("param2", dynamicValue2, System.Data.DbType.String); } ``` DynamicParameters also supports copying multiple parameters from existing objects of different types. ```csharp var queryParams = new DynamicParameters(objectOfType1); queryParams.AddDynamicParams(objectOfType2); ``` When an object that implements the `IDynamicParameters` interface passed into `Execute` or `Query` functions, parameter values will be extracted via this interface. Obviously, the most likely object class to use for this purpose would be the built-in `DynamicParameters` class. List Support ------------ Dapper allows you to pass in `IEnumerable` and will automatically parameterize your query. For example: ```csharp connection.Query("select * from (select 1 as Id union all select 2 union all select 3) as X where Id in @Ids", new { Ids = new int[] { 1, 2, 3 } }); ``` Will be translated to: ```csharp select * from (select 1 as Id union all select 2 union all select 3) as X where Id in (@Ids1, @Ids2, @Ids3)" // @Ids1 = 1 , @Ids2 = 2 , @Ids2 = 3 ``` Literal replacements ------------ Dapper supports literal replacements for bool and numeric types. ```csharp connection.Query("select * from User where UserTypeId = {=Admin}", new { UserTypeId.Admin }); ``` The literal replacement is not sent as a parameter; this allows better plans and filtered index usage but should usually be used sparingly and after testing. This feature is particularly useful when the value being injected is actually a fixed value (for example, a fixed "category id", "status code" or "region" that is specific to the query). For *live* data where you are considering literals, you might *also* want to consider and test provider-specific query hints like [`OPTIMIZE FOR UNKNOWN`](https://blogs.msdn.microsoft.com/sqlprogrammability/2008/11/26/optimize-for-unknown-a-little-known-sql-server-2008-feature/) with regular parameters. Buffered vs Unbuffered readers --------------------- Dapper's default behavior is to execute your SQL and buffer the entire reader on return. This is ideal in most cases as it minimizes shared locks in the db and cuts down on db network time. However when executing huge queries you may need to minimize memory footprint and only load objects as needed. To do so pass, `buffered: false` into the `Query` method. Multi Mapping --------------------- Dapper allows you to map a single row to multiple objects. This is a key feature if you want to avoid extraneous querying and eager load associations. Example: Consider 2 classes: `Post` and `User` ```csharp class Post { public int Id { get; set; } public string Title { get; set; } public string Content { get; set; } public User Owner { get; set; } } class User { public int Id { get; set; } public string Name { get; set; } } ``` Now let us say that we want to map a query that joins both the posts and the users table. Until now if we needed to combine the result of 2 queries, we'd need a new object to express it but it makes more sense in this case to put the `User` object inside the `Post` object. This is the use case for multi mapping. You tell dapper that the query returns a `Post` and a `User` object and then give it a function describing what you want to do with each of the rows containing both a `Post` and a `User` object. In our case, we want to take the user object and put it inside the post object. So we write the function: ```csharp (post, user) => { post.Owner = user; return post; } ``` The 3 type arguments to the `Query` method specify what objects dapper should use to deserialize the row and what is going to be returned. We're going to interpret both rows as a combination of `Post` and `User` and we're returning back a `Post` object. Hence the type declaration becomes ```csharp ``` Everything put together, looks like this: ```csharp var sql = @"select * from #Posts p left join #Users u on u.Id = p.OwnerId Order by p.Id"; var data = connection.Query(sql, (post, user) => { post.Owner = user; return post;}); var post = data.First(); Assert.Equal("Sams Post1", post.Content); Assert.Equal(1, post.Id); Assert.Equal("Sam", post.Owner.Name); Assert.Equal(99, post.Owner.Id); ``` Dapper is able to split the returned row by making an assumption that your Id columns are named `Id` or `id`. If your primary key is different or you would like to split the row at a point other than `Id`, use the optional `splitOn` parameter. Multiple Results --------------------- Dapper allows you to process multiple result grids in a single query. Example: ```csharp var sql = @" select * from Customers where CustomerId = @id select * from Orders where CustomerId = @id select * from Returns where CustomerId = @id"; using (var multi = connection.QueryMultiple(sql, new {id=selectedId})) { var customer = multi.Read().Single(); var orders = multi.Read().ToList(); var returns = multi.Read().ToList(); ... } ``` Stored Procedures --------------------- Dapper fully supports stored procs: ```csharp var user = cnn.Query("spGetUser", new {Id = 1}, commandType: CommandType.StoredProcedure).SingleOrDefault(); ``` If you want something more fancy, you can do: ```csharp var p = new DynamicParameters(); p.Add("@a", 11); p.Add("@b", dbType: DbType.Int32, direction: ParameterDirection.Output); p.Add("@c", dbType: DbType.Int32, direction: ParameterDirection.ReturnValue); cnn.Execute("spMagicProc", p, commandType: CommandType.StoredProcedure); int b = p.Get("@b"); int c = p.Get("@c"); ``` Ansi Strings and varchar --------------------- Dapper supports varchar params, if you are executing a where clause on a varchar column using a param be sure to pass it in this way: ```csharp Query("select * from Thing where Name = @Name", new {Name = new DbString { Value = "abcde", IsFixedLength = true, Length = 10, IsAnsi = true }}); ``` On SQL Server it is crucial to use the unicode when querying unicode and ANSI when querying non unicode. Type Switching Per Row --------------------- Usually you'll want to treat all rows from a given table as the same data type. However, there are some circumstances where it's useful to be able to parse different rows as different data types. This is where `IDataReader.GetRowParser` comes in handy. Imagine you have a database table named "Shapes" with the columns: `Id`, `Type`, and `Data`, and you want to parse its rows into `Circle`, `Square`, or `Triangle` objects based on the value of the Type column. ```csharp var shapes = new List(); using (var reader = connection.ExecuteReader("select * from Shapes")) { // Generate a row parser for each type you expect. // The generic type is what the parser will return. // The argument (typeof(*)) is the concrete type to parse. var circleParser = reader.GetRowParser(typeof(Circle)); var squareParser = reader.GetRowParser(typeof(Square)); var triangleParser = reader.GetRowParser(typeof(Triangle)); var typeColumnIndex = reader.GetOrdinal("Type"); while (reader.Read()) { IShape shape; var type = (ShapeType)reader.GetInt32(typeColumnIndex); switch (type) { case ShapeType.Circle: shape = circleParser(reader); break; case ShapeType.Square: shape = squareParser(reader); break; case ShapeType.Triangle: shape = triangleParser(reader); break; default: throw new NotImplementedException(); } shapes.Add(shape); } } ``` User Defined Variables in MySQL/MariaDB --------------------- In order to use Non-parameter SQL variables with MySql Connector, you have to add the following option to your connection string: `Allow User Variables=True` Make sure you don't provide Dapper with a property to map. Limitations and caveats --------------------- Dapper caches information about every query it runs, this allows it to materialize objects quickly and process parameters quickly. The current implementation caches this information in a `ConcurrentDictionary` object. Statements that are only used once are routinely flushed from this cache. Still, if you are generating SQL strings on the fly without using parameters it is possible you may hit memory issues. Dapper's simplicity means that many features that ORMs ship with are stripped out. It worries about the 95% scenario, and gives you the tools you need most of the time. It doesn't attempt to solve every problem. Will Dapper work with my DB provider? --------------------- Dapper has no DB specific implementation details, it works across all .NET ADO providers including [SQLite](https://www.sqlite.org/), SQL CE, Firebird, Oracle, MariaDB, MySQL, PostgreSQL and SQL Server. Do you have a comprehensive list of examples? --------------------- Dapper has a comprehensive test suite in the [test project](https://github.com/DapperLib/Dapper/tree/main/tests/Dapper.Tests). Who is using this? --------------------- Dapper is in production use at [Stack Overflow](https://stackoverflow.com/). ================================================ FILE: appveyor.yml ================================================ image: Visual Studio 2022 skip_branch_with_pr: true skip_tags: true skip_commits: files: - '**/*.md' environment: Appveyor: true # Postgres POSTGRES_PATH: C:\Program Files\PostgreSQL\16 PGUSER: postgres PGPASSWORD: Password12! POSTGRES_ENV_POSTGRES_USER: postgres POSTGRES_ENV_POSTGRES_PASSWORD: Password12! POSTGRES_ENV_POSTGRES_DB: test # MySQL MYSQL_PATH: C:\Program Files\MySQL\MySQL Server 8.0 MYSQL_PWD: Password12! MYSQL_ENV_MYSQL_USER: root MYSQL_ENV_MYSQL_PASSWORD: Password12! MYSQL_ENV_MYSQL_DATABASE: test # Connection strings for tests: MySqlConnectionString: Server=localhost;Database=test;Uid=root;Pwd=Password12! OLEDBConnectionString: Provider=SQLOLEDB;Data Source=(local)\SQL2019;Initial Catalog=tempdb;User Id=sa;Password=Password12! PostgesConnectionString: Server=localhost;Port=5432;User Id=postgres;Password=Password12!;Database=test SqlServerConnectionString: Server=(local)\SQL2019;Database=tempdb;User ID=sa;Password=Password12! init: - git config --global core.autocrlf input - SET PATH=%POSTGRES_PATH%\bin;%MYSQL_PATH%\bin;%PATH% - net start MSSQL$SQL2019 - net start postgresql-x64-16 - ps: Start-Service MySQL80 nuget: disable_publish_on_pr: true before_build: # Postgres - createdb test # MySQL - '"C:\Program Files\MySQL\MySQL Server 8.0\bin\mysql" -e "create database test;" --user=root' build_script: # Our stuff - ps: .\build.ps1 -PullRequestNumber "$env:APPVEYOR_PULL_REQUEST_NUMBER" -CreatePackages $true test: off artifacts: - path: .\.nupkgs\*.nupkg deploy: - provider: NuGet server: https://www.myget.org/F/stackoverflow/api/v2 on: branch: main api_key: secure: P/UHxq2DEs0GI1SoDXDesHjRVsSVgdywz5vmsnhFQQY5aJgO3kP+QfhwfhXz19Rw symbol_server: https://www.myget.org/F/stackoverflow/symbols/api/v2/package - provider: NuGet server: https://www.myget.org/F/dapper/api/v2 on: branch: main api_key: secure: PV7ERAltWWLhy7AT2h+Vb5c1BM9/WFgvggb+rKyQ8hDg3fYqpZauYdidOOgt2lp4 symbol_server: https://www.myget.org/F/dapper/api/v2/package ================================================ FILE: benchmarks/Dapper.Tests.Performance/Benchmarks.Belgrade.cs ================================================ using BenchmarkDotNet.Attributes; using Belgrade.SqlClient.SqlDb; using Belgrade.SqlClient; using System.ComponentModel; using System.Threading.Tasks; namespace Dapper.Tests.Performance { [Description("Belgrade")] public class BelgradeBenchmarks : BenchmarkBase { private QueryMapper _mapper; [GlobalSetup] public void Setup() { BaseSetup(); _mapper = new QueryMapper(ConnectionString); } [Benchmark(Description = "FirstOrDefault")] public Task FirstOrDefault() { Step(); return _mapper.Sql("SELECT TOP 1 * FROM Posts WHERE Id = @Id").Param("Id", i).FirstOrDefault( reader => new Post { Id = reader.GetInt32(0), Text = reader.GetString(1), CreationDate = reader.GetDateTime(2), LastChangeDate = reader.GetDateTime(3), Counter1 = reader.IsDBNull(4) ? null : (int?)reader.GetInt32(4), Counter2 = reader.IsDBNull(5) ? null : (int?)reader.GetInt32(5), Counter3 = reader.IsDBNull(6) ? null : (int?)reader.GetInt32(6), Counter4 = reader.IsDBNull(7) ? null : (int?)reader.GetInt32(7), Counter5 = reader.IsDBNull(8) ? null : (int?)reader.GetInt32(8), Counter6 = reader.IsDBNull(9) ? null : (int?)reader.GetInt32(9), Counter7 = reader.IsDBNull(10) ? null : (int?)reader.GetInt32(10), Counter8 = reader.IsDBNull(11) ? null : (int?)reader.GetInt32(11), Counter9 = reader.IsDBNull(12) ? null : (int?)reader.GetInt32(12), }); } } } ================================================ FILE: benchmarks/Dapper.Tests.Performance/Benchmarks.Dapper.cs ================================================ using BenchmarkDotNet.Attributes; using Dapper.Contrib.Extensions; using System.ComponentModel; using System.Linq; namespace Dapper.Tests.Performance { [Description("Dapper")] public class DapperBenchmarks : BenchmarkBase { [GlobalSetup] public void Setup() { BaseSetup(); } [Benchmark(Description = "Query (buffered)")] public Post QueryBuffered() { Step(); return _connection.Query("select * from Posts where Id = @Id", new { Id = i }, buffered: true).First(); } [Benchmark(Description = "Query (buffered)")] public dynamic QueryBufferedDynamic() { Step(); return _connection.Query("select * from Posts where Id = @Id", new { Id = i }, buffered: true).First(); } [Benchmark(Description = "Query (unbuffered)")] public Post QueryUnbuffered() { Step(); return _connection.Query("select * from Posts where Id = @Id", new { Id = i }, buffered: false).First(); } [Benchmark(Description = "Query (unbuffered)")] public dynamic QueryUnbufferedDynamic() { Step(); return _connection.Query("select * from Posts where Id = @Id", new { Id = i }, buffered: false).First(); } [Benchmark(Description = "QueryFirstOrDefault")] public Post QueryFirstOrDefault() { Step(); return _connection.QueryFirstOrDefault("select * from Posts where Id = @Id", new { Id = i }); } [Benchmark(Description = "QueryFirstOrDefault")] public dynamic QueryFirstOrDefaultDynamic() { Step(); return _connection.QueryFirstOrDefault("select * from Posts where Id = @Id", new { Id = i }).First(); } [Benchmark(Description = "Contrib Get")] public Post ContribGet() { Step(); return _connection.Get(i); } } } ================================================ FILE: benchmarks/Dapper.Tests.Performance/Benchmarks.Dashing.cs ================================================ #if NET4X using System.ComponentModel; using BenchmarkDotNet.Attributes; using Dapper.Tests.Performance.Dashing; using Dashing; namespace Dapper.Tests.Performance { [Description("Dashing")] public class DashingBenchmarks : BenchmarkBase { private ISession Session; [GlobalSetup] public void Setup() { BaseSetup(); var configuration = new DashingConfiguration(); var database = new SqlDatabase(configuration, ConnectionString); Session = database.BeginTransactionLessSession(_connection); } // This needs love to be compatible with current SDKs (weaving doesn't work and shouldn't be used here anyway (competition). // I'll file an issue with Dashing to see if someone can help me out here since I can't figure out from the docs how to // make it work correctly. //[Benchmark(Description = "Get")] public Dashing.Post Get() { Step(); return Session.Get(i); } } } #endif ================================================ FILE: benchmarks/Dapper.Tests.Performance/Benchmarks.EntityFramework.cs ================================================ using BenchmarkDotNet.Attributes; using System.ComponentModel; using System.Linq; namespace Dapper.Tests.Performance { [Description("EF 6")] public class EF6Benchmarks : BenchmarkBase { private EntityFramework.EFContext Context; [GlobalSetup] public void Setup() { BaseSetup(); Context = new EntityFramework.EFContext(_connection); } [Benchmark(Description = "First")] public Post First() { Step(); return Context.Posts.First(p => p.Id == i); } [Benchmark(Description = "SqlQuery")] public Post SqlQuery() { Step(); return Context.Database.SqlQuery("select * from Posts where Id = {0}", i).First(); } [Benchmark(Description = "First (No Tracking)")] public Post NoTracking() { Step(); return Context.Posts.AsNoTracking().First(p => p.Id == i); } } } ================================================ FILE: benchmarks/Dapper.Tests.Performance/Benchmarks.EntityFrameworkCore.cs ================================================ using BenchmarkDotNet.Attributes; using Dapper.Tests.Performance.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using System; using System.ComponentModel; using System.Linq; namespace Dapper.Tests.Performance { [Description("EF Core")] public class EFCoreBenchmarks : BenchmarkBase { private EFCoreContext Context; private static readonly Func compiledQuery = EF.CompileQuery((EFCoreContext ctx, int id) => ctx.Posts.First(p => p.Id == id)); [GlobalSetup] public void Setup() { BaseSetup(); Context = new EFCoreContext(ConnectionString); } [Benchmark(Description = "First")] public Post First() { Step(); return Context.Posts.First(p => p.Id == i); } [Benchmark(Description = "First (Compiled)")] public Post Compiled() { Step(); return compiledQuery(Context, i); } [Benchmark(Description = "SqlQuery")] public Post SqlQuery() { Step(); return Context.Posts.FromSqlRaw("select * from Posts where Id = {0}", i).First(); } [Benchmark(Description = "First (No Tracking)")] public Post NoTracking() { Step(); return Context.Posts.AsNoTracking().First(p => p.Id == i); } } } ================================================ FILE: benchmarks/Dapper.Tests.Performance/Benchmarks.HandCoded.cs ================================================ using BenchmarkDotNet.Attributes; using System; using System.ComponentModel; using System.Data; using Microsoft.Data.SqlClient; namespace Dapper.Tests.Performance { [Description("Hand Coded")] public class HandCodedBenchmarks : BenchmarkBase { private SqlCommand _postCommand; private SqlParameter _idParam; private DataTable _table; [GlobalSetup] public void Setup() { BaseSetup(); _postCommand = new SqlCommand("select Top 1 * from Posts where Id = @Id", _connection); _idParam = _postCommand.Parameters.Add("@Id", SqlDbType.Int); _postCommand.Prepare(); _table = new DataTable { Columns = { {"Id", typeof (int)}, {"Text", typeof (string)}, {"CreationDate", typeof (DateTime)}, {"LastChangeDate", typeof (DateTime)}, {"Counter1", typeof (int)}, {"Counter2", typeof (int)}, {"Counter3", typeof (int)}, {"Counter4", typeof (int)}, {"Counter5", typeof (int)}, {"Counter6", typeof (int)}, {"Counter7", typeof (int)}, {"Counter8", typeof (int)}, {"Counter9", typeof (int)}, } }; } [Benchmark(Description = "SqlCommand")] public Post SqlCommand() { Step(); _idParam.Value = i; using (var reader = _postCommand.ExecuteReader(CommandBehavior.SingleResult | CommandBehavior.SingleRow)) { reader.Read(); return new Post { Id = reader.GetInt32(0), Text = reader.GetNullableString(1), CreationDate = reader.GetDateTime(2), LastChangeDate = reader.GetDateTime(3), Counter1 = reader.GetNullableValue(4), Counter2 = reader.GetNullableValue(5), Counter3 = reader.GetNullableValue(6), Counter4 = reader.GetNullableValue(7), Counter5 = reader.GetNullableValue(8), Counter6 = reader.GetNullableValue(9), Counter7 = reader.GetNullableValue(10), Counter8 = reader.GetNullableValue(11), Counter9 = reader.GetNullableValue(12) }; } } [Benchmark(Description = "DataTable")] public dynamic DataTableDynamic() { Step(); _idParam.Value = i; _table.Rows.Clear(); var values = new object[13]; using (var reader = _postCommand.ExecuteReader(CommandBehavior.SingleResult | CommandBehavior.SingleRow)) { reader.Read(); reader.GetValues(values); return _table.Rows.Add(values); } } } } ================================================ FILE: benchmarks/Dapper.Tests.Performance/Benchmarks.Linq2DB.cs ================================================ using BenchmarkDotNet.Attributes; using System; using System.Linq; using Dapper.Tests.Performance.Linq2Db; using LinqToDB; using LinqToDB.Data; using System.ComponentModel; namespace Dapper.Tests.Performance { [Description("LINQ to DB")] public class LinqToDBBenchmarks : BenchmarkBase // note To not 2 because the "2" confuses BDN CLI { private Linq2DBContext _dbContext; private static readonly Func compiledQuery = CompiledQuery.Compile((Linq2DBContext db, int id) => db.Posts.First(c => c.Id == id)); [GlobalSetup] public void Setup() { BaseSetup(); DataConnection.DefaultSettings = new Linq2DBSettings(_connection.ConnectionString); _dbContext = new Linq2DBContext(); } [Benchmark(Description = "First")] public Post First() { Step(); return _dbContext.Posts.First(p => p.Id == i); } [Benchmark(Description = "First (Compiled)")] public Post Compiled() { Step(); return compiledQuery(_dbContext, i); } [Benchmark(Description = "Query")] public Post Query() { Step(); return _dbContext.Query("select * from Posts where Id = @id", new { id = i }).First(); } } } ================================================ FILE: benchmarks/Dapper.Tests.Performance/Benchmarks.Linq2Sql.cs ================================================ #if NET4X using BenchmarkDotNet.Attributes; using Dapper.Tests.Performance.Linq2Sql; using System; using System.ComponentModel; using System.Data.Linq; using System.Linq; namespace Dapper.Tests.Performance { [Description("LINQ to SQL")] public class LinqToSqlBenchmarks : BenchmarkBase // note To not 2 because the "2" confuses BDN CLI { private DataClassesDataContext Linq2SqlContext; private static readonly Func compiledQuery = CompiledQuery.Compile((DataClassesDataContext ctx, int id) => ctx.Posts.First(p => p.Id == id)); [GlobalSetup] public void Setup() { BaseSetup(); Linq2SqlContext = new DataClassesDataContext(_connection); } [Benchmark(Description = "First")] public Linq2Sql.Post First() { Step(); return Linq2SqlContext.Posts.First(p => p.Id == i); } [Benchmark(Description = "First (Compiled)")] public Linq2Sql.Post Compiled() { Step(); return compiledQuery(Linq2SqlContext, i); } [Benchmark(Description = "ExecuteQuery")] public Post ExecuteQuery() { Step(); return Linq2SqlContext.ExecuteQuery("select * from Posts where Id = {0}", i).First(); } } } #endif ================================================ FILE: benchmarks/Dapper.Tests.Performance/Benchmarks.Massive.cs ================================================ using BenchmarkDotNet.Attributes; using Massive; using System.ComponentModel; using System.Linq; namespace Dapper.Tests.Performance { [Description("Massive")] public class MassiveBenchmarks : BenchmarkBase { private DynamicModel _model; [GlobalSetup] public void Setup() { BaseSetup(); RegisterSqlFactory(); _model = new DynamicModel(ConnectionString); } [Benchmark(Description = "Query (dynamic)")] public dynamic QueryDynamic() { Step(); return _model.Query("select * from Posts where Id = @0", _connection, i).First(); } } } ================================================ FILE: benchmarks/Dapper.Tests.Performance/Benchmarks.Mighty.cs ================================================ using BenchmarkDotNet.Attributes; using Mighty; using System.ComponentModel; using System.Linq; namespace Dapper.Tests.Performance { [Description("Mighty")] public class MightyBenchmarks : BenchmarkBase { private MightyOrm _model; private MightyOrm _dynamicModel; [GlobalSetup] public void Setup() { BaseSetup(); // Mighty needs the connection string to contain the ProviderName in addition to everything else for Reasons. // However, it appears the SQL Server driver chokes on it if it's in the full connection string, so we programatically add it here. var connectionString = $"{ConnectionStringSettings.ConnectionString};ProviderName={ConnectionStringSettings.ProviderName}"; _model = new MightyOrm(connectionString); _dynamicModel = new MightyOrm(connectionString); } [Benchmark(Description = "Query")] public Post Query() { Step(); return _model.Query("select * from Posts where Id = @0", _connection, i).First(); } [Benchmark(Description = "Query")] public dynamic QueryDynamic() { Step(); return _dynamicModel.Query("select * from Posts where Id = @0", _connection, i).First(); } [Benchmark(Description = "SingleFromQuery")] public Post SingleFromQuery() { Step(); return _model.SingleFromQuery("select * from Posts where Id = @0", _connection, i); } [Benchmark(Description = "SingleFromQuery")] public dynamic SingleFromQueryDynamic() { Step(); return _dynamicModel.SingleFromQuery("select * from Posts where Id = @0", _connection, i); } } } ================================================ FILE: benchmarks/Dapper.Tests.Performance/Benchmarks.NHibernate.cs ================================================ using BenchmarkDotNet.Attributes; using Dapper.Tests.Performance.NHibernate; using NHibernate; using NHibernate.Criterion; using NHibernate.Linq; using NHibernate.Transform; using NHibernate.Util; using System.Linq; namespace Dapper.Tests.Performance { public class NHibernateBenchmarks : BenchmarkBase { private IStatelessSession _sql, _hql, _criteria, _linq, _get; [GlobalSetup] public void Setup() { BaseSetup(); _sql = NHibernateHelper.OpenSession(); _hql = NHibernateHelper.OpenSession(); _criteria = NHibernateHelper.OpenSession(); _linq = NHibernateHelper.OpenSession(); _get = NHibernateHelper.OpenSession(); } [Benchmark(Description = "SQL")] public Post SQL() { Step(); return _sql.CreateSQLQuery("select * from Posts where Id = :id") .SetInt32("id", i) .SetResultTransformer(Transformers.AliasToBean()) .List()[0]; } [Benchmark(Description = "HQL")] public Post HQL() { Step(); return _hql.CreateQuery("from Post as p where p.Id = :id") .SetInt32("id", i) .List()[0]; } [Benchmark(Description = "Criteria")] public Post Criteria() { Step(); return _criteria.CreateCriteria() .Add(Restrictions.IdEq(i)) .List()[0]; } [Benchmark(Description = "LINQ")] public Post LINQ() { Step(); return _linq.Query().First(p => p.Id == i); } [Benchmark(Description = "Get")] public Post Get() { Step(); return _get.Get(i); } } } ================================================ FILE: benchmarks/Dapper.Tests.Performance/Benchmarks.Norm.cs ================================================ #if !NET4X using BenchmarkDotNet.Attributes; using System.ComponentModel; using System.Linq; using Norm; using System; namespace Dapper.Tests.Performance { [Description("Norm")] public class NormBenchmarks : BenchmarkBase { [GlobalSetup] public void Setup() { BaseSetup(); } [Benchmark(Description = "Read<> (class)")] public Post Read() { Step(); return _connection.Read("select * from Posts where Id = @Id", i).First(); } [Benchmark(Description = "Read<> (tuples)")] public (int, string, DateTime, DateTime, int?, int?, int?, int?, int?, int?, int?, int?) ReadSimpleValues() { Step(); return _connection.Read("select * from Posts where Id = @Id", i).First(); } [Benchmark(Description = "Read<()> (named tuples)")] public (int Id, string Text, DateTime CreationDate, DateTime LastChangeDate, int? Counter1, int? Counter2, int? Counter3, int? Counter4, int? Counter5, int? Counter6, int? Counter7, int? Counter8) ReadTuple() { Step(); return _connection.Read<(int Id, string Text, DateTime CreationDate, DateTime LastChangeDate, int? Counter1, int? Counter2, int? Counter3, int? Counter4, int? Counter5, int? Counter6, int? Counter7, int? Counter8)>("select * from Posts where Id = @Id", i).First(); } } } #endif ================================================ FILE: benchmarks/Dapper.Tests.Performance/Benchmarks.PetaPoco.cs ================================================ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; using PetaPoco; using System.ComponentModel; using System.Linq; namespace Dapper.Tests.Performance { #if !NET5_0_OR_GREATER /* System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.InvalidProgramException: Common Language Runtime detected an invalid program. at System.Reflection.Emit.DynamicMethod.CreateDelegate(Type delegateType, Object target) at PetaPoco.Database.PocoData.GetFactory[T](String key, Boolean ForceDateTimesToUtc, IDataReader r) in /_/benchmarks/Dapper.Tests.Performance/PetaPoco/PetaPoco.cs:line 1127 at PetaPoco.Database.Fetch[T](String sql, Object[] args) in /_/benchmarks/Dapper.Tests.Performance/PetaPoco/PetaPoco.cs:line 458 at Dapper.Tests.Performance.PetaPocoBenchmarks.FetchFast() in /_/benchmarks/Dapper.Tests.Performance/Benchmarks.PetaPoco.cs:line 38 at BenchmarkDotNet.Autogenerated.Runnable_42.WorkloadActionUnroll(Int64 invokeCount) in /_/benchmarks/Dapper.Tests.Performance/bin/Release/net8.0/5e0d07b1-6b4c-4578-a0eb-d46563cab999/5e0d07b1-6b4c-4578-a0eb-d46563cab999.notcs:line 49834 at BenchmarkDotNet.Engines.Engine.RunIteration(IterationData data) at BenchmarkDotNet.Engines.EngineFactory.Jit(Engine engine, Int32 jitIndex, Int32 invokeCount, Int32 unrollFactor) at BenchmarkDotNet.Engines.EngineFactory.CreateReadyToRun(EngineParameters engineParameters) at BenchmarkDotNet.Autogenerated.Runnable_42.Run(IHost host, String benchmarkName) in /_/benchmarks/Dapper.Tests.Performance/bin/Release/net8.0/5e0d07b1-6b4c-4578-a0eb-d46563cab999/5e0d07b1-6b4c-4578-a0eb-d46563cab999.notcs:line 49238 at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr) --- End of inner exception stack trace --- at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr) at System.Reflection.MethodBaseInvoker.InvokeWithFewArgs(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters) at BenchmarkDotNet.Autogenerated.UniqueProgramName.AfterAssemblyLoadingAttached(String[] args) in /_/benchmarks/Dapper.Tests.Performance/bin/Release/net8.0/5e0d07b1-6b4c-4578-a0eb-d46563cab999/5e0d07b1-6b4c-4578-a0eb-d46563cab999.notcs:line 57 */ [Description("PetaPoco")] public class PetaPocoBenchmarks : BenchmarkBase { private Database _db, _dbFast; [GlobalSetup] public void Setup() { BaseSetup(); RegisterSqlFactory(); _db = new Database(ConnectionString, "System.Data.SqlClient"); _db.OpenSharedConnection(); _dbFast = new Database(ConnectionString, "System.Data.SqlClient"); _dbFast.OpenSharedConnection(); _dbFast.EnableAutoSelect = false; _dbFast.EnableNamedParams = false; _dbFast.ForceDateTimesToUtc = false; } [Benchmark(Description = "Fetch")] public Post Fetch() { Step(); return _db.Fetch("SELECT * from Posts where Id=@0", i).First(); } [Benchmark(Description = "Fetch (Fast)")] public Post FetchFast() { Step(); return _dbFast.Fetch("SELECT * from Posts where Id=@0", i).First(); } } #endif } ================================================ FILE: benchmarks/Dapper.Tests.Performance/Benchmarks.RepoDB.cs ================================================ using System.ComponentModel; using System.Linq; using BenchmarkDotNet.Attributes; using RepoDb; using RepoDb.DbHelpers; using RepoDb.DbSettings; using RepoDb.StatementBuilders; namespace Dapper.Tests.Performance { [Description("RepoDB")] public class RepoDbBenchmarks : BenchmarkBase { [GlobalSetup] public void Setup() { BaseSetup(); GlobalConfiguration.Setup().UseSqlServer(); // We need this since benchmarks using System.Data.SqlClient var dbSetting = new SqlServerDbSetting(); DbSettingMapper .Add(dbSetting, true); DbHelperMapper .Add(new SqlServerDbHelper(), true); StatementBuilderMapper .Add(new SqlServerStatementBuilder(dbSetting), true); ClassMapper.Add("Posts"); } [Benchmark(Description = "Query")] public Post Query() { Step(); return _connection.Query(i).First(); } [Benchmark(Description = "QueryWhere")] public Post QueryWhere() { Step(); return _connection.Query(x => x.Id == i).First(); } [Benchmark(Description = "QueryDynamic")] public Post QueryDynamic() { Step(); return _connection.Query(new { Id = i }).First(); } [Benchmark(Description = "QueryField")] public Post QueryField() { Step(); return _connection.Query([new(nameof(Post.Id), i)]).First(); } [Benchmark(Description = "ExecuteQuery")] public Post ExecuteQuery() { Step(); return _connection.ExecuteQuery("select * from Posts where Id = @Id", new { Id = i }).First(); } } } ================================================ FILE: benchmarks/Dapper.Tests.Performance/Benchmarks.ServiceStack.cs ================================================ using BenchmarkDotNet.Attributes; using ServiceStack.OrmLite; using System.ComponentModel; using System.Data; namespace Dapper.Tests.Performance { [Description("ServiceStack")] public class ServiceStackBenchmarks : BenchmarkBase { private IDbConnection _db; [GlobalSetup] public void Setup() { BaseSetup(); var dbFactory = new OrmLiteConnectionFactory(ConnectionString, SqlServerDialect.Provider); _db = dbFactory.Open(); } [Benchmark(Description = "SingleById")] public Post Query() { Step(); return _db.SingleById(i); } } } ================================================ FILE: benchmarks/Dapper.Tests.Performance/Benchmarks.SqlMarshal.cs ================================================ using BenchmarkDotNet.Attributes; using System.ComponentModel; namespace Dapper.Tests.Performance { [Description("SqlMarshal")] public partial class SqlMarshalBenchmarks : BenchmarkBase { [GlobalSetup] public void Setup() { BaseSetup(); } [Benchmark(Description = "SqlCommand")] public Post SqlCommand() { Step(); return ReadPost("select Top 1 * from Posts where Id = @id", i); } [SqlMarshal("")] private partial Post ReadPost([RawSql]string sql, int id); } } ================================================ FILE: benchmarks/Dapper.Tests.Performance/Benchmarks.Susanoo.cs ================================================ #if NET4X using BenchmarkDotNet.Attributes; using Susanoo; using Susanoo.Processing; using System.ComponentModel; using System.Data; using System.Linq; namespace Dapper.Tests.Performance { [Description("Susanoo")] public class SusanooBenchmarks : BenchmarkBase { private DatabaseManager _db; private static readonly ISingleResultSetCommandProcessor _cmd = CommandManager.Instance.DefineCommand("SELECT * FROM Posts WHERE Id = @Id", CommandType.Text) .DefineResults() .Realize(); private static readonly ISingleResultSetCommandProcessor _cmdDynamic = CommandManager.Instance.DefineCommand("SELECT * FROM Posts WHERE Id = @Id", CommandType.Text) .DefineResults() .Realize(); [GlobalSetup] public void Setup() { BaseSetup(); _db = new DatabaseManager(_connection); } [Benchmark(Description = "Execute (Cache)")] public Post MappingCache() { Step(); return CommandManager.Instance.DefineCommand("SELECT * FROM Posts WHERE Id = @Id", CommandType.Text) .DefineResults() .Realize() .Execute(_db, new { Id = i }).First(); } [Benchmark(Description = "Execute (Cache)")] public dynamic MappingCacheDynamic() { Step(); return CommandManager.Instance.DefineCommand("SELECT * FROM Posts WHERE Id = @Id", CommandType.Text) .DefineResults() .Realize() .Execute(_db, new { Id = i }).First(); } [Benchmark(Description = "Execute (Static)")] public Post MappingStatic() { Step(); return _cmd.Execute(_db, new { Id = i }).First(); } [Benchmark(Description = "Execut (Static)")] public dynamic MappingStaticDynamic() { Step(); return _cmdDynamic.Execute(_db, new { Id = i }).First(); } } } #endif ================================================ FILE: benchmarks/Dapper.Tests.Performance/Benchmarks.XPO.cs ================================================ using BenchmarkDotNet.Attributes; using System; using System.Linq; using System.ComponentModel; using DevExpress.Xpo; using DevExpress.Data.Filtering; namespace Dapper.Tests.Performance { [Description("DevExpress.XPO")] public class XpoBenchmarks : BenchmarkBase { public UnitOfWork _session; [GlobalSetup] public void Setup() { BaseSetup(); IDataLayer dataLayer = XpoDefault.GetDataLayer(_connection, DevExpress.Xpo.DB.AutoCreateOption.SchemaAlreadyExists); dataLayer.Dictionary.GetDataStoreSchema(typeof(Xpo.Post)); _session = new UnitOfWork(dataLayer, dataLayer) { IdentityMapBehavior = IdentityMapBehavior.Strong }; _session.TypesManager.EnsureIsTypedObjectValid(); } [GlobalCleanup] public void Cleanup() { _session.Dispose(); } [Benchmark(Description = "GetObjectByKey")] public Xpo.Post GetObjectByKey() { Step(); return _session.GetObjectByKey(i, true); } [Benchmark(Description = "FindObject")] public Xpo.Post FindObject() { Step(); CriteriaOperator _findCriteria = new BinaryOperator() { OperatorType = BinaryOperatorType.Equal, LeftOperand = new OperandProperty("Id"), RightOperand = new ConstantValue(i) }; return _session.FindObject(_findCriteria); } [Benchmark(Description = "Query")] public Xpo.Post Query() { Step(); return _session.Query().First(p => p.Id == i); } } } ================================================ FILE: benchmarks/Dapper.Tests.Performance/Benchmarks.cs ================================================ using BenchmarkDotNet.Attributes; using System; using System.Configuration; using Microsoft.Data.SqlClient; namespace Dapper.Tests.Performance { [BenchmarkCategory("ORM")] public abstract class BenchmarkBase { protected static readonly Random _rand = new Random(); protected SqlConnection _connection; public static ConnectionStringSettings ConnectionStringSettings { get; } = ConfigurationManager.ConnectionStrings["Main"]; public static string ConnectionString { get; } = ConnectionStringSettings.ConnectionString; protected int i; protected void BaseSetup() { i = 0; _connection = new SqlConnection(ConnectionString); _connection.Open(); } protected void RegisterSqlFactory() { #if NETCOREAPP System.Data.Common.DbProviderFactories.RegisterFactory("System.Data.SqlClient", SqlClientFactory.Instance); #endif } protected void Step() { i++; if (i > 5000) i = 1; } } } ================================================ FILE: benchmarks/Dapper.Tests.Performance/Config.cs ================================================ using BenchmarkDotNet.Columns; using BenchmarkDotNet.Configs; using BenchmarkDotNet.Diagnosers; using BenchmarkDotNet.Exporters; using BenchmarkDotNet.Exporters.Csv; using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Loggers; using BenchmarkDotNet.Order; using Dapper.Tests.Performance.Helpers; namespace Dapper.Tests.Performance { public class Config : ManualConfig { public const int Iterations = 500; public Config() { AddLogger(ConsoleLogger.Default); AddExporter(CsvExporter.Default); AddExporter(MarkdownExporter.GitHub); AddExporter(HtmlExporter.Default); var md = MemoryDiagnoser.Default; AddDiagnoser(md); AddColumn(new ORMColum()); AddColumn(TargetMethodColumn.Method); AddColumn(new ReturnColum()); AddColumn(StatisticColumn.Mean); AddColumn(StatisticColumn.StdDev); AddColumn(StatisticColumn.Error); AddColumn(BaselineRatioColumn.RatioMean); AddColumnProvider(DefaultColumnProviders.Metrics); AddJob(Job.ShortRun .WithLaunchCount(1) .WithWarmupCount(2) .WithUnrollFactor(Iterations) .WithIterationCount(10) ); Orderer = new DefaultOrderer(SummaryOrderPolicy.FastestToSlowest); Options |= ConfigOptions.JoinSummary; } } } ================================================ FILE: benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj ================================================  Dapper.Tests.Performance Dapper Core Performance Suite Exe net462;net8.0 false $(NoWarn);IDE0063;IDE0034;IDE0059;IDE0060 $(DefineConstants);NET4X ================================================ FILE: benchmarks/Dapper.Tests.Performance/DapperCacheImpact.cs ================================================ using System.ComponentModel; using BenchmarkDotNet.Attributes; namespace Dapper.Tests.Performance { [Description("Dapper cache impact")] [MemoryDiagnoser] public class DapperCacheImpact : BenchmarkBase { [GlobalSetup] public void Setup() => BaseSetup(); private readonly object args = new { Id = 42, Name = "abc" }; public class Foo { public int Id { get; set; } public string Name { get; set; } } // note: custom BDN setup means [Params] is awkward; unroll manually instead [Benchmark] public void ExecuteNoParameters_Cache() => _connection.Execute(new CommandDefinition("select '42' as Id, 'abc' as Name", flags: CommandFlags.None)); [Benchmark] public void ExecuteParameters_Cache() => _connection.Execute(new CommandDefinition("select @id as Id, @name as Name", args, flags: CommandFlags.None)); [Benchmark] public void QueryFirstNoParameters_Cache() => _connection.QueryFirst(new CommandDefinition("select '42' as Id, 'abc' as Name", flags: CommandFlags.None)); [Benchmark] public void QueryFirstParameters_Cache() => _connection.QueryFirst(new CommandDefinition("select @id as Id, @name as Name", args, flags: CommandFlags.None)); [Benchmark] public void ExecuteNoParameters_NoCache() => _connection.Execute(new CommandDefinition("select '42' as Id, 'abc' as Name", flags: CommandFlags.NoCache)); [Benchmark] public void ExecuteParameters_NoCache() => _connection.Execute(new CommandDefinition("select @id as Id, @name as Name", args, flags: CommandFlags.NoCache)); [Benchmark] public void QueryFirstNoParameters_NoCache() => _connection.QueryFirst(new CommandDefinition("select '42' as Id, 'abc' as Name", flags: CommandFlags.NoCache)); [Benchmark] public void QueryFirstParameters_NoCache() => _connection.QueryFirst(new CommandDefinition("select @id as Id, @name as Name", args, flags: CommandFlags.NoCache)); } } ================================================ FILE: benchmarks/Dapper.Tests.Performance/Dashing/DashingConfiguration.cs ================================================ using Dashing.Configuration; namespace Dapper.Tests.Performance.Dashing { public class DashingConfiguration : BaseConfiguration { public DashingConfiguration() { Add(); } } } ================================================ FILE: benchmarks/Dapper.Tests.Performance/Dashing/Post.cs ================================================ using System; namespace Dapper.Tests.Performance.Dashing { public class Post { public int Id { get; set; } public string Text { get; set; } public DateTime CreationDate { get; set; } public DateTime LastChangeDate { get; set; } public int? Counter1 { get; set; } public int? Counter2 { get; set; } public int? Counter3 { get; set; } public int? Counter4 { get; set; } public int? Counter5 { get; set; } public int? Counter6 { get; set; } public int? Counter7 { get; set; } public int? Counter8 { get; set; } public int? Counter9 { get; set; } } } ================================================ FILE: benchmarks/Dapper.Tests.Performance/EntityFramework/EFContext.cs ================================================ using System.Data.Common; using System.Data.Entity; namespace Dapper.Tests.Performance.EntityFramework { public class EFContext : DbContext { public EFContext(DbConnection connection, bool owned = false) : base(connection, owned) { } public DbSet Posts { get; set; } } } ================================================ FILE: benchmarks/Dapper.Tests.Performance/EntityFrameworkCore/EFCoreContext.cs ================================================ using Microsoft.EntityFrameworkCore; namespace Dapper.Tests.Performance.EntityFrameworkCore { public class EFCoreContext : DbContext { private readonly string _connectionString; public EFCoreContext(string connectionString) { _connectionString = connectionString; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.UseSqlServer(_connectionString); public DbSet Posts { get; set; } } } ================================================ FILE: benchmarks/Dapper.Tests.Performance/Helpers/ORMColum.cs ================================================ using System.ComponentModel; using System.Reflection; using BenchmarkDotNet.Columns; using BenchmarkDotNet.Reports; using BenchmarkDotNet.Running; namespace Dapper.Tests.Performance.Helpers { public class ORMColum : IColumn { public string Id => nameof(ORMColum); public string ColumnName { get; } = "ORM"; public string Legend => "The object/relational mapper being tested"; public bool IsDefault(Summary summary, BenchmarkCase benchmarkCase) => false; public string GetValue(Summary summary, BenchmarkCase benchmarkCase) { var type = benchmarkCase.Descriptor.WorkloadMethod.DeclaringType; return type.GetCustomAttribute()?.Description ?? type.Name.Replace("Benchmarks", string.Empty); } public string GetValue(Summary summary, BenchmarkCase benchmarkCase, SummaryStyle style) => GetValue(summary, benchmarkCase); public bool IsAvailable(Summary summary) => true; public bool AlwaysShow => true; public ColumnCategory Category => ColumnCategory.Job; public int PriorityInCategory => -10; public bool IsNumeric => false; public UnitType UnitType => UnitType.Dimensionless; public override string ToString() => ColumnName; } } ================================================ FILE: benchmarks/Dapper.Tests.Performance/Helpers/ReturnColum.cs ================================================ using BenchmarkDotNet.Columns; using BenchmarkDotNet.Reports; using BenchmarkDotNet.Running; namespace Dapper.Tests.Performance.Helpers { public class ReturnColum : IColumn { public string Id => nameof(ReturnColum); public string ColumnName { get; } = "Return"; public string Legend => "The return type of the method"; public bool IsDefault(Summary summary, BenchmarkCase benchmarkCase) => false; public string GetValue(Summary summary, BenchmarkCase benchmarkCase) { var type = benchmarkCase.Descriptor.WorkloadMethod.ReturnType; return type == typeof(object) ? "dynamic" : type.Name; } public string GetValue(Summary summary, BenchmarkCase benchmarkCase, SummaryStyle style) => GetValue(summary, benchmarkCase); public bool IsAvailable(Summary summary) => true; public bool AlwaysShow => true; public ColumnCategory Category => ColumnCategory.Job; public int PriorityInCategory => 1; public bool IsNumeric => false; public UnitType UnitType => UnitType.Dimensionless; public override string ToString() => ColumnName; } } ================================================ FILE: benchmarks/Dapper.Tests.Performance/LegacyTests.cs ================================================ using System; using System.Collections.Generic; using System.Data; using Microsoft.Data.SqlClient; using System.Diagnostics; using System.Linq; using Belgrade.SqlClient; using Dapper.Contrib.Extensions; using Dapper.Tests.Performance.Dashing; using Dapper.Tests.Performance.EntityFramework; using Dapper.Tests.Performance.EntityFrameworkCore; using Dapper.Tests.Performance.NHibernate; using Dashing; using DevExpress.Xpo; using DevExpress.Data.Filtering; using Massive; using Microsoft.EntityFrameworkCore; using NHibernate.Criterion; using ServiceStack.OrmLite; using ServiceStack.OrmLite.Dapper; using System.Configuration; using System.Threading.Tasks; #if NET4X using System.Data.Linq; using Dapper.Tests.Performance.Linq2Sql; using Dapper.Tests.Performance.Xpo; using NHibernate.Linq; using Susanoo; #endif namespace Dapper.Tests.Performance { public class LegacyTests { private class Test { public Test(Action iteration, string name) { Iteration = iteration; Name = name; } public Test(Func iterationAsync, string name) { IterationAsync = iterationAsync; Name = name; } public Action Iteration { get; set; } public Func IterationAsync { get; set; } public string Name { get; set; } public Stopwatch Watch { get; set; } } private class Tests : List { public void Add(Action iteration, string name) { Add(new Test(iteration, name)); } public void AsyncAdd(Func iterationAsync, string name) { Add(new Test(iterationAsync, name)); } public async Task RunAsync(int iterations) { // warmup foreach (var test in this) { test.Iteration?.Invoke(iterations + 1); if (test.IterationAsync != null) await test.IterationAsync(iterations + 1).ConfigureAwait(false); test.Watch = new Stopwatch(); test.Watch.Reset(); } var rand = new Random(); for (int i = 1; i <= iterations; i++) { foreach (var test in this.OrderBy(ignore => rand.Next())) { test.Watch.Start(); test.Iteration?.Invoke(i); if (test.IterationAsync != null) await test.IterationAsync(i).ConfigureAwait(false); test.Watch.Stop(); } } Console.WriteLine("|Time|Framework|"); foreach (var test in this.OrderBy(t => t.Watch.ElapsedMilliseconds)) { var ms = test.Watch.ElapsedMilliseconds.ToString(); Console.Write("|"); Console.Write(ms); Program.WriteColor("ms ".PadRight(8 - ms.Length), ConsoleColor.DarkGray); Console.Write("|"); Console.Write(test.Name); Console.WriteLine("|"); } } } public static string ConnectionString { get; } = ConfigurationManager.ConnectionStrings["Main"].ConnectionString; public static SqlConnection GetOpenConnection() { var connection = new SqlConnection(ConnectionString); connection.Open(); return connection; } #if NET4X private static DataClassesDataContext GetL2SContext(SqlConnection connection) => new DataClassesDataContext(connection); #endif private static void Try(Action action, string blame) { try { action(); } catch (Exception ex) { Console.Error.WriteLine($"{blame}: {ex.Message}"); } } [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1806:Do not ignore method results", Justification = "Intentional - just make sure we have something")] public async Task RunAsync(int iterations) { using (var connection = GetOpenConnection()) { #pragma warning disable IDE0017 // Simplify object initialization #pragma warning disable RCS1121 // Use [] instead of calling 'First'. var tests = new Tests(); // Entity Framework Core Try(() => { var entityContext = new EFCoreContext(ConnectionString); tests.Add(id => entityContext.Posts.First(p => p.Id == id), "Entity Framework Core"); var entityContext2 = new EFCoreContext(ConnectionString); tests.Add(id => entityContext2.Posts.FromSqlRaw("select * from Posts where Id = {0}", id).First(), "Entity Framework Core: FromSql"); var entityContext3 = new EFCoreContext(ConnectionString); tests.Add(id => entityContext3.Posts.AsNoTracking().First(p => p.Id == id), "Entity Framework Core: No Tracking"); }, "Entity Framework Core"); // Dapper Try(() => { var mapperConnection = GetOpenConnection(); tests.Add(id => mapperConnection.Query("select * from Posts where Id = @Id", new { Id = id }, buffered: true).First(), "Dapper: Query (buffered)"); tests.Add(id => mapperConnection.Query("select * from Posts where Id = @Id", new { Id = id }, buffered: false).First(), "Dapper: Query (non-buffered)"); tests.Add(id => mapperConnection.QueryFirstOrDefault("select * from Posts where Id = @Id", new { Id = id }), "Dapper: QueryFirstOrDefault"); var mapperConnection2 = GetOpenConnection(); tests.Add(id => mapperConnection2.Query("select * from Posts where Id = @Id", new { Id = id }, buffered: true).First(), "Dapper: Dynamic Query (buffered)"); tests.Add(id => mapperConnection2.Query("select * from Posts where Id = @Id", new { Id = id }, buffered: false).First(), "Dapper: Dynamic Query (non-buffered)"); tests.Add(id => mapperConnection2.QueryFirstOrDefault("select * from Posts where Id = @Id", new { Id = id }), "Dapper: Dynamic QueryFirstOrDefault"); // dapper.contrib var mapperConnection3 = GetOpenConnection(); tests.Add(id => mapperConnection3.Get(id), "Dapper.Contrib"); }, "Dapper"); // Massive Try(() => { var massiveModel = new DynamicModel(ConnectionString); var massiveConnection = GetOpenConnection(); tests.Add(id => massiveModel.Query("select * from Posts where Id = @0", massiveConnection, id).First(), "Massive: Dynamic ORM Query"); }, "Massive"); // PetaPoco Try(() => { // PetaPoco test with all default options var petapoco = new PetaPoco.Database(ConnectionString, "System.Data.SqlClient"); petapoco.OpenSharedConnection(); tests.Add(id => petapoco.Fetch("SELECT * from Posts where Id=@0", id).First(), "PetaPoco: Normal"); // PetaPoco with some "smart" functionality disabled var petapocoFast = new PetaPoco.Database(ConnectionString, "System.Data.SqlClient"); petapocoFast.OpenSharedConnection(); petapocoFast.EnableAutoSelect = false; petapocoFast.EnableNamedParams = false; petapocoFast.ForceDateTimesToUtc = false; tests.Add(id => petapocoFast.Fetch("SELECT * from Posts where Id=@0", id).First(), "PetaPoco: Fast"); }, "PetaPoco"); // NHibernate Try(() => { var nhSession1 = NHibernateHelper.OpenSession(); tests.Add(id => nhSession1.CreateSQLQuery("select * from Posts where Id = :id") .SetInt32("id", id) .List(), "NHibernate: SQL"); var nhSession2 = NHibernateHelper.OpenSession(); tests.Add(id => nhSession2.CreateQuery("from Post as p where p.Id = :id") .SetInt32("id", id) .List(), "NHibernate: HQL"); var nhSession3 = NHibernateHelper.OpenSession(); tests.Add(id => nhSession3.CreateCriteria() .Add(Restrictions.IdEq(id)) .List(), "NHibernate: Criteria"); var nhSession4 = NHibernateHelper.OpenSession(); tests.Add(id => nhSession4 .Query() .First(p => p.Id == id), "NHibernate: LINQ"); var nhSession5 = NHibernateHelper.OpenSession(); tests.Add(id => nhSession5.Get(id), "NHibernate: Session.Get"); }, "NHibernate"); // Belgrade Try(() => { var query = new Belgrade.SqlClient.SqlDb.QueryMapper(ConnectionString); tests.AsyncAdd(id => query.Sql("SELECT TOP 1 * FROM Posts WHERE Id = @Id").Param("Id", id).Map( reader => { var post = new Post(); post.Id = reader.GetInt32(0); post.Text = reader.GetString(1); post.CreationDate = reader.GetDateTime(2); post.LastChangeDate = reader.GetDateTime(3); post.Counter1 = reader.IsDBNull(4) ? null : (int?)reader.GetInt32(4); post.Counter2 = reader.IsDBNull(5) ? null : (int?)reader.GetInt32(5); post.Counter3 = reader.IsDBNull(6) ? null : (int?)reader.GetInt32(6); post.Counter4 = reader.IsDBNull(7) ? null : (int?)reader.GetInt32(7); post.Counter5 = reader.IsDBNull(8) ? null : (int?)reader.GetInt32(8); post.Counter6 = reader.IsDBNull(9) ? null : (int?)reader.GetInt32(9); post.Counter7 = reader.IsDBNull(10) ? null : (int?)reader.GetInt32(10); post.Counter8 = reader.IsDBNull(11) ? null : (int?)reader.GetInt32(11); post.Counter9 = reader.IsDBNull(12) ? null : (int?)reader.GetInt32(12); }), "Belgrade Sql Client"); }, "Belgrade Sql Client"); //ServiceStack's OrmLite: Try(() => { var dbFactory = new OrmLiteConnectionFactory(ConnectionString, SqlServerDialect.Provider); var db = dbFactory.Open(); tests.Add(id => db.SingleById(id), "ServiceStack.OrmLite: SingleById"); }, "ServiceStack.OrmLite"); // Hand Coded Try(() => { var postCommand = new SqlCommand() { Connection = connection, CommandText = @"select Id, [Text], [CreationDate], LastChangeDate, Counter1,Counter2,Counter3,Counter4,Counter5,Counter6,Counter7,Counter8,Counter9 from Posts where Id = @Id" }; var idParam = postCommand.Parameters.Add("@Id", SqlDbType.Int); tests.Add(id => { idParam.Value = id; using (var reader = postCommand.ExecuteReader()) { reader.Read(); var post = new Post(); post.Id = reader.GetInt32(0); post.Text = reader.GetNullableString(1); post.CreationDate = reader.GetDateTime(2); post.LastChangeDate = reader.GetDateTime(3); post.Counter1 = reader.GetNullableValue(4); post.Counter2 = reader.GetNullableValue(5); post.Counter3 = reader.GetNullableValue(6); post.Counter4 = reader.GetNullableValue(7); post.Counter5 = reader.GetNullableValue(8); post.Counter6 = reader.GetNullableValue(9); post.Counter7 = reader.GetNullableValue(10); post.Counter8 = reader.GetNullableValue(11); post.Counter9 = reader.GetNullableValue(12); } }, "Hand Coded"); var table = new DataTable { Columns = { {"Id", typeof (int)}, {"Text", typeof (string)}, {"CreationDate", typeof (DateTime)}, {"LastChangeDate", typeof (DateTime)}, {"Counter1", typeof (int)}, {"Counter2", typeof (int)}, {"Counter3", typeof (int)}, {"Counter4", typeof (int)}, {"Counter5", typeof (int)}, {"Counter6", typeof (int)}, {"Counter7", typeof (int)}, {"Counter8", typeof (int)}, {"Counter9", typeof (int)}, } }; tests.Add(id => { idParam.Value = id; object[] values = new object[13]; using (var reader = postCommand.ExecuteReader()) { reader.Read(); reader.GetValues(values); table.Rows.Add(values); } }, "DataTable via IDataReader.GetValues"); }, "Hand Coded"); // DevExpress.XPO Try(() => { IDataLayer dataLayer = XpoDefault.GetDataLayer(connection, DevExpress.Xpo.DB.AutoCreateOption.SchemaAlreadyExists); dataLayer.Dictionary.GetDataStoreSchema(typeof(Xpo.Post)); UnitOfWork session = new UnitOfWork(dataLayer, dataLayer); session.IdentityMapBehavior = IdentityMapBehavior.Strong; session.TypesManager.EnsureIsTypedObjectValid(); tests.Add(id => session.Query().First(p => p.Id == id), "DevExpress.XPO: Query"); tests.Add(id => session.GetObjectByKey(id, true), "DevExpress.XPO: GetObjectByKey"); tests.Add(id => { CriteriaOperator findCriteria = new BinaryOperator() { OperatorType = BinaryOperatorType.Equal, LeftOperand = new OperandProperty("Id"), RightOperand = new ConstantValue(id) }; session.FindObject(findCriteria); }, "DevExpress.XPO: FindObject"); }, "DevExpress.XPO"); // Entity Framework Try(() => { var entityContext = new EFContext(connection); tests.Add(id => entityContext.Posts.First(p => p.Id == id), "Entity Framework"); var entityContext2 = new EFContext(connection); tests.Add(id => entityContext2.Database.SqlQuery("select * from Posts where Id = {0}", id).First(), "Entity Framework: SqlQuery"); var entityContext3 = new EFContext(connection); tests.Add(id => entityContext3.Posts.AsNoTracking().First(p => p.Id == id), "Entity Framework: No Tracking"); }, "Entity Framework"); #if NET4X // Linq2SQL Try(() => { var l2scontext1 = GetL2SContext(connection); tests.Add(id => l2scontext1.Posts.First(p => p.Id == id), "Linq2Sql: Normal"); var l2scontext2 = GetL2SContext(connection); var compiledGetPost = CompiledQuery.Compile((Linq2Sql.DataClassesDataContext ctx, int id) => ctx.Posts.First(p => p.Id == id)); tests.Add(id => compiledGetPost(l2scontext2, id), "Linq2Sql: Compiled"); var l2scontext3 = GetL2SContext(connection); tests.Add(id => l2scontext3.ExecuteQuery("select * from Posts where Id = {0}", id).First(), "Linq2Sql: ExecuteQuery"); }, "LINQ-to-SQL"); // Dashing Try(() => { var config = new DashingConfiguration(); var database = new SqlDatabase(config, ConnectionString); var session = database.BeginTransactionLessSession(GetOpenConnection()); tests.Add(id => session.Get(id), "Dashing Get"); }, "Dashing"); //Susanoo Try(() => { var susanooDb = new DatabaseManager(connection); var susanooPreDefinedCommand = CommandManager.Instance.DefineCommand("SELECT * FROM Posts WHERE Id = @Id", CommandType.Text) .DefineResults() .Realize(); var susanooDynamicPreDefinedCommand = CommandManager.Instance.DefineCommand("SELECT * FROM Posts WHERE Id = @Id", CommandType.Text) .DefineResults() .Realize(); tests.Add(Id => CommandManager.Instance.DefineCommand("SELECT * FROM Posts WHERE Id = @Id", CommandType.Text) .DefineResults() .Realize() .Execute(susanooDb, new { Id }).First(), "Susanoo: Mapping Cache Retrieval"); tests.Add(Id => CommandManager.Instance.DefineCommand("SELECT * FROM Posts WHERE Id = @Id", CommandType.Text) .DefineResults() .Realize() .Execute(susanooDb, new { Id }).First(), "Susanoo: Dynamic Mapping Cache Retrieval"); tests.Add(Id => susanooDynamicPreDefinedCommand .Execute(susanooDb, new { Id }).First(), "Susanoo: Dynamic Mapping Static"); tests.Add(Id => susanooPreDefinedCommand .Execute(susanooDb, new { Id }).First(), "Susanoo: Mapping Static"); }, "Susanoo"); #endif // Subsonic isn't maintained anymore - doesn't import correctly //Try(() => // { // // Subsonic ActiveRecord // tests.Add(id => 3SubSonic.Post.SingleOrDefault(x => x.Id == id), "SubSonic ActiveRecord.SingleOrDefault"); // // Subsonic coding horror // SubSonic.tempdbDB db = new SubSonic.tempdbDB(); // tests.Add(id => new SubSonic.Query.CodingHorror(db.Provider, "select * from Posts where Id = @0", id).ExecuteTypedList(), "SubSonic Coding Horror"); //}, "Subsonic"); //// BLToolkit - doesn't import correctly in the new .csproj world //var db1 = new DbManager(GetOpenConnection()); //tests.Add(id => db1.SetCommand("select * from Posts where Id = @id", db1.Parameter("id", id)).ExecuteList(), "BLToolkit"); Console.WriteLine(); Console.WriteLine("Running..."); await tests.RunAsync(iterations).ConfigureAwait(false); #pragma warning restore RCS1121 // Use [] instead of calling 'First'. #pragma warning restore IDE0017 // Simplify object initialization } } } } ================================================ FILE: benchmarks/Dapper.Tests.Performance/Linq2DB/ConnectionStringSettings.cs ================================================ using LinqToDB.Configuration; namespace Dapper.Tests.Performance.Linq2Db { public class ConnectionStringSettings : IConnectionStringSettings { public string ConnectionString { get; set; } public string Name { get; set; } public string ProviderName { get; set; } public bool IsGlobal => false; } } ================================================ FILE: benchmarks/Dapper.Tests.Performance/Linq2DB/Linq2DBContext.cs ================================================ using LinqToDB; namespace Dapper.Tests.Performance.Linq2Db { public class Linq2DBContext : LinqToDB.Data.DataConnection { public ITable Posts => this.GetTable(); } } ================================================ FILE: benchmarks/Dapper.Tests.Performance/Linq2DB/Linq2DbSettings.cs ================================================ using System.Collections.Generic; using System.Linq; using LinqToDB.Configuration; namespace Dapper.Tests.Performance.Linq2Db { public class Linq2DBSettings : ILinqToDBSettings { private readonly string _connectionString; public IEnumerable DataProviders => Enumerable.Empty(); public string DefaultConfiguration => "SqlServer"; public string DefaultDataProvider => "SqlServer"; public Linq2DBSettings(string connectionString) { _connectionString = connectionString; } public IEnumerable ConnectionStrings { get { yield return new ConnectionStringSettings { Name = "SqlServer", ProviderName = "SqlServer", ConnectionString = _connectionString }; } } } } ================================================ FILE: benchmarks/Dapper.Tests.Performance/Linq2Sql/DataClasses.dbml ================================================ 
================================================ FILE: benchmarks/Dapper.Tests.Performance/Linq2Sql/DataClasses.dbml.layout ================================================  ================================================ FILE: benchmarks/Dapper.Tests.Performance/Linq2Sql/DataClasses.designer.cs ================================================ #if NET4X #pragma warning disable 1591 //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Dapper.Tests.Performance.Linq2Sql { using System.Data.Linq; using System.Data.Linq.Mapping; using System.Data; using System.Collections.Generic; using System.Reflection; using System.Linq; using System.Linq.Expressions; using System.ComponentModel; using System; [global::System.Data.Linq.Mapping.DatabaseAttribute(Name = "tempdb")] public partial class DataClassesDataContext : System.Data.Linq.DataContext { private static System.Data.Linq.Mapping.MappingSource mappingSource = new AttributeMappingSource(); #region Extensibility Method Definitions partial void OnCreated(); partial void InsertPost(Post instance); partial void UpdatePost(Post instance); partial void DeletePost(Post instance); #endregion public DataClassesDataContext(string connection) : base(connection, mappingSource) { OnCreated(); } public DataClassesDataContext(System.Data.IDbConnection connection) : base(connection, mappingSource) { OnCreated(); } public DataClassesDataContext(string connection, System.Data.Linq.Mapping.MappingSource mappingSource) : base(connection, mappingSource) { OnCreated(); } public DataClassesDataContext(System.Data.IDbConnection connection, System.Data.Linq.Mapping.MappingSource mappingSource) : base(connection, mappingSource) { OnCreated(); } public System.Data.Linq.Table Posts { get { return this.GetTable(); } } } [global::System.Data.Linq.Mapping.TableAttribute(Name = "dbo.Posts")] public partial class Post : INotifyPropertyChanging, INotifyPropertyChanged { private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty); private int _Id; private string _Text; private System.DateTime _CreationDate; private System.DateTime _LastChangeDate; private System.Nullable _Counter1; private System.Nullable _Counter2; private System.Nullable _Counter3; private System.Nullable _Counter4; private System.Nullable _Counter5; private System.Nullable _Counter6; private System.Nullable _Counter7; private System.Nullable _Counter8; private System.Nullable _Counter9; #region Extensibility Method Definitions partial void OnLoaded(); partial void OnValidate(System.Data.Linq.ChangeAction action); partial void OnCreated(); partial void OnIdChanging(int value); partial void OnIdChanged(); partial void OnTextChanging(string value); partial void OnTextChanged(); partial void OnCreationDateChanging(System.DateTime value); partial void OnCreationDateChanged(); partial void OnLastChangeDateChanging(System.DateTime value); partial void OnLastChangeDateChanged(); partial void OnCounter1Changing(System.Nullable value); partial void OnCounter1Changed(); partial void OnCounter2Changing(System.Nullable value); partial void OnCounter2Changed(); partial void OnCounter3Changing(System.Nullable value); partial void OnCounter3Changed(); partial void OnCounter4Changing(System.Nullable value); partial void OnCounter4Changed(); partial void OnCounter5Changing(System.Nullable value); partial void OnCounter5Changed(); partial void OnCounter6Changing(System.Nullable value); partial void OnCounter6Changed(); partial void OnCounter7Changing(System.Nullable value); partial void OnCounter7Changed(); partial void OnCounter8Changing(System.Nullable value); partial void OnCounter8Changed(); partial void OnCounter9Changing(System.Nullable value); partial void OnCounter9Changed(); #endregion public Post() { OnCreated(); } [global::System.Data.Linq.Mapping.ColumnAttribute(Storage = "_Id", AutoSync = AutoSync.OnInsert, DbType = "Int NOT NULL IDENTITY", IsPrimaryKey = true, IsDbGenerated = true)] public int Id { get { return this._Id; } set { if ((this._Id != value)) { this.OnIdChanging(value); this.SendPropertyChanging(); this._Id = value; this.SendPropertyChanged("Id"); this.OnIdChanged(); } } } [global::System.Data.Linq.Mapping.ColumnAttribute(Storage = "_Text", DbType = "VarChar(MAX) NOT NULL", CanBeNull = false)] public string Text { get { return this._Text; } set { if ((this._Text != value)) { this.OnTextChanging(value); this.SendPropertyChanging(); this._Text = value; this.SendPropertyChanged("Text"); this.OnTextChanged(); } } } [global::System.Data.Linq.Mapping.ColumnAttribute(Storage = "_CreationDate", DbType = "DateTime NOT NULL")] public System.DateTime CreationDate { get { return this._CreationDate; } set { if ((this._CreationDate != value)) { this.OnCreationDateChanging(value); this.SendPropertyChanging(); this._CreationDate = value; this.SendPropertyChanged("CreationDate"); this.OnCreationDateChanged(); } } } [global::System.Data.Linq.Mapping.ColumnAttribute(Storage = "_LastChangeDate", DbType = "DateTime NOT NULL")] public System.DateTime LastChangeDate { get { return this._LastChangeDate; } set { if ((this._LastChangeDate != value)) { this.OnLastChangeDateChanging(value); this.SendPropertyChanging(); this._LastChangeDate = value; this.SendPropertyChanged("LastChangeDate"); this.OnLastChangeDateChanged(); } } } [global::System.Data.Linq.Mapping.ColumnAttribute(Storage = "_Counter1", DbType = "Int")] public System.Nullable Counter1 { get { return this._Counter1; } set { if ((this._Counter1 != value)) { this.OnCounter1Changing(value); this.SendPropertyChanging(); this._Counter1 = value; this.SendPropertyChanged("Counter1"); this.OnCounter1Changed(); } } } [global::System.Data.Linq.Mapping.ColumnAttribute(Storage = "_Counter2", DbType = "Int")] public System.Nullable Counter2 { get { return this._Counter2; } set { if ((this._Counter2 != value)) { this.OnCounter2Changing(value); this.SendPropertyChanging(); this._Counter2 = value; this.SendPropertyChanged("Counter2"); this.OnCounter2Changed(); } } } [global::System.Data.Linq.Mapping.ColumnAttribute(Storage = "_Counter3", DbType = "Int")] public System.Nullable Counter3 { get { return this._Counter3; } set { if ((this._Counter3 != value)) { this.OnCounter3Changing(value); this.SendPropertyChanging(); this._Counter3 = value; this.SendPropertyChanged("Counter3"); this.OnCounter3Changed(); } } } [global::System.Data.Linq.Mapping.ColumnAttribute(Storage = "_Counter4", DbType = "Int")] public System.Nullable Counter4 { get { return this._Counter4; } set { if ((this._Counter4 != value)) { this.OnCounter4Changing(value); this.SendPropertyChanging(); this._Counter4 = value; this.SendPropertyChanged("Counter4"); this.OnCounter4Changed(); } } } [global::System.Data.Linq.Mapping.ColumnAttribute(Storage = "_Counter5", DbType = "Int")] public System.Nullable Counter5 { get { return this._Counter5; } set { if ((this._Counter5 != value)) { this.OnCounter5Changing(value); this.SendPropertyChanging(); this._Counter5 = value; this.SendPropertyChanged("Counter5"); this.OnCounter5Changed(); } } } [global::System.Data.Linq.Mapping.ColumnAttribute(Storage = "_Counter6", DbType = "Int")] public System.Nullable Counter6 { get { return this._Counter6; } set { if ((this._Counter6 != value)) { this.OnCounter6Changing(value); this.SendPropertyChanging(); this._Counter6 = value; this.SendPropertyChanged("Counter6"); this.OnCounter6Changed(); } } } [global::System.Data.Linq.Mapping.ColumnAttribute(Storage = "_Counter7", DbType = "Int")] public System.Nullable Counter7 { get { return this._Counter7; } set { if ((this._Counter7 != value)) { this.OnCounter7Changing(value); this.SendPropertyChanging(); this._Counter7 = value; this.SendPropertyChanged("Counter7"); this.OnCounter7Changed(); } } } [global::System.Data.Linq.Mapping.ColumnAttribute(Storage = "_Counter8", DbType = "Int")] public System.Nullable Counter8 { get { return this._Counter8; } set { if ((this._Counter8 != value)) { this.OnCounter8Changing(value); this.SendPropertyChanging(); this._Counter8 = value; this.SendPropertyChanged("Counter8"); this.OnCounter8Changed(); } } } [global::System.Data.Linq.Mapping.ColumnAttribute(Storage = "_Counter9", DbType = "Int")] public System.Nullable Counter9 { get { return this._Counter9; } set { if ((this._Counter9 != value)) { this.OnCounter9Changing(value); this.SendPropertyChanging(); this._Counter9 = value; this.SendPropertyChanged("Counter9"); this.OnCounter9Changed(); } } } public event PropertyChangingEventHandler PropertyChanging; public event PropertyChangedEventHandler PropertyChanged; protected virtual void SendPropertyChanging() { if ((this.PropertyChanging != null)) { this.PropertyChanging(this, emptyChangingEventArgs); } } protected virtual void SendPropertyChanged(String propertyName) { if ((this.PropertyChanged != null)) { this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } } } #pragma warning restore 1591 #endif ================================================ FILE: benchmarks/Dapper.Tests.Performance/Massive/Massive.cs ================================================ using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Data; using System.Data.Common; using System.Dynamic; using System.Linq; using System.Text; #pragma warning disable RCS1141 // Add parameter to documentation comment. namespace Massive { public static class ObjectExtensions { /// /// Extension method for adding in a bunch of parameters /// public static void AddParams(this DbCommand cmd, params object[] args) { foreach (var item in args) { AddParam(cmd, item); } } /// /// Extension for adding single parameter /// public static void AddParam(this DbCommand cmd, object item) { var p = cmd.CreateParameter(); p.ParameterName = string.Format("@{0}", cmd.Parameters.Count); if (item == null) { p.Value = DBNull.Value; } else { if (item.GetType() == typeof(Guid)) { p.Value = item.ToString(); p.DbType = DbType.String; p.Size = 4000; } else if (item.GetType() == typeof(ExpandoObject)) { var d = (IDictionary)item; p.Value = d.Values.FirstOrDefault(); } else { p.Value = item; } //from DataChomp if (item.GetType() == typeof(string)) p.Size = 4000; } cmd.Parameters.Add(p); } /// /// Turns an IDataReader to a Dynamic list of things /// public static List ToExpandoList(this IDataReader rdr) { var result = new List(); while (rdr.Read()) { result.Add(rdr.RecordToExpando()); } return result; } public static dynamic RecordToExpando(this IDataReader rdr) { dynamic e = new ExpandoObject(); var d = e as IDictionary; for (int i = 0; i < rdr.FieldCount; i++) d.Add(rdr.GetName(i), rdr[i]); return e; } /// /// Turns the object into an ExpandoObject /// public static dynamic ToExpando(this object o) { var result = new ExpandoObject(); var d = result as IDictionary; //work with the Expando as a Dictionary if (o.GetType() == typeof(ExpandoObject)) return o; //shouldn't have to... but just in case if (o.GetType() == typeof(NameValueCollection) || o.GetType().IsSubclassOf(typeof(NameValueCollection))) { var nv = (NameValueCollection)o; nv.Cast().Select(key => new KeyValuePair(key, nv[key])).ToList().ForEach(i => d.Add(i)); } else { var props = o.GetType().GetProperties(); foreach (var item in props) { d.Add(item.Name, item.GetValue(o, null)); } } return result; } /// /// Turns the object into a Dictionary /// public static IDictionary ToDictionary(this object thingy) { return (IDictionary)thingy.ToExpando(); } } /// /// A class that wraps your database table in Dynamic Funtime /// public class DynamicModel { private readonly DbProviderFactory _factory; #pragma warning disable 0649 #pragma warning disable RCS1169,IDE0044 // Mark field as read-only. private string _connectionString; #pragma warning restore RCS1169,IDE0044 // Mark field as read-only. #pragma warning restore 0649 public DynamicModel(string connectionStringName = "", string tableName = "", string primaryKeyField = "") { _factory = DbProviderFactories.GetFactory("System.Data.SqlClient"); /* TableName = tableName == "" ? this.GetType().Name : tableName; PrimaryKeyField = string.IsNullOrEmpty(primaryKeyField) ? "ID" : primaryKeyField; if (connectionStringName == "") connectionStringName = ConfigurationManager.ConnectionStrings[0].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 + "'"); } _factory = DbProviderFactories.GetFactory(_providerName); _connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString; */ } /// /// Enumerates the reader yielding the result - thanks to Jeroen Haegebaert /// public virtual IEnumerable Query(string sql, params object[] args) { using (var conn = OpenConnection()) { var rdr = CreateCommand(sql, conn, args).ExecuteReader(); while (rdr.Read()) { yield return rdr.RecordToExpando(); } } } public virtual IEnumerable Query(string sql, DbConnection connection, params object[] args) { using (var rdr = CreateCommand(sql, connection, args).ExecuteReader()) { while (rdr.Read()) { yield return rdr.RecordToExpando(); } } } /// /// Returns a single result /// public virtual object Scalar(string sql, params object[] args) { object result = null; using (var conn = OpenConnection()) { result = CreateCommand(sql, conn, args).ExecuteScalar(); } return result; } /// /// Creates a DBCommand that you can use for loving your database. /// private DbCommand CreateCommand(string sql, DbConnection conn, params object[] args) { var result = _factory.CreateCommand(); result.Connection = conn; result.CommandText = sql; if (args.Length > 0) result.AddParams(args); return result; } /// /// Returns and OpenConnection /// public virtual DbConnection OpenConnection() { var result = _factory.CreateConnection(); result.ConnectionString = _connectionString; result.Open(); return result; } /// /// Builds a set of Insert and Update commands based on the passed-on objects. /// These objects can be POCOs, Anonymous, NameValueCollections, or Expandos. Objects /// With a PK property (whatever PrimaryKeyField is set to) will be created at UPDATEs /// public virtual List BuildCommands(params object[] things) { var commands = new List(); foreach (var item in things) { if (HasPrimaryKey(item)) { commands.Add(CreateUpdateCommand(item, GetPrimaryKey(item))); } else { commands.Add(CreateInsertCommand(item)); } } return commands; } /// /// Executes a set of objects as Insert or Update commands based on their property settings, within a transaction. /// These objects can be POCOs, Anonymous, NameValueCollections, or Expandos. Objects /// With a PK property (whatever PrimaryKeyField is set to) will be created at UPDATEs /// public virtual int Save(params object[] things) { var commands = BuildCommands(things); return Execute(commands); } public virtual int Execute(DbCommand command) { return Execute(new DbCommand[] { command }); } /// /// Executes a series of DBCommands in a transaction /// public virtual int Execute(IEnumerable commands) { var result = 0; using (var conn = OpenConnection()) { using (var tx = conn.BeginTransaction()) { foreach (var cmd in commands) { cmd.Connection = conn; cmd.Transaction = tx; result += cmd.ExecuteNonQuery(); } tx.Commit(); } } return result; } public virtual string PrimaryKeyField { get; set; } /// /// Conventionally introspects the object passed in for a field that /// looks like a PK. If you've named your PrimaryKeyField, this becomes easy /// public virtual bool HasPrimaryKey(object o) { return o.ToDictionary().ContainsKey(PrimaryKeyField); } /// /// If the object passed in has a property with the same name as your PrimaryKeyField /// it is returned here. /// public virtual object GetPrimaryKey(object o) { o.ToDictionary().TryGetValue(PrimaryKeyField, out object result); return result; } public virtual string TableName { get; set; } /// /// Creates a command for use with transactions - internal stuff mostly, but here for you to play with /// public virtual DbCommand CreateInsertCommand(object o) { DbCommand result = null; var expando = o.ToExpando(); var settings = (IDictionary)expando; var sbKeys = new StringBuilder(); var sbVals = new StringBuilder(); const string stub = "INSERT INTO {0} ({1}) \r\n VALUES ({2})"; result = CreateCommand(stub, null); int counter = 0; foreach (var item in settings) { sbKeys.AppendFormat("{0},", item.Key); sbVals.AppendFormat("@{0},", counter.ToString()); result.AddParam(item.Value); counter++; } if (counter > 0) { var keys = sbKeys.ToString().Substring(0, sbKeys.Length - 1); var vals = sbVals.ToString().Substring(0, sbVals.Length - 1); result.CommandText = string.Format(stub, TableName, keys, vals); } else { throw new InvalidOperationException("Can't parse this object to the database - there are no properties set"); } return result; } /// /// Creates a command for use with transactions - internal stuff mostly, but here for you to play with /// public virtual DbCommand CreateUpdateCommand(object o, object key) { var expando = o.ToExpando(); var settings = (IDictionary)expando; var sbKeys = new StringBuilder(); const string stub = "UPDATE {0} SET {1} WHERE {2} = @{3}"; var args = new List(); var result = CreateCommand(stub, null); int counter = 0; foreach (var item in settings) { var val = item.Value; if (!item.Key.Equals(PrimaryKeyField, StringComparison.CurrentCultureIgnoreCase) && item.Value != null) { result.AddParam(val); sbKeys.AppendFormat("{0} = @{1}, \r\n", item.Key, counter.ToString()); counter++; } } if (counter > 0) { //add the key result.AddParam(key); //strip the last commas var keys = sbKeys.ToString().Substring(0, sbKeys.Length - 4); result.CommandText = string.Format(stub, TableName, keys, PrimaryKeyField, counter); } else { throw new InvalidOperationException("No parsable object was sent in - could not divine any name/value pairs"); } return result; } /// /// Removes one or more records from the DB according to the passed-in WHERE /// public virtual DbCommand CreateDeleteCommand(string where = "", object key = null, params object[] args) { var sql = string.Format("DELETE FROM {0} ", TableName); if (key != null) { sql += string.Format("WHERE {0}=@0", PrimaryKeyField); args = new object[] { key }; } else if (!string.IsNullOrEmpty(where)) { sql += where.Trim().StartsWith("where", StringComparison.CurrentCultureIgnoreCase) ? where : "WHERE " + where; } return CreateCommand(sql, null, args); } /// /// Adds a record to the database. You can pass in an Anonymous object, an ExpandoObject, /// A regular old POCO, or a NameValueColletion from a Request.Form or Request.QueryString /// public virtual object Insert(object o) { dynamic result = 0; using (var conn = OpenConnection()) { var cmd = CreateInsertCommand(o); cmd.Connection = conn; cmd.ExecuteNonQuery(); cmd.CommandText = "SELECT @@IDENTITY as newID"; result = cmd.ExecuteScalar(); } return result; } /// /// Updates a record in the database. You can pass in an Anonymous object, an ExpandoObject, /// A regular old POCO, or a NameValueCollection from a Request.Form or Request.QueryString /// public virtual int Update(object o, object key) { return Execute(CreateUpdateCommand(o, key)); } /// /// Removes one or more records from the DB according to the passed-in WHERE /// public int Delete(object key = null, string where = "", params object[] args) { return Execute(CreateDeleteCommand(where: where, key: key, args: args)); } /// /// Returns all records complying with the passed-in WHERE clause and arguments, /// ordered as specified, limited (TOP) by limit. /// public virtual IEnumerable All(string where = "", string orderBy = "", int limit = 0, string columns = "*", params object[] args) { string sql = limit > 0 ? "SELECT TOP " + limit + " {0} FROM {1} " : "SELECT {0} FROM {1} "; if (!string.IsNullOrEmpty(where)) sql += where.Trim().StartsWith("where", StringComparison.CurrentCultureIgnoreCase) ? where : "WHERE " + where; if (!string.IsNullOrEmpty(orderBy)) sql += orderBy.Trim().StartsWith("order by", StringComparison.CurrentCultureIgnoreCase) ? orderBy : " ORDER BY " + orderBy; return Query(string.Format(sql, columns, TableName), args); } /// /// Returns a dynamic PagedResult. Result properties are Items, TotalPages, and TotalRecords. /// public virtual dynamic Paged(string where = "", string orderBy = "", string columns = "*", int pageSize = 20, int currentPage = 1, params object[] args) { dynamic result = new ExpandoObject(); var countSQL = string.Format("SELECT COUNT({0}) FROM {1}", PrimaryKeyField, TableName); if (string.IsNullOrEmpty(orderBy)) orderBy = PrimaryKeyField; if (!string.IsNullOrEmpty(where)) { if (!where.Trim().StartsWith("where", StringComparison.CurrentCultureIgnoreCase)) { where = "WHERE " + where; } } var sql = string.Format("SELECT {0} FROM (SELECT ROW_NUMBER() OVER (ORDER BY {2}) AS Row, {0} FROM {3} {4}) AS Paged ", columns, pageSize, orderBy, TableName, where); var pageStart = (currentPage - 1) * pageSize; sql += string.Format(" WHERE Row >={0} AND Row <={1}", pageStart, pageStart + pageSize); countSQL += where; result.TotalRecords = Scalar(countSQL, args); result.TotalPages = result.TotalRecords / pageSize; if (result.TotalRecords % pageSize > 0) result.TotalPages += 1; result.Items = Query(string.Format(sql, columns, TableName), args); return result; } /// /// Returns a single row from the database /// public virtual dynamic Single(object key, string columns = "*") { var sql = $"SELECT {columns} FROM {TableName} WHERE {PrimaryKeyField} = @0"; var items = Query(sql, key).ToList(); return items.FirstOrDefault(); } } } #pragma warning restore RCS1141 // Add parameter to documentation comment. ================================================ FILE: benchmarks/Dapper.Tests.Performance/NHibernate/NHibernateHelper.cs ================================================ using System.Reflection; using NHibernate; using NHibernate.Cfg; namespace Dapper.Tests.Performance.NHibernate { public static class NHibernateHelper { private static ISessionFactory _sessionFactory; private static ISessionFactory SessionFactory { get { if (_sessionFactory == null) { var configuration = new Configuration(); configuration.Configure(Assembly.GetExecutingAssembly(), "Dapper.Tests.Performance.NHibernate.hibernate.cfg.xml"); configuration.AddAssembly(typeof(Post).Assembly); _sessionFactory = configuration.BuildSessionFactory(); } return _sessionFactory; } } public static IStatelessSession OpenSession() { return SessionFactory.OpenStatelessSession(); } } } ================================================ FILE: benchmarks/Dapper.Tests.Performance/NHibernate/Post.hbm.xml ================================================  ================================================ FILE: benchmarks/Dapper.Tests.Performance/NHibernate/hibernate.cfg.xml ================================================  NHibernate.Connection.DriverConnectionProvider NHibernate.Dialect.MsSql2005Dialect NHibernate.Driver.SqlClientDriver Main false ================================================ FILE: benchmarks/Dapper.Tests.Performance/PetaPoco/PetaPoco.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Configuration; using System.Data.Common; using System.Data; using System.Text.RegularExpressions; using System.Reflection; using System.Reflection.Emit; #pragma warning disable RCS1023 // Format empty block. namespace PetaPoco { // Poco's marked [Explicit] require all column properties to be marked [AttributeUsage(AttributeTargets.Class)] public class ExplicitColumns : Attribute { } // For non-explicit pocos, causes a property to be ignored [AttributeUsage(AttributeTargets.Property)] public class Ignore : Attribute { } // For explicit pocos, marks property as a column and optionally supplies column name [AttributeUsage(AttributeTargets.Property)] public class Column : Attribute { public Column() { } public Column(string name) { Name = name; } public string Name { get; set; } } // For explicit pocos, marks property as a result column and optionally supplies column name [AttributeUsage(AttributeTargets.Property)] public class ResultColumn : Column { public ResultColumn() { } public ResultColumn(string name) : base(name) { } } // Specify the table name of a poco [AttributeUsage(AttributeTargets.Class)] public class TableName : Attribute { public TableName(string tableName) { Value = tableName; } public string Value { get; } } // Specific the primary key of a poco class [AttributeUsage(AttributeTargets.Class)] public class PrimaryKey : Attribute { public PrimaryKey(string primaryKey) { Value = primaryKey; } public string Value { get; } } // Results from paged request public class Page where T : new() { public long CurrentPage { get; set; } public long TotalPages { get; set; } public long TotalItems { get; set; } public long ItemsPerPage { get; set; } public List Items { get; set; } } // Optionally provide and implementation of this to Database.Mapper public interface IMapper { void GetTableInfo(Type t, ref string tableName, ref string primaryKey); bool MapPropertyToColumn(PropertyInfo pi, ref string columnName, ref bool resultColumn); Func GetValueConverter(PropertyInfo pi, Type SourceType); } // Database class ... this is where most of the action happens public class Database : IDisposable { public Database(DbConnection connection) { _sharedConnection = connection; _connectionString = connection.ConnectionString; _sharedConnectionDepth = 2; // Prevent closing external connection CommonConstruct(); } public Database(string connectionString, string providerName) { _connectionString = connectionString; _providerName = providerName; CommonConstruct(); } public Database(string connectionStringName) { // Use first? if (connectionStringName == string.Empty) 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; CommonConstruct(); } private // Common initialization void CommonConstruct() { _transactionDepth = 0; EnableAutoSelect = true; EnableNamedParams = true; ForceDateTimesToUtc = true; if (_providerName != null) _factory = DbProviderFactories.GetFactory(_providerName); if (_connectionString?.IndexOf("Allow User Variables=true") >= 0 && IsMySql()) _paramPrefix = "?"; } // Automatically close one open shared connection public void Dispose() { if (_sharedConnectionDepth > 0) CloseSharedConnection(); } // Who are we talking too? private bool IsMySql() { return string.Compare(_providerName, "MySql.Data.MySqlClient", true) == 0; } private bool IsSqlServer() { return string.Compare(_providerName, "System.Data.SqlClient", true) == 0; } // Open a connection (can be nested) public void OpenSharedConnection() { if (_sharedConnectionDepth == 0) { _sharedConnection = _factory.CreateConnection(); _sharedConnection.ConnectionString = _connectionString; _sharedConnection.Open(); } _sharedConnectionDepth++; } // Close a previously opened connection public void CloseSharedConnection() { _sharedConnectionDepth--; if (_sharedConnectionDepth == 0) { _sharedConnection.Dispose(); _sharedConnection = null; } } // Helper to create a transaction scope public Transaction Transaction => new Transaction(this); // Use by derived repo generated by T4 templates public virtual void OnBeginTransaction() { } public virtual void OnEndTransaction() { } // Start a new transaction, can be nested, every call must be // matched by a call to AbortTransaction or CompleteTransaction // Use `using (var scope=db.Transaction) { scope.Complete(); }` to ensure correct semantics public void BeginTransaction() { _transactionDepth++; if (_transactionDepth == 1) { OpenSharedConnection(); _transaction = _sharedConnection.BeginTransaction(); _transactionCancelled = false; OnBeginTransaction(); } } private // Internal helper to cleanup transaction stuff void CleanupTransaction() { OnEndTransaction(); if (_transactionCancelled) _transaction.Rollback(); else _transaction.Commit(); _transaction.Dispose(); _transaction = null; CloseSharedConnection(); } // Abort the entire outer most transaction scope public void AbortTransaction() { _transactionCancelled = true; if ((--_transactionDepth) == 0) CleanupTransaction(); } // Complete the transaction public void CompleteTransaction() { if ((--_transactionDepth) == 0) CleanupTransaction(); } // Helper to handle named parameters from object properties private static readonly Regex rxParams = new Regex(@"(? args_dest) { return rxParams.Replace(_sql, m => { string param = m.Value.Substring(1); if (int.TryParse(param, out int paramIndex)) { // Numbered parameter if (paramIndex < 0 || paramIndex >= args_src.Length) throw new ArgumentOutOfRangeException(string.Format("Parameter '@{0}' specified but only {1} parameters supplied (in `{2}`)", paramIndex, args_src.Length, _sql)); args_dest.Add(args_src[paramIndex]); } else { // Look for a property on one of the arguments with this name bool found = false; foreach (var o in args_src) { var pi = o.GetType().GetProperty(param); if (pi != null) { args_dest.Add(pi.GetValue(o, null)); found = true; break; } } if (!found) throw new ArgumentException(string.Format("Parameter '@{0}' specified but none of the passed arguments have a property with this name (in '{1}')", param, _sql)); } return "@" + (args_dest.Count - 1).ToString(); } ); } // Add a parameter to a DB command private static void AddParam(DbCommand cmd, object item, string ParameterPrefix) { var p = cmd.CreateParameter(); p.ParameterName = string.Format("{0}{1}", ParameterPrefix, cmd.Parameters.Count); if (item == null) { p.Value = DBNull.Value; } else { if (item.GetType() == typeof(Guid)) { p.Value = item.ToString(); p.DbType = DbType.String; p.Size = 4000; } else if (item.GetType() == typeof(string)) { p.Size = (item as string).Length + 1; if (p.Size < 4000) p.Size = 4000; p.Value = item; } else { p.Value = item; } } cmd.Parameters.Add(p); } // Create a command public DbCommand CreateCommand(DbConnection connection, string sql, params object[] args) { if (EnableNamedParams) { // Perform named argument replacements var new_args = new List(); sql = ProcessParams(sql, args, new_args); args = new_args.ToArray(); } // If we're in MySQL "Allow User Variables", we need to fix up parameter prefixes if (_paramPrefix == "?") { // Convert "@parameter" -> "?parameter" Regex paramReg = new Regex(@"(? "?" + m.Value.Substring(1)); // Convert @@uservar -> @uservar and @@@systemvar -> @@systemvar sql = sql.Replace("@@", "@"); } // Save the last sql and args _lastSql = sql; _lastArgs = args; DbCommand cmd = connection.CreateCommand(); cmd.CommandText = sql; cmd.Transaction = _transaction; foreach (var item in args) { var p = cmd.CreateParameter(); p.ParameterName = string.Format("{0}{1}", _paramPrefix, cmd.Parameters.Count); if (item == null) { p.Value = DBNull.Value; } else { if (item.GetType() == typeof(Guid)) { p.Value = item.ToString(); p.DbType = DbType.String; p.Size = 4000; } else if (item.GetType() == typeof(string)) { p.Size = (item as string).Length + 1; if (p.Size < 4000) p.Size = 4000; p.Value = item; } else { p.Value = item; } } cmd.Parameters.Add(p); } return cmd; } // Override this to log/capture exceptions public virtual void OnException(Exception x) { System.Diagnostics.Debug.WriteLine(x.ToString()); System.Diagnostics.Debug.WriteLine(LastCommand); } // Execute a non-query command public int Execute(string sql, params object[] args) { try { OpenSharedConnection(); try { using (var cmd = CreateCommand(_sharedConnection, sql, args)) { return cmd.ExecuteNonQuery(); } } finally { CloseSharedConnection(); } } catch (Exception x) { OnException(x); throw; } } public int Execute(Sql sql) { return Execute(sql.SQL, sql.Arguments); } // Execute and cast a scalar property public T ExecuteScalar(string sql, params object[] args) { try { OpenSharedConnection(); try { using (var cmd = CreateCommand(_sharedConnection, sql, args)) { object val = cmd.ExecuteScalar(); return (T)Convert.ChangeType(val, typeof(T)); } } finally { CloseSharedConnection(); } } catch (Exception x) { OnException(x); throw; } } public T ExecuteScalar(Sql sql) { return ExecuteScalar(sql.SQL, sql.Arguments); } private readonly Regex rxSelect = new Regex(@"^\s*SELECT\s", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Multiline); private string AddSelectClause(string sql) { // Already present? if (rxSelect.IsMatch(sql)) return sql; // Get the poco data for this type var pd = PocoData.ForType(typeof(T)); return string.Format("SELECT {0} FROM {1} {2}", pd.QueryColumns, pd.TableName, sql); } public bool EnableAutoSelect { get; set; } public bool EnableNamedParams { get; set; } public bool ForceDateTimesToUtc { get; set; } // Return a typed list of pocos public List Fetch(string sql, params object[] args) where T : new() { // Auto select clause? if (EnableAutoSelect) sql = AddSelectClause(sql); try { OpenSharedConnection(); try { using (var cmd = CreateCommand(_sharedConnection, sql, args)) { using (var r = cmd.ExecuteReader()) { var l = new List(); var pd = PocoData.ForType(typeof(T)); var factory = pd.GetFactory(sql + "-" + _sharedConnection.ConnectionString + ForceDateTimesToUtc.ToString(), ForceDateTimesToUtc, r); while (r.Read()) { l.Add(factory(r)); } return l; } } } finally { CloseSharedConnection(); } } catch (Exception x) { OnException(x); throw; } } // Optimized version when only needing a single record public T FirstOrDefault(string sql, params object[] args) where T : new() { // Auto select clause? if (EnableAutoSelect) sql = AddSelectClause(sql); try { OpenSharedConnection(); try { using (var cmd = CreateCommand(_sharedConnection, sql, args)) { using (var r = cmd.ExecuteReader()) { if (!r.Read()) return default(T); var pd = PocoData.ForType(typeof(T)); var factory = pd.GetFactory(sql + "-" + _sharedConnection.ConnectionString + ForceDateTimesToUtc.ToString(), ForceDateTimesToUtc, r); return factory(r); } } } finally { CloseSharedConnection(); } } catch (Exception x) { OnException(x); throw; } } // Optimized version when only wanting a single record public T SingleOrDefault(string sql, params object[] args) where T : new() { // Auto select clause? if (EnableAutoSelect) sql = AddSelectClause(sql); try { OpenSharedConnection(); try { using (var cmd = CreateCommand(_sharedConnection, sql, args)) { using (var r = cmd.ExecuteReader()) { if (!r.Read()) return default(T); var pd = PocoData.ForType(typeof(T)); var factory = pd.GetFactory(sql + "-" + _sharedConnection.ConnectionString + ForceDateTimesToUtc.ToString(), ForceDateTimesToUtc, r); T ret = factory(r); if (r.Read()) throw new InvalidOperationException("Sequence contains more than one element"); return ret; } } } finally { CloseSharedConnection(); } } catch (Exception x) { OnException(x); throw; } } // Warning: scary regex follows private static readonly Regex rxColumns = new Regex(@"^\s*SELECT\s+((?:\((?>\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|.)*?)(?\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?(?:\s*,\s*(?:\((?>\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?)*", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled); public static bool SplitSqlForPaging(string sql, out string sqlCount, out string sqlSelectRemoved, out string sqlOrderBy) { sqlSelectRemoved = null; sqlCount = null; sqlOrderBy = null; // Extract the columns from "SELECT FROM" var m = rxColumns.Match(sql); if (!m.Success) return false; // Save column list and replace with COUNT(*) Group g = m.Groups[1]; sqlCount = sql.Substring(0, g.Index) + "COUNT(*) " + sql.Substring(g.Index + g.Length); sqlSelectRemoved = sql.Substring(g.Index); // Look for an "ORDER BY " clause m = rxOrderBy.Match(sqlCount); if (!m.Success) return false; g = m.Groups[0]; sqlOrderBy = g.ToString(); sqlCount = sqlCount.Substring(0, g.Index) + sqlCount.Substring(g.Index + g.Length); return true; } // Fetch a page public Page Page(long page, long itemsPerPage, string sql, params object[] args) where T : new() { // Add auto select clause if (EnableAutoSelect) sql = AddSelectClause(sql); if (!SplitSqlForPaging(sql, out string sqlCount, out string sqlSelectRemoved, out string sqlOrderBy)) throw new Exception("Unable to parse SQL statement for paged query"); // Setup the paged result var result = new Page { CurrentPage = page, ItemsPerPage = itemsPerPage, TotalItems = ExecuteScalar(sqlCount, args) }; result.TotalPages = result.TotalItems / itemsPerPage; if ((result.TotalItems % itemsPerPage) != 0) result.TotalPages++; // Build the SQL for the actual final result string sqlPage; if (IsSqlServer()) { // Ugh really? sqlSelectRemoved = rxOrderBy.Replace(sqlSelectRemoved, ""); sqlPage = string.Format("SELECT * FROM (SELECT ROW_NUMBER() OVER ({0}) AS __rn, {1}) as __paged WHERE __rn>{2} AND __rn<={3}", sqlOrderBy, sqlSelectRemoved, (page - 1) * itemsPerPage, page * itemsPerPage); } else { // Nice sqlPage = string.Format("{0}\nLIMIT {1} OFFSET {2}", sql, itemsPerPage, (page - 1) * itemsPerPage); } // Get the records result.Items = Fetch(sqlPage, args); // Done return result; } public Page Page(long page, long itemsPerPage, Sql sql) where T : new() { return Page(page, itemsPerPage, sql.SQL, sql.Arguments); } // Return an enumerable collection of pocos public IEnumerable Query(string sql, params object[] args) where T : new() { if (EnableAutoSelect) sql = AddSelectClause(sql); using (var conn = new ShareableConnection(this)) { using (var cmd = CreateCommand(conn.Connection, sql, args)) { IDataReader r; var pd = PocoData.ForType(typeof(T)); try { r = cmd.ExecuteReader(); } catch (Exception x) { OnException(x); throw; } var factory = pd.GetFactory(sql + "-" + conn.Connection.ConnectionString + ForceDateTimesToUtc.ToString(), ForceDateTimesToUtc, r); using (r) { while (true) { T poco; try { if (!r.Read()) yield break; poco = factory(r); } catch (Exception x) { OnException(x); throw; } yield return poco; } } } } } public List Fetch(Sql sql) where T : new() { return Fetch(sql.SQL, sql.Arguments); } public IEnumerable Query(Sql sql) where T : new() { return Query(sql.SQL, sql.Arguments); } public T Single(string sql, params object[] args) where T : new() { T val = SingleOrDefault(sql, args); if (!EqualityComparer.Default.Equals(val, default(T))) return val; else throw new InvalidOperationException("The sequence contains no elements"); } public T First(string sql, params object[] args) where T : new() { T val = FirstOrDefault(sql, args); if (!EqualityComparer.Default.Equals(val, default(T))) return val; else throw new InvalidOperationException("The sequence contains no elements"); } public T Single(Sql sql) where T : new() { return Single(sql.SQL, sql.Arguments); } public T SingleOrDefault(Sql sql) where T : new() { return SingleOrDefault(sql.SQL, sql.Arguments); } public T FirstOrDefault(Sql sql) where T : new() { return FirstOrDefault(sql.SQL, sql.Arguments); } public T First(Sql sql) where T : new() { return First(sql.SQL, sql.Arguments); } // Insert a poco into a table. If the poco has a property with the same name // as the primary key the id of the new record is assigned to it. Either way, // the new id is returned. public object Insert(string tableName, string primaryKeyName, object poco) { try { OpenSharedConnection(); try { using (var cmd = CreateCommand(_sharedConnection, "")) { var pd = PocoData.ForType(poco.GetType()); var names = new List(); var values = new List(); var index = 0; foreach (var i in pd.Columns) { // Don't insert the primary key or result only columns if ((primaryKeyName != null && i.Key == primaryKeyName) || i.Value.ResultColumn) continue; names.Add(i.Key); values.Add(string.Format("{0}{1}", _paramPrefix, index++)); AddParam(cmd, i.Value.PropertyInfo.GetValue(poco, null), _paramPrefix); } cmd.CommandText = string.Format("INSERT INTO {0} ({1}) VALUES ({2}); SELECT @@IDENTITY AS NewID;", tableName, string.Join(",", names.ToArray()), string.Join(",", values.ToArray()) ); _lastSql = cmd.CommandText; _lastArgs = values.ToArray(); // Insert the record, should get back it's ID var id = cmd.ExecuteScalar(); // Assign the ID back to the primary key property if (primaryKeyName != null) { if (pd.Columns.TryGetValue(primaryKeyName, out PocoColumn pc)) { pc.PropertyInfo.SetValue(poco, Convert.ChangeType(id, pc.PropertyInfo.PropertyType), null); } } return id; } } finally { CloseSharedConnection(); } } catch (Exception x) { OnException(x); throw; } } // Insert an annotated poco object public object Insert(object poco) { var pd = PocoData.ForType(poco.GetType()); return Insert(pd.TableName, pd.PrimaryKey, poco); } // Update a record with values from a poco. primary key value can be either supplied or read from the poco public int Update(string tableName, string primaryKeyName, object poco, object primaryKeyValue) { try { OpenSharedConnection(); try { using (var cmd = CreateCommand(_sharedConnection, "")) { var sb = new StringBuilder(); var index = 0; var pd = PocoData.ForType(poco.GetType()); foreach (var i in pd.Columns) { // Don't update the primary key, but grab the value if we don't have it if (i.Key == primaryKeyName) { primaryKeyValue ??= i.Value.PropertyInfo.GetValue(poco, null); continue; } // Dont update result only columns if (i.Value.ResultColumn) continue; // Build the sql if (index > 0) sb.Append(", "); sb.AppendFormat("{0} = {1}{2}", i.Key, _paramPrefix, index++); // Store the parameter in the command AddParam(cmd, i.Value.PropertyInfo.GetValue(poco, null), _paramPrefix); } cmd.CommandText = string.Format("UPDATE {0} SET {1} WHERE {2} = {3}{4}", tableName, sb.ToString(), primaryKeyName, _paramPrefix, index++ ); AddParam(cmd, primaryKeyValue, _paramPrefix); _lastSql = cmd.CommandText; _lastArgs = new object[] { primaryKeyValue }; // Do it return cmd.ExecuteNonQuery(); } } finally { CloseSharedConnection(); } } catch (Exception x) { OnException(x); throw; } } public int Update(string tableName, string primaryKeyName, object poco) { return Update(tableName, primaryKeyName, poco, null); } public int Update(object poco) { return Update(poco, null); } public int Update(object poco, object primaryKeyValue) { var pd = PocoData.ForType(poco.GetType()); return Update(pd.TableName, pd.PrimaryKey, poco, primaryKeyValue); } public int Update(string sql, params object[] args) { var pd = PocoData.ForType(typeof(T)); return Execute(string.Format("UPDATE {0} {1}", pd.TableName, sql), args); } public int Update(Sql sql) { var pd = PocoData.ForType(typeof(T)); return Execute(new Sql(string.Format("UPDATE {0}", pd.TableName)).Append(sql)); } public int Delete(string tableName, string primaryKeyName, object poco) { return Delete(tableName, primaryKeyName, poco, null); } public int Delete(string tableName, string primaryKeyName, object poco, object primaryKeyValue) { // If primary key value not specified, pick it up from the object if (primaryKeyValue == null) { var pd = PocoData.ForType(poco.GetType()); if (pd.Columns.TryGetValue(primaryKeyName, out PocoColumn pc)) { primaryKeyValue = pc.PropertyInfo.GetValue(poco, null); } } // Do it var sql = string.Format("DELETE FROM {0} WHERE {1}=@0", tableName, primaryKeyName); return Execute(sql, primaryKeyValue); } public int Delete(object poco) { var pd = PocoData.ForType(poco.GetType()); return Delete(pd.TableName, pd.PrimaryKey, poco); } public int Delete(string sql, params object[] args) { var pd = PocoData.ForType(typeof(T)); return Execute(string.Format("DELETE FROM {0} {1}", pd.TableName, sql), args); } public int Delete(Sql sql) { var pd = PocoData.ForType(typeof(T)); return Execute(new Sql(string.Format("DELETE FROM {0}", pd.TableName)).Append(sql)); } // Check if a poco represents a new record public bool IsNew(string primaryKeyName, object poco) { // If primary key value not specified, pick it up from the object var pd = PocoData.ForType(poco.GetType()); PropertyInfo pi; if (pd.Columns.TryGetValue(primaryKeyName, out PocoColumn pc)) { pi = pc.PropertyInfo; } else { pi = poco.GetType().GetProperty(primaryKeyName); if (pi == null) throw new ArgumentException("The object doesn't have a property matching the primary key column name '{0}'", primaryKeyName); } // Get it's value var pk = pi.GetValue(poco, null); if (pk == null) return true; var type = pk.GetType(); if (type.IsValueType) { // Common primary key types if (type == typeof(long)) return (long)pk == 0; else if (type == typeof(ulong)) return (ulong)pk == 0; else if (type == typeof(int)) return (int)pk == 0; else if (type == typeof(uint)) return (int)pk == 0; // Create a default instance and compare return pk == Activator.CreateInstance(pk.GetType()); } else { return pk == null; } } public bool IsNew(object poco) { var pd = PocoData.ForType(poco.GetType()); return IsNew(pd.PrimaryKey, poco); } // Insert new record or Update existing record public void Save(string tableName, string primaryKeyName, object poco) { if (IsNew(primaryKeyName, poco)) { Insert(tableName, primaryKeyName, poco); } else { Update(tableName, primaryKeyName, poco); } } public void Save(object poco) { var pd = PocoData.ForType(poco.GetType()); Save(pd.TableName, pd.PrimaryKey, poco); } public string LastSQL => _lastSql; public object[] LastArgs => _lastArgs; public string LastCommand { get { var sb = new StringBuilder(); if (_lastSql == null) return ""; sb.Append(_lastSql); if (_lastArgs != null) { sb.Append("\r\n\r\n"); for (int i = 0; i < _lastArgs.Length; i++) { sb.AppendFormat("{0} - {1}\r\n", i, _lastArgs[i].ToString()); } } return sb.ToString(); } } public static IMapper Mapper { get; set; } internal class PocoColumn { public string ColumnName; public PropertyInfo PropertyInfo; public bool ResultColumn; } internal class PocoData { public static PocoData ForType(Type t) { lock (m_PocoData) { if (!m_PocoData.TryGetValue(t, out PocoData pd)) { pd = new PocoData(t); m_PocoData.Add(t, pd); } return pd; } } public PocoData(Type t) { // Get the table name var a = t.GetCustomAttributes(typeof(TableName), true); var tempTableName = a.Length == 0 ? t.Name : (a[0] as TableName).Value; // Get the primary key a = t.GetCustomAttributes(typeof(PrimaryKey), true); var tempPrimaryKey = a.Length == 0 ? "ID" : (a[0] as PrimaryKey).Value; // Call column mapper Database.Mapper?.GetTableInfo(t, ref tempTableName, ref tempPrimaryKey); TableName = tempTableName; PrimaryKey = tempPrimaryKey; // Work out bound properties bool ExplicitColumns = t.GetCustomAttributes(typeof(ExplicitColumns), true).Length > 0; Columns = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var pi in t.GetProperties()) { // Work out if properties is to be included var ColAttrs = pi.GetCustomAttributes(typeof(Column), true); if (ExplicitColumns) { if (ColAttrs.Length == 0) continue; } else { if (pi.GetCustomAttributes(typeof(Ignore), true).Length != 0) continue; } var pc = new PocoColumn() { PropertyInfo = pi }; // Work out the DB column name if (ColAttrs.Length > 0) { var colattr = (Column)ColAttrs[0]; pc.ColumnName = colattr.Name; if (colattr is ResultColumn) pc.ResultColumn = true; } if (pc.ColumnName == null) { pc.ColumnName = pi.Name; if (Mapper?.MapPropertyToColumn(pi, ref pc.ColumnName, ref pc.ResultColumn) == false) continue; } // Store it Columns.Add(pc.ColumnName, pc); } // Build column list for automatic select QueryColumns = string.Join(", ", (from c in Columns where !c.Value.ResultColumn select c.Key).ToArray()); } // Create factory function that can convert a IDataReader record into a POCO public Func GetFactory(string key, bool ForceDateTimesToUtc, IDataReader r) { lock (PocoFactories) { // Have we already created it? if (PocoFactories.TryGetValue(key, out object factory)) return factory as Func; lock (m_Converters) { // Create the method var m = new DynamicMethod("petapoco_factory_" + PocoFactories.Count.ToString(), typeof(T), new Type[] { typeof(IDataReader) }, true); var il = m.GetILGenerator(); // Running under mono? int p = (int)Environment.OSVersion.Platform; bool Mono = (p == 4) || (p == 6) || (p == 128); // var poco=new T() il.Emit(OpCodes.Newobj, typeof(T).GetConstructor(Type.EmptyTypes)); // Enumerate all fields generating a set assignment for the column for (int i = 0; i < r.FieldCount; i++) { // Get the PocoColumn for this db column, ignore if not known if (!Columns.TryGetValue(r.GetName(i), out PocoColumn pc)) continue; // Get the source type for this column var srcType = r.GetFieldType(i); var dstType = pc.PropertyInfo.PropertyType; // "if (!rdr.IsDBNull(i))" il.Emit(OpCodes.Ldarg_0); // poco,rdr il.Emit(OpCodes.Ldc_I4, i); // poco,rdr,i il.Emit(OpCodes.Callvirt, fnIsDBNull); // poco,bool var lblNext = il.DefineLabel(); il.Emit(OpCodes.Brtrue_S, lblNext); // poco il.Emit(OpCodes.Dup); // poco,poco // Do we need to install a converter? Func converter = null; // Get converter from the mapper if (Database.Mapper != null) { converter = Mapper.GetValueConverter(pc.PropertyInfo, srcType); } // Standard DateTime->Utc mapper if (ForceDateTimesToUtc && converter == null && srcType == typeof(DateTime) && (dstType == typeof(DateTime) || dstType == typeof(DateTime?))) { converter = (object src) => new DateTime(((DateTime)src).Ticks, DateTimeKind.Utc); } // Forced type conversion if (converter == null && !dstType.IsAssignableFrom(srcType)) { converter = (object src) => Convert.ChangeType(src, dstType, null); } // Fast bool Handled = false; if (converter == null) { var valuegetter = typeof(IDataRecord).GetMethod("Get" + srcType.Name, new Type[] { typeof(int) }); if (valuegetter != null && valuegetter.ReturnType == srcType && (valuegetter.ReturnType == dstType || valuegetter.ReturnType == Nullable.GetUnderlyingType(dstType))) { il.Emit(OpCodes.Ldarg_0); // *,rdr il.Emit(OpCodes.Ldc_I4, i); // *,rdr,i il.Emit(OpCodes.Callvirt, valuegetter); // *,value // Mono give IL error if we don't explicitly create Nullable instance for the assignment if (Mono && Nullable.GetUnderlyingType(dstType) != null) { il.Emit(OpCodes.Newobj, dstType.GetConstructor(new Type[] { Nullable.GetUnderlyingType(dstType) })); } il.Emit(OpCodes.Callvirt, pc.PropertyInfo.GetSetMethod()); // poco Handled = true; } } // Not so fast if (!Handled) { // Setup stack for call to converter int converterIndex = -1; if (converter != null) { // Add the converter converterIndex = m_Converters.Count; m_Converters.Add(converter); // Generate IL to push the converter onto the stack il.Emit(OpCodes.Ldsfld, fldConverters); il.Emit(OpCodes.Ldc_I4, converterIndex); il.Emit(OpCodes.Callvirt, fnListGetItem); // Converter } // "value = rdr.GetValue(i)" il.Emit(OpCodes.Ldarg_0); // *,rdr il.Emit(OpCodes.Ldc_I4, i); // *,rdr,i il.Emit(OpCodes.Callvirt, fnGetValue); // *,value // Call the converter if (converter != null) il.Emit(OpCodes.Callvirt, fnInvoke); // Assign it il.Emit(OpCodes.Unbox_Any, pc.PropertyInfo.PropertyType); // poco,poco,value il.Emit(OpCodes.Callvirt, pc.PropertyInfo.GetSetMethod()); // poco } il.MarkLabel(lblNext); } il.Emit(OpCodes.Ret); // Cache it, return it var del = (Func)m.CreateDelegate(typeof(Func)); PocoFactories.Add(key, del); return del; } } } private static readonly Dictionary m_PocoData = new Dictionary(); private static readonly List> m_Converters = new List>(); private static readonly MethodInfo fnGetValue = typeof(IDataRecord).GetMethod("GetValue", new Type[] { typeof(int) }); private static readonly MethodInfo fnIsDBNull = typeof(IDataRecord).GetMethod("IsDBNull"); private static readonly FieldInfo fldConverters = typeof(PocoData).GetField("m_Converters", BindingFlags.Static | BindingFlags.GetField | BindingFlags.NonPublic); private static readonly MethodInfo fnListGetItem = typeof(List>).GetProperty("Item").GetGetMethod(); private static readonly MethodInfo fnInvoke = typeof(Func).GetMethod("Invoke"); public string TableName { get; } public string PrimaryKey { get; } public string QueryColumns { get; } public Dictionary Columns { get; } private readonly Dictionary PocoFactories = new Dictionary(); } // ShareableConnection represents either a shared connection used by a transaction, // or a one-off connection if not in a transaction. // Non-shared connections are disposed private class ShareableConnection : IDisposable { public ShareableConnection(Database db) { _db = db; _db.OpenSharedConnection(); } public DbConnection Connection => _db._sharedConnection; private readonly Database _db; public void Dispose() { _db.CloseSharedConnection(); } } // Member variables private readonly string _connectionString; private readonly string _providerName; private DbProviderFactory _factory; private DbConnection _sharedConnection; private DbTransaction _transaction; private int _sharedConnectionDepth; private int _transactionDepth; private bool _transactionCancelled; private string _lastSql; private object[] _lastArgs; private string _paramPrefix = "@"; } // Transaction object helps maintain transaction depth counts public class Transaction : IDisposable { public Transaction(Database db) { _db = db; _db.BeginTransaction(); } public void Complete() { _db.CompleteTransaction(); _db = null; } public void Dispose() { _db?.AbortTransaction(); } private Database _db; } // Simple helper class for building SQL statments public class Sql { public Sql() { } public Sql(string sql, params object[] args) { _sql = sql; _args = args; } private readonly string _sql; private readonly object[] _args; private Sql _rhs; private string _sqlFinal; private object[] _argsFinal; private void Build() { // already built? if (_sqlFinal != null) return; // Build it var sb = new StringBuilder(); var args = new List(); Build(sb, args, null); _sqlFinal = sb.ToString(); _argsFinal = args.ToArray(); } public string SQL { get { Build(); return _sqlFinal; } } public object[] Arguments { get { Build(); return _argsFinal; } } public Sql Append(Sql sql) { if (_rhs != null) _rhs.Append(sql); else _rhs = sql; return this; } public Sql Append(string sql, params object[] args) { return Append(new Sql(sql, args)); } public Sql Where(string sql, params object[] args) { return Append(new Sql("WHERE " + sql, args)); } public Sql OrderBy(params object[] args) { return Append(new Sql("ORDER BY " + string.Join(", ", (from x in args select x.ToString()).ToArray()))); } public Sql Select(params object[] args) { return Append(new Sql("SELECT " + string.Join(", ", (from x in args select x.ToString()).ToArray()))); } public Sql From(params object[] args) { return Append(new Sql("FROM " + string.Join(", ", (from x in args select x.ToString()).ToArray()))); } private static bool Is(Sql sql, string sqltype) { return sql?._sql != null && sql._sql.StartsWith(sqltype, StringComparison.InvariantCultureIgnoreCase); } public void Build(StringBuilder sb, List args, Sql lhs) { if (!string.IsNullOrEmpty(_sql)) { // Add SQL to the string if (sb.Length > 0) { sb.Append("\n"); } var sql = Database.ProcessParams(_sql, _args, args); if (Is(lhs, "WHERE ") && Is(this, "WHERE ")) sql = "AND " + sql.Substring(6); if (Is(lhs, "ORDER BY ") && Is(this, "ORDER BY ")) sql = ", " + sql.Substring(9); sb.Append(sql); } // Now do rhs _rhs?.Build(sb, args, this); } } } #pragma warning restore RCS1023 // Format empty block. ================================================ FILE: benchmarks/Dapper.Tests.Performance/Post.cs ================================================ using System; namespace Dapper.Tests.Performance { [ServiceStack.DataAnnotations.Alias("Posts")] [LinqToDB.Mapping.Table(Name = "Posts")] public class Post { [LinqToDB.Mapping.PrimaryKey, LinqToDB.Mapping.Identity] public int Id { get; set; } [LinqToDB.Mapping.Column, LinqToDB.Mapping.Nullable] public string Text { get; set; } [LinqToDB.Mapping.Column, LinqToDB.Mapping.NotNull] public DateTime CreationDate { get; set; } [LinqToDB.Mapping.Column, LinqToDB.Mapping.NotNull] public DateTime LastChangeDate { get; set; } [LinqToDB.Mapping.Column, LinqToDB.Mapping.Nullable] public int? Counter1 { get; set; } [LinqToDB.Mapping.Column, LinqToDB.Mapping.Nullable] public int? Counter2 { get; set; } [LinqToDB.Mapping.Column, LinqToDB.Mapping.Nullable] public int? Counter3 { get; set; } [LinqToDB.Mapping.Column, LinqToDB.Mapping.Nullable] public int? Counter4 { get; set; } [LinqToDB.Mapping.Column, LinqToDB.Mapping.Nullable] public int? Counter5 { get; set; } [LinqToDB.Mapping.Column, LinqToDB.Mapping.Nullable] public int? Counter6 { get; set; } [LinqToDB.Mapping.Column, LinqToDB.Mapping.Nullable] public int? Counter7 { get; set; } [LinqToDB.Mapping.Column, LinqToDB.Mapping.Nullable] public int? Counter8 { get; set; } [LinqToDB.Mapping.Column, LinqToDB.Mapping.Nullable] public int? Counter9 { get; set; } } } ================================================ FILE: benchmarks/Dapper.Tests.Performance/Program.cs ================================================ using BenchmarkDotNet.Running; using System; using Microsoft.Data.SqlClient; using System.Linq; using static System.Console; namespace Dapper.Tests.Performance { public static class Program { public static void Main(string[] args) { #if DEBUG WriteLineColor("Warning: DEBUG configuration; performance may be impacted!", ConsoleColor.Red); WriteLine(); #endif WriteLine("Welcome to Dapper's ORM performance benchmark suite, based on BenchmarkDotNet."); Write(" If you find a problem, please report it at: "); WriteLineColor("https://github.com/DapperLib/Dapper", ConsoleColor.Blue); WriteLine(" Or if you're up to it, please submit a pull request! We welcome new additions."); WriteLine(); if (args.Length == 0) { WriteLine("Optional arguments:"); WriteColor(" (no args)", ConsoleColor.Blue); WriteLine(": run all benchmarks"); WriteColor(" --legacy", ConsoleColor.Blue); WriteLineColor(": run the legacy benchmark suite/format", ConsoleColor.Gray); WriteLine(); } WriteLine("Using ConnectionString: " + BenchmarkBase.ConnectionString); EnsureDBSetup(); WriteLine("Database setup complete."); if (args.Any(a => a == "--legacy")) { var test = new LegacyTests(); const int iterations = 500; WriteLineColor($"Running legacy benchmarks: {iterations} iterations that load up a Post entity.", ConsoleColor.Green); test.RunAsync(iterations).GetAwaiter().GetResult(); WriteLine(); WriteLineColor("Run complete.", ConsoleColor.Green); } else { WriteLine("Iterations: " + Config.Iterations); new BenchmarkSwitcher(typeof(BenchmarkBase).Assembly).Run(args, new Config()); } } private static void EnsureDBSetup() { using (var cnn = new SqlConnection(BenchmarkBase.ConnectionString)) { cnn.Open(); var cmd = cnn.CreateCommand(); cmd.CommandText = @" If (Object_Id('Posts') Is Null) Begin Create Table Posts ( Id int identity primary key, [Text] varchar(max) not null, CreationDate datetime not null, LastChangeDate datetime not null, Counter1 int, Counter2 int, Counter3 int, Counter4 int, Counter5 int, Counter6 int, Counter7 int, Counter8 int, Counter9 int ); Set NoCount On; Declare @i int = 0; While @i <= 5001 Begin Insert Posts ([Text],CreationDate, LastChangeDate) values (replicate('x', 2000), GETDATE(), GETDATE()); Set @i = @i + 1; End End "; cmd.Connection = cnn; cmd.ExecuteNonQuery(); } } public static void WriteLineColor(string message, ConsoleColor color) { var orig = ForegroundColor; ForegroundColor = color; WriteLine(message); ForegroundColor = orig; } public static void WriteColor(string message, ConsoleColor color) { var orig = ForegroundColor; ForegroundColor = color; Write(message); ForegroundColor = orig; } } } ================================================ FILE: benchmarks/Dapper.Tests.Performance/SqlDataReaderHelper.cs ================================================ using System; using Microsoft.Data.SqlClient; using System.Runtime.CompilerServices; namespace Dapper.Tests.Performance { public static class SqlDataReaderHelper { [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string GetNullableString(this SqlDataReader reader, int index) { object tmp = reader.GetValue(index); if (tmp != DBNull.Value) { return (string)tmp; } return null; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T? GetNullableValue(this SqlDataReader reader, int index) where T : struct { object tmp = reader.GetValue(index); if (tmp != DBNull.Value) { return (T)tmp; } return null; } } } ================================================ FILE: benchmarks/Dapper.Tests.Performance/XPO/Post.cs ================================================ using System; using DevExpress.Xpo; namespace Dapper.Tests.Performance.Xpo { [Persistent("Posts")] public class Post : XPLiteObject { [Key(false)] public int Id { get; set; } public string Text { get; set; } public DateTime CreationDate { get; set; } public DateTime LastChangeDate { get; set; } public int? Counter1 { get; set; } public int? Counter2 { get; set; } public int? Counter3 { get; set; } public int? Counter4 { get; set; } public int? Counter5 { get; set; } public int? Counter6 { get; set; } public int? Counter7 { get; set; } public int? Counter8 { get; set; } public int? Counter9 { get; set; } public Post(Session session) : base(session) { } } } ================================================ FILE: benchmarks/Dapper.Tests.Performance/app.config ================================================  ================================================ FILE: benchmarks/Directory.Build.props ================================================ false false false false $(DefineConstants);WINDOWS ================================================ FILE: build.cmd ================================================ @ECHO OFF PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0build.ps1' %*; exit $LASTEXITCODE" ================================================ FILE: build.ps1 ================================================ [CmdletBinding(PositionalBinding=$false)] param( [bool] $CreatePackages, [bool] $RunTests = $true, [string] $PullRequestNumber ) Write-Host "Run Parameters:" -ForegroundColor Cyan Write-Host " CreatePackages: $CreatePackages" Write-Host " RunTests: $RunTests" Write-Host " dotnet --version:" (dotnet --version) $packageOutputFolder = "$PSScriptRoot\.nupkgs" if ($PullRequestNumber) { Write-Host "Building for a pull request (#$PullRequestNumber), skipping packaging." -ForegroundColor Yellow $CreatePackages = $false } Write-Host "Building all projects (Build.csproj traversal)..." -ForegroundColor "Magenta" dotnet build ".\Build.csproj" -c Release /p:CI=true Write-Host "Done building." -ForegroundColor "Green" if ($RunTests) { Write-Host "Running tests: Build.csproj" -ForegroundColor "Magenta" dotnet test ".\Build.csproj" -c Release --no-build -p:TestTfmsInParallel=false if ($LastExitCode -ne 0) { Write-Host "Error with tests, aborting build." -Foreground "Red" Exit 1 } Write-Host "Tests passed!" -ForegroundColor "Green" } if ($CreatePackages) { New-Item -ItemType Directory -Path $packageOutputFolder -Force | Out-Null Write-Host "Clearing existing $packageOutputFolder..." -NoNewline Get-ChildItem $packageOutputFolder | Remove-Item Write-Host "done." -ForegroundColor "Green" Write-Host "Building all packages" -ForegroundColor "Green" dotnet pack ".\Build.csproj" --no-build -c Release /p:PackageOutputPath=$packageOutputFolder /p:CI=true } Write-Host "Build Complete." -ForegroundColor "Green" ================================================ FILE: docs/_config.yml ================================================ theme: jekyll-theme-cayman ================================================ FILE: docs/dapperplus.md ================================================ # Dapper and Dapper Plus Dapper is the micro-ORM developed initially by Stack Overflow and now maintained independently, that offers simple, high performance access to the ADO.NET API. Dapper Plus is a separate tool by ZZZ Projects, which builds on the path set by Dapper, offering features like bulk operations, and a range of [documentation for Dapper](https://www.learndapper.com/). From 2024, Dapper Plus is now a major sponsor of Dapper, helping to secure ongoing quality development and support of the Dapper platform. This sponsorship does not impact the ownership, license, or any other particulars of how Dapper operates. The core Dapper libraries continue to be freely available and fully open source. Dapper Plus logo ================================================ FILE: docs/docs.csproj ================================================  net8.0 ================================================ FILE: docs/index.md ================================================ # Dapper - a simple object mapper for .NET ## Overview A brief guide is available [on github](https://github.com/DapperLib/Dapper/blob/main/Readme.md) Questions on Stack Overflow should be tagged [`dapper`](https://stackoverflow.com/questions/tagged/dapper) ## Installation From NuGet: Install-Package Dapper or Install-Package Dapper.StrongName Note: to get the latest pre-release build, add ` -Pre` to the end of the command. ## Release Notes **RELEASE NOTE TRACKING HAS MOVED TO GITHUB** See: https://github.com/DapperLib/Dapper/releases Archive only (no new entries): ### 2.1.11 (note: new PRs will not be merged until they add release note wording here) - infer command text without any whitespace as stored-procedure (#1975 via @mgravell) - add global `SupportLegacyParameterTokens` setting to enable or disable single-character parameter tokens (#1974 via @Giorgi) - revert `$` addition for legacy parameter tokens (#1979 via @mgravell) - change NRT annotation on `GetConstructorParameter` (#1980 via @mgravell, fixes #1969) ### 2.1.4 - add untyped `GridReader.ReadUnbufferedAsync` API (#1958 via @mgravell) - tweak NRT annotations on type-handler API (#1960 via @mgravell, fixes #1959) ### 2.1.1 - add NRT annotations (#1928 via @mgravell) - extend `GridReader` API to allow it to be subclassed by external consumers (#1928 via @mgravell) - support `$` as a parameter prefix (#1952 via @Giorgi) - add public API tracking (#1948 via @mgravell) ### 2.0.151 - add global `FetchSize` setting for use with Oracle (#1946 via mgravell, fixes #1945) (also add some missing logic in `Settings.Reset()`) - add underscore handling with constructors (#1786 via @jo-goro, fixes #818; also #1947 via mgravell) ### 2.0.143 - add missing non-generic `AsyncEnumerable QueryUnbufferedAsync(...)` API (#1925 via mgravell, fixes #1922) - formally mark all `struct` types as `readonly` (#1925 via mgravell) - reinstate fallback support for `IDataReader`, and implement missing `DbDataReader` async APIs (#1913 via mgravell) ### 2.0.138 - (#1910 via mgravell, fix #1907, #1263) - add support for `SqlDecimal` and other types that need to be accessed via `DbDataReader.GetFieldValue` - add an overload of `AddTypeMap` that supports `DbDataReader.GetFieldValue` for additional types - acknowledge that in reality we only support `DbDataReader`; this has been true (via `DbConnection`) for `async` forever - (#1912 via mgravell) - add missing `AsyncEnumerable QueryUnbufferedAsync(...)` and `GridReader.ReadUnbufferedAsync(...)` APIs (.NET 5 and later) - implement `IAsyncDisposable` on `GridReader` (.NET 5 and later) ### 2.0.123 - Parameters can now be re-used on subsequent commands (#952 via jamescrowley) - Array query support (`.Query`) on supported platforms (e.g. Postgres) (#1598 via DarkWanderer) - `SqlMapper.HasTypeHandler` is made public for consumers (#1405 via brendangooden) - Improves multi-mapping error message when a specified column in splitOn can't be found (#1664 via NickCraver) - Improves `DbString.ToString()` (#1665 via NickCraver) - `DbType` for date/time types is no longer explicitly specified (resolves `Npgsql` v6 issue) - add `Settings.UseIncrementalPseudoPositionalParameterNames`, to support "snowflake" parameter naming conventions ### 2.0.90 - logo added; license updated to mention logo usage (via mgravell) - moved to DapperLib org; links updated (#1656) - RepoDb benchmark added (#1626 via stevedesmond-ca) - excise unrelated Soma tests (#1642 via kant2002) - SqlMarshl benchmark added (#1646 via kant2002) - documentation fixes (#1615 via Rollerss, #1604 via GitHubPang) ### 2.0.78 - fix `DynamicParameters` loop bug - wrong index (#1443 via DamirAinullin) - fix nullable tuple handling (#1400 via JulianRooze) - support update set in `SqlBuilder` (#1404 via Wei) - initialize collections with counts when possible (#1449 via DamirAinullin) - general code cleanup (#1452, #1457, #1458, #1459 all via DamirAinullin) - C# 9 and .NET 5 preparation/cleanup (#1572 via mgravell) - GitHub action, Docker, AppVeyor work (build/test) work (#1563 via Tyrrrz, #1559 via craver, #1450 via craver) - Test project rationalization (#1556 via craver) - ClickHouse detection (#1462 via DarkWanderer) - Switched to "main" branch (via craver) - documentation fixed (#1596 via royal, #1560 via wswind, #1558 via paul42, #1507 via imba-tjd, #1508 via dogac00, 899c9feb via BlackjacketMack, 0b17133 via BlackjacketMack, 9b6c8c7d via bryancrosby, #1202 via craver) - MightyOrm benchmark added (455b3f3b via cdonnellytx) - EF6 performance test updates (#1361 via AlexBagnolini) ### 2.0.35 - build tooling: enable "deterministic builds" and enable SDK roll-foward - fix culture related formatting/parsing issue with Sqlite (#1363 via sebastienros) - documentation fixes (#1357 via jawn) - add tests for `SqlBuilder` (#1369 via shps951023) ### 2.0.30 - upstream library updates; project (build) cleanup - reinstated net461 build target - add Dapper.ProviderTools library (to help with System vs Microsoft SqlClient migration, etc) - fix double dictionary lookup (#1339 via DamirAinullin) - fix bug with dynamic parameters accessing the wrong member (#1334 via DamirAinullin) - fix explicit-key issue with `DeleteAsync` (#1309 via james-hester-ah) - fix for `char` on Postgres (#1326 via jjonescz) - documentation fixes (#1340 via jawn) - test and benchmark fixes (#1337 via DamirAinullin, #1206 via yesmey, #1331 via andresrsanchez, #1335 via DamirAinullin) ### 2.0.4 Primary changes: - remove the System.Data.SqlClient dependency, allowing consumers to use System.Data.SqlClient or Microsoft.Data.SqlClient (or neither, or both) as they choose - this means that some users may need to *re-add* one of the above as a `` for their project to build, if they were previously relying on Dapper to provide System.Data.SqlClient - the `AsTableValuedParameter(this IEnumerable)` extension method is now `AsTableValuedParameter(this IEnumerable) where T : IDataRecord`; this is a breaking change but should be code-compatible and just requires a rebuild - unify the target platform at NetStandard2.0 (and .NET Framework 4.6.2 for the EF DB geometry/geography types) - fix bug with `Identity` not enforcing type identity of multi-mapped types Other changes merged: - fix #1242, #1280, #1282 - fix value-tuple mapping - fix #1295 - add `ExecuteReaderAsync` overload to expose `DbDataReader` - fix #569 - handing of `IN` and similar clauses in some scenarios - fix #1256 - make `Dispose()` polymorphic in "rainbow" - fix #1257 - make the `.Connection` available in "rainbow" ### 1.60.6 - improve performance of descriptor API ### 1.60.5 - add descriptor API to `DapperRow` (enables UI binding with non-generic `Query()` API) ### 1.60.1 - Fix [#1196](https://github.com/DapperLib/Dapper/issues/1196) - versioning fix only ([#1198](https://github.com/DapperLib/Dapper/pull/1198)) - assembly version is now locked at 1.60.0 to resolve some mismatch issues with .NET Core assembly loading/binding. ### 1.50.7 - Fix [#1190](https://github.com/DapperLib/Dapper/issues/1190) - incorrect unmanaged pointer when processing parameters that are a boxed struct (rare error relating to GC) - Fix [#1111](https://github.com/DapperLib/Dapper/issues/1111) - make `SqlMapper.Parse` consistent with `QueryImpl` - Fix #111- - improve error message for invalid literal types - Fix [#1149](https://github.com/DapperLib/Dapper/pull/1149) - improve error messages in "contrib" - Improved detection of empty table-valued-parameters ### 1.50.5 - Fixes empty result set hanging with `QueryAsync` - `DapperRow` now implements `IReadOnlyDictionary` - Improved error messages for `Async` when the provided `IDbConnection` is not a `DbConnection` - Contrib: `GetAll` now handles nullable types ### 1.50.4 - Added back missing .NET Standard functionality (restored in `netstandard2.0`) - Bumped `SqlClient` dependency to 4.4.0 (to help propagate the newer client) ### 1.50.2 - Fix issue [#569](https://github.com/DapperLib/Dapper/issues/569) (`in` expansions using ODBC pseudo-positional arguments) ### 1.50.1 - Change to how `string_split` is used for `InListStringSplitCount` ### 1.50.0 - No changes; stable release ### 1.50.0-rc3 - Updated for .Net Core RTM package dependencies ### 1.50.0-rc2b - New `InListStringSplitCount` global setting; if set (non-negative), `in @foo` expansions (of at least the specified size) of primitive types (`int`, `tinyint`, `smallint`, `bigint`) are implemented via the SQL Server 2016 (compat level 130) `STRING_SPLIT` function - Fix for incorrect conversions in `GridReader` ([#254](https://github.com/DapperLib/Dapper/issues/254)) ### 1.50.0-rc2 / 1.50.0-rc2a - Packaging for .NET Core rc2 ### 1.50-beta9 - Fix for `PadListExpansions` to work correctly with `not in` scenarios; now uses last non-null value instead of `null`; if none available, don't pad - Fix problems with single-result/single-row not being supported by all providers (basically: sqlite, [#466](https://github.com/DapperLib/Dapper/issues/466)) - Fix problems with enums - nulls ([#467](https://github.com/DapperLib/Dapper/issues/467)) and primitive values ([#468](https://github.com/DapperLib/Dapper/issues/468)) - Add support for C# 6 get-only properties ([#473](https://github.com/DapperLib/Dapper/issues/473)) - Add support for various xml types ([#427](https://github.com/DapperLib/Dapper/issues/427)) ### 1.50-beta8 - Addition of `GetRowParser` extension method on `IDataReader` API - allows manual construction of discriminated unions, etc - Addition of `Settings.PadListExpansions` - reduces query-plan saturation by padding list expansions with `null` values (opt-in, because on some DB configurations this could change the meaning) *(note: bad choice of `null` revised in 1.50-beta9)* - Addition of `Settings.ApplyNullValues` - assigns (rather than ignores) `null` values when possible - Fix for [#461](https://github.com/DapperLib/Dapper/issues/461) - ensure type-handlers work for constructor-based initialization - Fix for [#455](https://github.com/DapperLib/Dapper/issues/455) - make the `LookupDbType` method available again ### 1.50-beta7 - Addition of `GetRowParser(Type)` (and refactor the backing store for readers to suit) - Column hash should consider type, not just name ### 1.50-beta6 - Fix for issue [#424](https://github.com/DapperLib/Dapper/issues/424) - defensive `SqlDataRecord` handling ### 1.50-beta5 - Add "single", "first", "single or default" to complement the "first or default" options from 1.50-beta4 - Use single-row/single-result when possible - Fix for proxy-generator (issue #361) ### 1.50-beta4 - Add `QueryFirstOrDefault` / `ReadFirstOrDefault` methods that optimize the single-row scenario - Remove some legacy `dynamic` usage from the async API - Make `DynamicTypeMap` public again (error during core-clr migration) - Use `Hashtable` again on core-clr ### 1.50-beta3 - Core CLR support: add explicit `dnx451` support in addition to `dotnet5.4` (aka `netstandard1.4`) ### 1.50-beta2 - Core CLR now targets rc1 / 23516 - Various Core CLR fixes - Code cleanup and C# 6 usage (assorted) ### 1.50-beta1 - Split `SqlMapper.cs` as it was becoming too unmaintainable; NuGet is now the only supported deployment channel - Remove down-level C# requirements, as "drop in the file" is no longer the expected usage - `SqlMapper.Settings` added; provides high-level global configuration; initially `CommandTimeout` (@Irrational86) - improve error message if an array is used as a parameter in an invalid context - Add `Type[]` support for `GridReader.Read` scenarios (@NikolayGlynchak) - Support for custom type-maps in collection parameters (@gjsduarte) - Fix incorrect cast in `QueryAsync` (@phnx47, [#346](https://github.com/DapperLib/Dapper/issues/346)) - Fix incorrect null handling re `UdtTypeName` (@perliedman) - Support for `SqlDataRecord` (@sqmgh) - Allow `DbString` default for `IsAnsi` to be specified (@kppullin) - provide `TypeMapProvider` with lazy func-based initialization (@garyhuntddn) - Core-clr updated to beta-8 and various cleanups/fixes - Built using core-clr build tools ### 1.42 - Fix bug with dynamic parameters where `.Get` is called before the command is executed ### 1.41-beta5 - Core-clr packaging build and workarounds - Fix bug with literal `{=val}` boolean replacements ### 1.41-beta4 - Core-clr packaging build - Improve mapping to enum members (@BrianJolly) ### 1.41-beta - Core-clr packaging build ### 1.41-alpha - Introduces dnx (core-clr) experimental changes - Adds `SqlBuilder` project - Improve error message when incorrectly accessing parameter values ### 1.40 - Workaround for broken `GetValues()` on Mono; add `AsList()` ### 1.39 - Fix case on SQL CLR types; grid-reader should respect no-cache flags; make parameter inclusion case-insensitive ### 1.38 - Specify constructor explicitly; allow value-type parameters (albeit: boxed) ### 1.37 - Reuse StringBuilder instances when possible (list parameters in particular) ### 1.36 - Fix Issue [#192](https://github.com/DapperLib/Dapper/issues/192) (expanded parameter naming glitch) and Issue [#178](https://github.com/DapperLib/Dapper/issues/178) (execute reader now wraps the command/reader pair, to extend the command lifetime; note that the underlying command/reader are available by casting to `IWrappedDataReader`) ### 1.35 - Fix Issue [#151](https://github.com/DapperLib/Dapper/issues/151) (Execute should work with `ExpandoObject` etc); Fix Issue #182 (better support for db-type when using `object` values); - Output expressions / callbacks in dynamic args (via Derek); arbitrary number of types in multi-mapping (via James Holwell); - Fix `DbString`/Oracle bug (via Mauro Cerutti); new support for **named positional arguments** ### 1.34 - Support for `SqlHierarchyId` (core) ### 1.33 - Support for `SqlGeometry` (core) and `DbGeometry` (EF) ### 1.32 - Support for `SqlGeography` in core library ### 1.31 - Fix issue with error message when there is a column/type mismatch ### 1.30 - Better async cancellation ### 1.29 - Make underscore name matching optional (opt-in) - this can be a breaking change for some people ### 1.28 - Much better numeric type conversion; fix for large oracle strings; map `Foo_Bar` to `FooBar` (etc); `ExecuteScalar` added; stability fixes ### 1.27 - Fixes for type-handler parse; ensure type-handlers get last dibs on configuring parameters ### 1.26 - New type handler API for extension support ### 1.25 - Command recycling and disposing during pipelined async multi-exec; enable pipeline (via sync-over-async) for sync API" ================================================ FILE: docs/readme.md ================================================ # Dapper Dapper is a simple micro-ORM used to simplify working with ADO.NET; if you like SQL but dislike the boilerplate of ADO.NET: Dapper is for you! As a simple example: ``` c# string region = ... var customers = connection.Query( "select * from Customers where Region = @region", // SQL new { region } // parameters ).AsList(); ``` But all the execute/single-row/scalar/async/etc functionality you would expect: is there as extension methods on your `DbConnection`. See [GitHub](https://github.com/DapperLib/Dapper) for more information and examples. Sponsors -------- Dapper was originally developed for and by Stack Overflow, but is F/OSS. Sponsorship is welcome and invited - see the sponsor link at the top of the page. A huge thanks to everyone (individuals or organisations) who have sponsored Dapper, but a massive thanks in particular to: - [Dapper Plus](https://dapper-plus.net/) is a major sponsor and is proud to contribute to the development of Dapper ([read more](https://dapperlib.github.io/Dapper/dapperplus)) - [AWS](https://github.com/aws) who sponsored Dapper from Oct 2023 via the [.NET on AWS Open Source Software Fund](https://github.com/aws/dotnet-foss) [![Dapper Plus logo](https://raw.githubusercontent.com/DapperLib/Dapper/main/docs/dapper-sponsor.png)](https://dapper-plus.net/) ================================================ FILE: global.json ================================================ { "sdk": { "version": "10.0.102", "rollForward": "latestMajor" } } ================================================ FILE: nuget.config ================================================  ================================================ FILE: signatures/version1/cla.json ================================================ { "signedContributors": [] } ================================================ FILE: tests/Dapper.Tests/App.config ================================================  ================================================ FILE: tests/Dapper.Tests/AsyncTests.cs ================================================ using System; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; namespace Dapper.Tests { [Collection(NonParallelDefinition.Name)] public sealed class SystemSqlClientAsyncTests : AsyncTests { } #if MSSQLCLIENT [Collection(NonParallelDefinition.Name)] public sealed class MicrosoftSqlClientAsyncTests : AsyncTests { } #endif [Collection(NonParallelDefinition.Name)] public sealed class SystemSqlClientAsyncQueryCacheTests : AsyncQueryCacheTests { public SystemSqlClientAsyncQueryCacheTests(ITestOutputHelper log) : base(log) { } } #if MSSQLCLIENT [Collection(NonParallelDefinition.Name)] public sealed class MicrosoftSqlClientAsyncQueryCacheTests : AsyncQueryCacheTests { public MicrosoftSqlClientAsyncQueryCacheTests(ITestOutputHelper log) : base(log) { } } #endif public abstract class AsyncTests : TestBase where TProvider : SqlServerDatabaseProvider { private DbConnection? _marsConnection; private DbConnection MarsConnection => _marsConnection ??= Provider.GetOpenConnection(true); [Fact] public async Task TestBasicStringUsageAsync() { var query = await connection.QueryAsync("select 'abc' as [Value] union all select @txt", new { txt = "def" }).ConfigureAwait(false); var arr = query.ToArray(); Assert.Equal(new[] { "abc", "def" }, arr); } [Fact] public async Task TestBasicStringUsageUnbufferedDynamicAsync() { var results = new List(); await foreach (var row in connection.QueryUnbufferedAsync("select 'abc' as [Value] union all select @txt", new { txt = "def" }) .ConfigureAwait(false)) { string value = row.Value; results.Add(value); } var arr = results.ToArray(); Assert.Equal(new[] { "abc", "def" }, arr); } [Fact] public async Task TestBasicStringUsageUnbufferedAsync() { var results = new List(); await foreach (var value in connection.QueryUnbufferedAsync("select 'abc' as [Value] union all select @txt", new { txt = "def" }) .ConfigureAwait(false)) { results.Add(value); } var arr = results.ToArray(); Assert.Equal(new[] { "abc", "def" }, arr); } [Fact] public async Task TestBasicStringUsageUnbufferedAsync_Cancellation() { using var cts = new CancellationTokenSource(); var results = new List(); await Assert.ThrowsAnyAsync(async () => { await foreach (var value in connection.QueryUnbufferedAsync("select 'abc' as [Value] union all select @txt", new { txt = "def" }) .ConfigureAwait(false).WithCancellation(cts.Token)) { results.Add(value); cts.Cancel(); // cancel after first item } }); var arr = results.ToArray(); Assert.Equal(new[] { "abc" }, arr); // we don't expect the "def" because of the cancellation } [Fact] public async Task TestBasicStringUsageViaGridReaderUnbufferedAsync() { var results = new List(); await using (var grid = await connection.QueryMultipleAsync("select 'abc' union select 'def'; select @txt", new { txt = "ghi" }) .ConfigureAwait(false)) { while (!grid.IsConsumed) { await foreach (var value in grid.ReadUnbufferedAsync() .ConfigureAwait(false)) { results.Add(value); } } } var arr = results.ToArray(); Assert.Equal(new[] { "abc", "def", "ghi" }, arr); } [Fact] public async Task TestBasicStringUsageViaGridReaderUnbufferedDynamicAsync() { var results = new List(); await using (var grid = await connection.QueryMultipleAsync("select 'abc' as [Foo] union select 'def'; select @txt as [Foo]", new { txt = "ghi" }) .ConfigureAwait(false)) { while (!grid.IsConsumed) { await foreach (var value in grid.ReadUnbufferedAsync() .ConfigureAwait(false)) { results.Add((string)value.Foo); } } } var arr = results.ToArray(); Assert.Equal(new[] { "abc", "def", "ghi" }, arr); } [Fact] public async Task TestBasicStringUsageViaGridReaderUnbufferedAsync_Cancellation() { using var cts = new CancellationTokenSource(); var results = new List(); await using (var grid = await connection.QueryMultipleAsync("select 'abc' union select 'def'; select @txt", new { txt = "ghi" }) .ConfigureAwait(false)) { var ex = await Assert.ThrowsAnyAsync(async () => { while (!grid.IsConsumed) { await foreach (var value in grid.ReadUnbufferedAsync() .ConfigureAwait(false) .WithCancellation(cts.Token)) { results.Add(value); } cts.Cancel(); } }); Assert.True(ex is OperationCanceledException or DbException { Message: "Operation cancelled by user." }); } var arr = results.ToArray(); Assert.Equal(new[] { "abc", "def" }, arr); // don't expect the ghi because of cancellation } [Fact] public async Task TestBasicStringUsageQueryFirstAsync() { var str = await connection.QueryFirstAsync(new CommandDefinition("select 'abc' as [Value] union all select @txt", new { txt = "def" })).ConfigureAwait(false); Assert.Equal("abc", str); } [Fact] public async Task TestBasicStringUsageQueryFirstAsyncDynamic() { var str = await connection.QueryFirstAsync("select 'abc' as [Value] union all select @txt", new { txt = "def" }).ConfigureAwait(false); Assert.Equal("abc", str.Value); } [Fact] public async Task TestBasicStringUsageQueryFirstOrDefaultAsync() { var str = await connection.QueryFirstOrDefaultAsync(new CommandDefinition("select null as [Value] union all select @txt", new { txt = "def" })).ConfigureAwait(false); Assert.Null(str); } [Fact] public async Task TestBasicStringUsageQueryFirstOrDefaultAsyncDynamic() { var str = await connection.QueryFirstOrDefaultAsync("select null as [Value] union all select @txt", new { txt = "def" }).ConfigureAwait(false); Assert.Null(str!.Value); } [Fact] public async Task TestBasicStringUsageQuerySingleAsyncDynamic() { var str = await connection.QuerySingleAsync(new CommandDefinition("select 'abc' as [Value]")).ConfigureAwait(false); Assert.Equal("abc", str); } [Fact] public async Task TestBasicStringUsageQuerySingleAsync() { var str = await connection.QuerySingleAsync("select 'abc' as [Value]").ConfigureAwait(false); Assert.Equal("abc", str.Value); } [Fact] public async Task TestBasicStringUsageQuerySingleOrDefaultAsync() { var str = await connection.QuerySingleOrDefaultAsync(new CommandDefinition("select null as [Value]")).ConfigureAwait(false); Assert.Null(str); } [Fact] public async Task TestBasicStringUsageQuerySingleOrDefaultAsyncDynamic() { var str = (await connection.QuerySingleOrDefaultAsync("select null as [Value]").ConfigureAwait(false))!; Assert.Null(str.Value); } [Fact] public async Task TestBasicStringUsageAsyncNonBuffered() { var query = await connection.QueryAsync(new CommandDefinition("select 'abc' as [Value] union all select @txt", new { txt = "def" }, flags: CommandFlags.None)).ConfigureAwait(false); var arr = query.ToArray(); Assert.Equal(new[] { "abc", "def" }, arr); } [Fact] public void TestLongOperationWithCancellation() { CancellationTokenSource cancel = new(TimeSpan.FromSeconds(5)); var task = connection.QueryAsync(new CommandDefinition("waitfor delay '00:00:10';select 1", cancellationToken: cancel.Token)); try { if (!task.Wait(TimeSpan.FromSeconds(7))) { throw new TimeoutException(); // should have cancelled } } catch (AggregateException agg) { Assert.Equal("SqlException", agg.InnerException?.GetType().Name); } } [Fact] public async Task TestBasicStringUsageClosedAsync() { using var conn = GetClosedConnection(); var query = await conn.QueryAsync("select 'abc' as [Value] union all select @txt", new { txt = "def" }).ConfigureAwait(false); var arr = query.ToArray(); Assert.Equal(new[] { "abc", "def" }, arr); } [Fact] public async Task TestQueryDynamicAsync() { var row = (await connection.QueryAsync("select 'abc' as [Value]").ConfigureAwait(false)).Single(); string value = row.Value; Assert.Equal("abc", value); } [Fact] public async Task TestClassWithStringUsageAsync() { var query = await connection.QueryAsync("select 'abc' as [Value] union all select @txt", new { txt = "def" }).ConfigureAwait(false); var arr = query.ToArray(); Assert.Equal(new[] { "abc", "def" }, arr.Select(x => x.Value)); } [Fact] public async Task TestExecuteAsync() { var val = await connection.ExecuteAsync("declare @foo table(id int not null); insert @foo values(@id);", new { id = 1 }).ConfigureAwait(false); Assert.Equal(1, val); } [Fact] public void TestExecuteClosedConnAsyncInner() { using var conn = GetClosedConnection(); var query = conn.ExecuteAsync("declare @foo table(id int not null); insert @foo values(@id);", new { id = 1 }); var val = query.Result; Assert.Equal(1, val); } [Fact] public async Task TestMultiMapWithSplitAsync() { const string sql = "select 1 as id, 'abc' as name, 2 as id, 'def' as name"; var productQuery = await connection.QueryAsync(sql, (prod, cat) => { prod.Category = cat; return prod; }).ConfigureAwait(false); var product = productQuery.First(); // assertions Assert.Equal(1, product.Id); Assert.Equal("abc", product.Name); Assert.NotNull(product.Category); Assert.Equal(2, product.Category.Id); Assert.Equal("def", product.Category.Name); } [Fact] public async Task TestMultiMapArbitraryWithSplitAsync() { const string sql = "select 1 as id, 'abc' as name, 2 as id, 'def' as name"; var productQuery = await connection.QueryAsync(sql, new[] { typeof(Product), typeof(Category) }, (objects) => { var prod = (Product)objects[0]; prod.Category = (Category)objects[1]; return prod; }).ConfigureAwait(false); var product = productQuery.First(); // assertions Assert.Equal(1, product.Id); Assert.Equal("abc", product.Name); Assert.NotNull(product.Category); Assert.Equal(2, product.Category.Id); Assert.Equal("def", product.Category.Name); } [Fact] public async Task TestMultiMapWithSplitClosedConnAsync() { const string sql = "select 1 as id, 'abc' as name, 2 as id, 'def' as name"; using var conn = GetClosedConnection(); var productQuery = await conn.QueryAsync(sql, (prod, cat) => { prod.Category = cat; return prod; }).ConfigureAwait(false); var product = productQuery.First(); // assertions Assert.Equal(1, product.Id); Assert.Equal("abc", product.Name); Assert.NotNull(product.Category); Assert.Equal(2, product.Category.Id); Assert.Equal("def", product.Category.Name); } [Fact] public async Task TestMultiAsync() { using SqlMapper.GridReader multi = await connection.QueryMultipleAsync("select 1; select 2").ConfigureAwait(false); Assert.Equal(1, multi.ReadAsync().Result.Single()); Assert.Equal(2, multi.ReadAsync().Result.Single()); } [Fact] public async Task TestMultiConversionAsync() { using SqlMapper.GridReader multi = await connection.QueryMultipleAsync("select Cast(1 as BigInt) Col1; select Cast(2 as BigInt) Col2").ConfigureAwait(false); Assert.Equal(1, multi.ReadAsync().Result.Single()); Assert.Equal(2, multi.ReadAsync().Result.Single()); } [Fact] public async Task TestMultiAsyncViaFirstOrDefault() { using SqlMapper.GridReader multi = await connection.QueryMultipleAsync("select 1; select 2; select 3; select 4; select 5").ConfigureAwait(false); Assert.Equal(1, multi.ReadFirstOrDefaultAsync().Result); Assert.Equal(2, multi.ReadAsync().Result.Single()); Assert.Equal(3, multi.ReadFirstOrDefaultAsync().Result); Assert.Equal(4, multi.ReadAsync().Result.Single()); Assert.Equal(5, multi.ReadFirstOrDefaultAsync().Result); } [Fact] public async Task TestMultiClosedConnAsync() { using var conn = GetClosedConnection(); using SqlMapper.GridReader multi = await conn.QueryMultipleAsync("select 1; select 2").ConfigureAwait(false); Assert.Equal(1, multi.ReadAsync().Result.Single()); Assert.Equal(2, multi.ReadAsync().Result.Single()); } [Fact] public async Task TestMultiClosedConnAsyncViaFirstOrDefault() { using var conn = GetClosedConnection(); using SqlMapper.GridReader multi = await conn.QueryMultipleAsync("select 1; select 2; select 3; select 4; select 5").ConfigureAwait(false); Assert.Equal(1, multi.ReadFirstOrDefaultAsync().Result); Assert.Equal(2, multi.ReadAsync().Result.Single()); Assert.Equal(3, multi.ReadFirstOrDefaultAsync().Result); Assert.Equal(4, multi.ReadAsync().Result.Single()); Assert.Equal(5, multi.ReadFirstOrDefaultAsync().Result); } [Fact] public async Task ExecuteReaderOpenAsync() { var dt = new DataTable(); dt.Load(await connection.ExecuteReaderAsync("select 3 as [three], 4 as [four]").ConfigureAwait(false)); Assert.Equal(2, dt.Columns.Count); Assert.Equal("three", dt.Columns[0].ColumnName); Assert.Equal("four", dt.Columns[1].ColumnName); Assert.Equal(1, dt.Rows.Count); Assert.Equal(3, (int)dt.Rows[0][0]); Assert.Equal(4, (int)dt.Rows[0][1]); } [Fact] public async Task ExecuteReaderClosedAsync() { using var conn = GetClosedConnection(); var dt = new DataTable(); dt.Load(await conn.ExecuteReaderAsync("select 3 as [three], 4 as [four]").ConfigureAwait(false)); Assert.Equal(2, dt.Columns.Count); Assert.Equal("three", dt.Columns[0].ColumnName); Assert.Equal("four", dt.Columns[1].ColumnName); Assert.Equal(1, dt.Rows.Count); Assert.Equal(3, (int)dt.Rows[0][0]); Assert.Equal(4, (int)dt.Rows[0][1]); } [Fact] public async Task LiteralReplacementOpen() { await LiteralReplacement(connection).ConfigureAwait(false); } [Fact] public async Task LiteralReplacementClosed() { using var conn = GetClosedConnection(); await LiteralReplacement(conn).ConfigureAwait(false); } private static async Task LiteralReplacement(IDbConnection conn) { try { await conn.ExecuteAsync("drop table literal1").ConfigureAwait(false); } catch { /* don't care */ } await conn.ExecuteAsync("create table literal1 (id int not null, foo int not null)").ConfigureAwait(false); await conn.ExecuteAsync("insert literal1 (id,foo) values ({=id}, @foo)", new { id = 123, foo = 456 }).ConfigureAwait(false); var rows = new[] { new { id = 1, foo = 2 }, new { id = 3, foo = 4 } }; await conn.ExecuteAsync("insert literal1 (id,foo) values ({=id}, @foo)", rows).ConfigureAwait(false); var count = (await conn.QueryAsync("select count(1) from literal1 where id={=foo}", new { foo = 123 }).ConfigureAwait(false)).Single(); Assert.Equal(1, count); int sum = (await conn.QueryAsync("select sum(id) + sum(foo) from literal1").ConfigureAwait(false)).Single(); Assert.Equal(123 + 456 + 1 + 2 + 3 + 4, sum); } [Fact] public async Task LiteralReplacementDynamicOpen() { await LiteralReplacementDynamic(connection).ConfigureAwait(false); } [Fact] public async Task LiteralReplacementDynamicClosed() { using var conn = GetClosedConnection(); await LiteralReplacementDynamic(conn).ConfigureAwait(false); } private static async Task LiteralReplacementDynamic(IDbConnection conn) { var args = new DynamicParameters(); args.Add("id", 123); try { await conn.ExecuteAsync("drop table literal2").ConfigureAwait(false); } catch { /* don't care */ } await conn.ExecuteAsync("create table literal2 (id int not null)").ConfigureAwait(false); await conn.ExecuteAsync("insert literal2 (id) values ({=id})", args).ConfigureAwait(false); args = new DynamicParameters(); args.Add("foo", 123); var count = (await conn.QueryAsync("select count(1) from literal2 where id={=foo}", args).ConfigureAwait(false)).Single(); Assert.Equal(1, count); } [Fact] public async Task LiteralInAsync() { await connection.ExecuteAsync("create table #literalin(id int not null);").ConfigureAwait(false); await connection.ExecuteAsync("insert #literalin (id) values (@id)", new[] { new { id = 1 }, new { id = 2 }, new { id = 3 }, }).ConfigureAwait(false); var count = (await connection.QueryAsync("select count(1) from #literalin where id in {=ids}", new { ids = new[] { 1, 3, 4 } }).ConfigureAwait(false)).Single(); Assert.Equal(2, count); } [FactLongRunning] public async Task RunSequentialVersusParallelAsync() { var ids = Enumerable.Range(1, 20000).Select(id => new { id }).ToArray(); await MarsConnection.ExecuteAsync(new CommandDefinition("select @id", ids.Take(5), flags: CommandFlags.None)).ConfigureAwait(false); var watch = Stopwatch.StartNew(); await MarsConnection.ExecuteAsync(new CommandDefinition("select @id", ids, flags: CommandFlags.None)).ConfigureAwait(false); watch.Stop(); Console.WriteLine("No pipeline: {0}ms", watch.ElapsedMilliseconds); watch = Stopwatch.StartNew(); await MarsConnection.ExecuteAsync(new CommandDefinition("select @id", ids, flags: CommandFlags.Pipelined)).ConfigureAwait(false); watch.Stop(); Console.WriteLine("Pipeline: {0}ms", watch.ElapsedMilliseconds); } [FactLongRunning] public void RunSequentialVersusParallelSync() { var ids = Enumerable.Range(1, 20000).Select(id => new { id }).ToArray(); MarsConnection.Execute(new CommandDefinition("select @id", ids.Take(5), flags: CommandFlags.None)); var watch = Stopwatch.StartNew(); MarsConnection.Execute(new CommandDefinition("select @id", ids, flags: CommandFlags.None)); watch.Stop(); Console.WriteLine("No pipeline: {0}ms", watch.ElapsedMilliseconds); watch = Stopwatch.StartNew(); MarsConnection.Execute(new CommandDefinition("select @id", ids, flags: CommandFlags.Pipelined)); watch.Stop(); Console.WriteLine("Pipeline: {0}ms", watch.ElapsedMilliseconds); } private class BasicType { public string? Value { get; set; } } [Fact] public async Task TypeBasedViaTypeAsync() { Type type = Common.GetSomeType(); dynamic actual = (await MarsConnection.QueryAsync(type, "select @A as [A], @B as [B]", new { A = 123, B = "abc" }).ConfigureAwait(false)).FirstOrDefault()!; Assert.Equal(((object)actual).GetType(), type); int a = actual.A; string b = actual.B; Assert.Equal(123, a); Assert.Equal("abc", b); } [Fact] public async Task TypeBasedViaTypeAsyncFirstOrDefault() { Type type = Common.GetSomeType(); dynamic actual = (await MarsConnection.QueryFirstOrDefaultAsync(type, "select @A as [A], @B as [B]", new { A = 123, B = "abc" }).ConfigureAwait(false))!; Assert.Equal(((object)actual).GetType(), type); int a = actual.A; string b = actual.B; Assert.Equal(123, a); Assert.Equal("abc", b); } [Fact] public async Task Issue22_ExecuteScalarAsync() { int i = await connection.ExecuteScalarAsync("select 123").ConfigureAwait(false); Assert.Equal(123, i); i = await connection.ExecuteScalarAsync("select cast(123 as bigint)").ConfigureAwait(false); Assert.Equal(123, i); long j = await connection.ExecuteScalarAsync("select 123").ConfigureAwait(false); Assert.Equal(123L, j); j = await connection.ExecuteScalarAsync("select cast(123 as bigint)").ConfigureAwait(false); Assert.Equal(123L, j); int? k = await connection.ExecuteScalarAsync("select @i", new { i = default(int?) }).ConfigureAwait(false); Assert.Null(k); } [Fact] public async Task Issue346_QueryAsyncConvert() { int i = (await connection.QueryAsync("Select Cast(123 as bigint)").ConfigureAwait(false)).First(); Assert.Equal(123, i); } [Fact] public async Task TestSupportForDynamicParametersOutputExpressionsAsync() { { var bob = new Person { Name = "bob", PersonId = 1, Address = new Address { PersonId = 2, Index = new Index() } }; var p = new DynamicParameters(bob); p.Output(bob, b => b.PersonId); p.Output(bob, b => b.Occupation); p.Output(bob, b => b.NumberOfLegs); p.Output(bob, b => b.Address!.Name); p.Output(bob, b => b.Address!.PersonId); p.Output(bob, b => b.Address!.Index!.Id); await connection.ExecuteAsync(@" SET @Occupation = 'grillmaster' SET @PersonId = @PersonId + 1 SET @NumberOfLegs = @NumberOfLegs - 1 SET @AddressName = 'bobs burgers' SET @AddressPersonId = @PersonId SET @AddressIndexId = '01088'", p).ConfigureAwait(false); Assert.Equal("grillmaster", bob.Occupation); Assert.Equal(2, bob.PersonId); Assert.Equal(1, bob.NumberOfLegs); Assert.Equal("bobs burgers", bob.Address.Name); Assert.Equal(2, bob.Address.PersonId); Assert.Equal("01088", bob.Address.Index.Id); } } [Fact] public async Task TestSupportForDynamicParametersOutputExpressions_ScalarAsync() { var bob = new Person { Name = "bob", PersonId = 1, Address = new Address { PersonId = 2 } }; var p = new DynamicParameters(bob); p.Output(bob, b => b.PersonId); p.Output(bob, b => b.Occupation); p.Output(bob, b => b.NumberOfLegs); p.Output(bob, b => b.Address!.Name); p.Output(bob, b => b.Address!.PersonId); var result = (int)(await connection.ExecuteScalarAsync(@" SET @Occupation = 'grillmaster' SET @PersonId = @PersonId + 1 SET @NumberOfLegs = @NumberOfLegs - 1 SET @AddressName = 'bobs burgers' SET @AddressPersonId = @PersonId select 42", p).ConfigureAwait(false))!; Assert.Equal("grillmaster", bob.Occupation); Assert.Equal(2, bob.PersonId); Assert.Equal(1, bob.NumberOfLegs); Assert.Equal("bobs burgers", bob.Address.Name); Assert.Equal(2, bob.Address.PersonId); Assert.Equal(42, result); } [Fact] public async Task TestSupportForDynamicParametersOutputExpressions_Query_Default() { var bob = new Person { Name = "bob", PersonId = 1, Address = new Address { PersonId = 2 } }; var p = new DynamicParameters(bob); p.Output(bob, b => b.PersonId); p.Output(bob, b => b.Occupation); p.Output(bob, b => b.NumberOfLegs); p.Output(bob, b => b.Address!.Name); p.Output(bob, b => b.Address!.PersonId); var result = (await connection.QueryAsync(@" SET @Occupation = 'grillmaster' SET @PersonId = @PersonId + 1 SET @NumberOfLegs = @NumberOfLegs - 1 SET @AddressName = 'bobs burgers' SET @AddressPersonId = @PersonId select 42", p).ConfigureAwait(false)).Single(); Assert.Equal("grillmaster", bob.Occupation); Assert.Equal(2, bob.PersonId); Assert.Equal(1, bob.NumberOfLegs); Assert.Equal("bobs burgers", bob.Address.Name); Assert.Equal(2, bob.Address.PersonId); Assert.Equal(42, result); } [Fact] public async Task TestSupportForDynamicParametersOutputExpressions_QueryFirst() { var bob = new Person { Name = "bob", PersonId = 1, Address = new Address { PersonId = 2 } }; var p = new DynamicParameters(bob); p.Output(bob, b => b.PersonId); p.Output(bob, b => b.Occupation); p.Output(bob, b => b.NumberOfLegs); p.Output(bob, b => b.Address!.Name); p.Output(bob, b => b.Address!.PersonId); var result = (await connection.QueryFirstAsync(@" SET @Occupation = 'grillmaster' SET @PersonId = @PersonId + 1 SET @NumberOfLegs = @NumberOfLegs - 1 SET @AddressName = 'bobs burgers' SET @AddressPersonId = @PersonId select 42", p).ConfigureAwait(false)); Assert.Equal("grillmaster", bob.Occupation); Assert.Equal(2, bob.PersonId); Assert.Equal(1, bob.NumberOfLegs); Assert.Equal("bobs burgers", bob.Address.Name); Assert.Equal(2, bob.Address.PersonId); Assert.Equal(42, result); } [Fact] public async Task TestSupportForDynamicParametersOutputExpressions_Query_BufferedAsync() { var bob = new Person { Name = "bob", PersonId = 1, Address = new Address { PersonId = 2 } }; var p = new DynamicParameters(bob); p.Output(bob, b => b.PersonId); p.Output(bob, b => b.Occupation); p.Output(bob, b => b.NumberOfLegs); p.Output(bob, b => b.Address!.Name); p.Output(bob, b => b.Address!.PersonId); var result = (await connection.QueryAsync(new CommandDefinition(@" SET @Occupation = 'grillmaster' SET @PersonId = @PersonId + 1 SET @NumberOfLegs = @NumberOfLegs - 1 SET @AddressName = 'bobs burgers' SET @AddressPersonId = @PersonId select 42", p, flags: CommandFlags.Buffered)).ConfigureAwait(false)).Single(); Assert.Equal("grillmaster", bob.Occupation); Assert.Equal(2, bob.PersonId); Assert.Equal(1, bob.NumberOfLegs); Assert.Equal("bobs burgers", bob.Address.Name); Assert.Equal(2, bob.Address.PersonId); Assert.Equal(42, result); } [Fact] public async Task TestSupportForDynamicParametersOutputExpressions_Query_NonBufferedAsync() { var bob = new Person { Name = "bob", PersonId = 1, Address = new Address { PersonId = 2 } }; var p = new DynamicParameters(bob); p.Output(bob, b => b.PersonId); p.Output(bob, b => b.Occupation); p.Output(bob, b => b.NumberOfLegs); p.Output(bob, b => b.Address!.Name); p.Output(bob, b => b.Address!.PersonId); var result = (await connection.QueryAsync(new CommandDefinition(@" SET @Occupation = 'grillmaster' SET @PersonId = @PersonId + 1 SET @NumberOfLegs = @NumberOfLegs - 1 SET @AddressName = 'bobs burgers' SET @AddressPersonId = @PersonId select 42", p, flags: CommandFlags.None)).ConfigureAwait(false)).Single(); Assert.Equal("grillmaster", bob.Occupation); Assert.Equal(2, bob.PersonId); Assert.Equal(1, bob.NumberOfLegs); Assert.Equal("bobs burgers", bob.Address.Name); Assert.Equal(2, bob.Address.PersonId); Assert.Equal(42, result); } [Fact] public async Task TestSupportForDynamicParametersOutputExpressions_QueryMultipleAsync() { var bob = new Person { Name = "bob", PersonId = 1, Address = new Address { PersonId = 2 } }; var p = new DynamicParameters(bob); p.Output(bob, b => b.PersonId); p.Output(bob, b => b.Occupation); p.Output(bob, b => b.NumberOfLegs); p.Output(bob, b => b.Address!.Name); p.Output(bob, b => b.Address!.PersonId); int x, y; using (var multi = await connection.QueryMultipleAsync(@" SET @Occupation = 'grillmaster' SET @PersonId = @PersonId + 1 SET @NumberOfLegs = @NumberOfLegs - 1 SET @AddressName = 'bobs burgers' select 42 select 17 SET @AddressPersonId = @PersonId", p).ConfigureAwait(false)) { x = multi.ReadAsync().Result.Single(); y = multi.ReadAsync().Result.Single(); } Assert.Equal("grillmaster", bob.Occupation); Assert.Equal(2, bob.PersonId); Assert.Equal(1, bob.NumberOfLegs); Assert.Equal("bobs burgers", bob.Address.Name); Assert.Equal(2, bob.Address.PersonId); Assert.Equal(42, x); Assert.Equal(17, y); } [Fact] public async Task TestSubsequentQueriesSuccessAsync() { var data0 = (await connection.QueryAsync("select 1 as [Id] where 1 = 0").ConfigureAwait(false)).ToList(); Assert.Empty(data0); var data1 = (await connection.QueryAsync(new CommandDefinition("select 1 as [Id] where 1 = 0", flags: CommandFlags.Buffered)).ConfigureAwait(false)).ToList(); Assert.Empty(data1); var data2 = (await connection.QueryAsync(new CommandDefinition("select 1 as [Id] where 1 = 0", flags: CommandFlags.None)).ConfigureAwait(false)).ToList(); Assert.Empty(data2); data0 = (await connection.QueryAsync("select 1 as [Id] where 1 = 0").ConfigureAwait(false)).ToList(); Assert.Empty(data0); data1 = (await connection.QueryAsync(new CommandDefinition("select 1 as [Id] where 1 = 0", flags: CommandFlags.Buffered)).ConfigureAwait(false)).ToList(); Assert.Empty(data1); data2 = (await connection.QueryAsync(new CommandDefinition("select 1 as [Id] where 1 = 0", flags: CommandFlags.None)).ConfigureAwait(false)).ToList(); Assert.Empty(data2); } private class AsyncFoo0 { public int Id { get; set; } } private class AsyncFoo1 { public int Id { get; set; } } private class AsyncFoo2 { public int Id { get; set; } } [Fact] public async Task TestSchemaChangedViaFirstOrDefaultAsync() { await connection.ExecuteAsync("create table #dog(Age int, Name nvarchar(max)) insert #dog values(1, 'Alf')").ConfigureAwait(false); try { var d = await connection.QueryFirstOrDefaultAsync("select * from #dog").ConfigureAwait(false); Assert.NotNull(d); Assert.Equal("Alf", d.Name); Assert.Equal(1, d.Age); connection.Execute("alter table #dog drop column Name"); d = await connection.QueryFirstOrDefaultAsync("select * from #dog").ConfigureAwait(false); Assert.NotNull(d); Assert.Null(d.Name); Assert.Equal(1, d.Age); } finally { await connection.ExecuteAsync("drop table #dog").ConfigureAwait(false); } } [Fact] public async Task TestMultiMapArbitraryMapsAsync() { // please excuse the trite example, but it is easier to follow than a more real-world one const string createSql = @" create table #ReviewBoards (Id int, Name varchar(20), User1Id int, User2Id int, User3Id int, User4Id int, User5Id int, User6Id int, User7Id int, User8Id int, User9Id int) create table #Users (Id int, Name varchar(20)) insert #Users values(1, 'User 1') insert #Users values(2, 'User 2') insert #Users values(3, 'User 3') insert #Users values(4, 'User 4') insert #Users values(5, 'User 5') insert #Users values(6, 'User 6') insert #Users values(7, 'User 7') insert #Users values(8, 'User 8') insert #Users values(9, 'User 9') insert #ReviewBoards values(1, 'Review Board 1', 1, 2, 3, 4, 5, 6, 7, 8, 9) "; await connection.ExecuteAsync(createSql).ConfigureAwait(false); try { const string sql = @" select rb.Id, rb.Name, u1.*, u2.*, u3.*, u4.*, u5.*, u6.*, u7.*, u8.*, u9.* from #ReviewBoards rb inner join #Users u1 on u1.Id = rb.User1Id inner join #Users u2 on u2.Id = rb.User2Id inner join #Users u3 on u3.Id = rb.User3Id inner join #Users u4 on u4.Id = rb.User4Id inner join #Users u5 on u5.Id = rb.User5Id inner join #Users u6 on u6.Id = rb.User6Id inner join #Users u7 on u7.Id = rb.User7Id inner join #Users u8 on u8.Id = rb.User8Id inner join #Users u9 on u9.Id = rb.User9Id "; var types = new[] { typeof(ReviewBoard), typeof(User), typeof(User), typeof(User), typeof(User), typeof(User), typeof(User), typeof(User), typeof(User), typeof(User) }; Func mapper = (objects) => { var board = (ReviewBoard)objects[0]; board.User1 = (User)objects[1]; board.User2 = (User)objects[2]; board.User3 = (User)objects[3]; board.User4 = (User)objects[4]; board.User5 = (User)objects[5]; board.User6 = (User)objects[6]; board.User7 = (User)objects[7]; board.User8 = (User)objects[8]; board.User9 = (User)objects[9]; return board; }; var data = (await connection.QueryAsync(sql, types, mapper).ConfigureAwait(false)).ToList(); var p = data[0]; Assert.Equal(1, p.Id); Assert.Equal("Review Board 1", p.Name); Assert.NotNull(p.User1); Assert.NotNull(p.User2); Assert.NotNull(p.User3); Assert.NotNull(p.User4); Assert.NotNull(p.User5); Assert.NotNull(p.User6); Assert.NotNull(p.User7); Assert.NotNull(p.User8); Assert.NotNull(p.User9); Assert.Equal(1, p.User1.Id); Assert.Equal(2, p.User2.Id); Assert.Equal(3, p.User3.Id); Assert.Equal(4, p.User4.Id); Assert.Equal(5, p.User5.Id); Assert.Equal(6, p.User6.Id); Assert.Equal(7, p.User7.Id); Assert.Equal(8, p.User8.Id); Assert.Equal(9, p.User9.Id); Assert.Equal("User 1", p.User1.Name); Assert.Equal("User 2", p.User2.Name); Assert.Equal("User 3", p.User3.Name); Assert.Equal("User 4", p.User4.Name); Assert.Equal("User 5", p.User5.Name); Assert.Equal("User 6", p.User6.Name); Assert.Equal("User 7", p.User7.Name); Assert.Equal("User 8", p.User8.Name); Assert.Equal("User 9", p.User9.Name); } finally { connection.Execute("drop table #Users drop table #ReviewBoards"); } } [Fact] public async Task Issue157_ClosedReaderAsync() { var args = new { x = 42 }; const string sql = "select 123 as [A], 'abc' as [B] where @x=42"; var row = (await connection.QueryAsync(new CommandDefinition( sql, args, flags: CommandFlags.None)).ConfigureAwait(false)).Single(); Assert.NotNull(row); Assert.Equal(123, row.A); Assert.Equal("abc", row.B); args = new { x = 5 }; Assert.False((await connection.QueryAsync(new CommandDefinition(sql, args, flags: CommandFlags.None)).ConfigureAwait(false)).Any()); } [Fact] public async Task TestAtEscaping() { var id = (await connection.QueryAsync(@" declare @@Name int select @@Name = @Id+1 select @@Name ", new Product { Id = 1 }).ConfigureAwait(false)).Single(); Assert.Equal(2, id); } [Fact] public async Task Issue1281_DataReaderOutOfOrderAsync() { using var reader = await connection.ExecuteReaderAsync("Select 0, 1, 2").ConfigureAwait(false); Assert.True(reader.Read()); Assert.Equal(2, reader.GetInt32(2)); Assert.Equal(0, reader.GetInt32(0)); Assert.Equal(1, reader.GetInt32(1)); Assert.False(reader.Read()); } [Fact] public async Task Issue563_QueryAsyncShouldThrowException() { try { var data = (await connection.QueryAsync("select 1 union all select 2; RAISERROR('after select', 16, 1);").ConfigureAwait(false)).ToList(); Assert.Fail("Expected Exception"); } catch (Exception ex) when (ex.GetType().Name == "SqlException" && ex.Message == "after select") { /* swallow only this */ } } } [Collection(NonParallelDefinition.Name)] public abstract class AsyncQueryCacheTests : TestBase where TProvider : SqlServerDatabaseProvider { private readonly ITestOutputHelper _log; public AsyncQueryCacheTests(ITestOutputHelper log) => _log = log; private DbConnection? _marsConnection; private DbConnection MarsConnection => _marsConnection ??= Provider.GetOpenConnection(true); public override void Dispose() { _marsConnection?.Dispose(); _marsConnection = null; base.Dispose(); } [Fact] public void AssertNoCacheWorksForQueryMultiple() { const int a = 123, b = 456; var cmdDef = new CommandDefinition("select @a; select @b;", new { a, b }, commandType: CommandType.Text, flags: CommandFlags.NoCache); int c, d; SqlMapper.PurgeQueryCache(); int before = SqlMapper.GetCachedSQLCount(); using (var multi = MarsConnection.QueryMultiple(cmdDef)) { c = multi.Read().Single(); d = multi.Read().Single(); } int after = SqlMapper.GetCachedSQLCount(); _log?.WriteLine($"before: {before}; after: {after}"); // too brittle in concurrent tests to assert // Assert.Equal(0, before); // Assert.Equal(0, after); Assert.Equal(123, c); Assert.Equal(456, d); } [Fact] public async Task AssertNoCacheWorksForMultiMap() { const int a = 123, b = 456; var cmdDef = new CommandDefinition("select @a as a, @b as b;", new { a, b }, commandType: CommandType.Text, flags: CommandFlags.NoCache | CommandFlags.Buffered); SqlMapper.PurgeQueryCache(); var before = SqlMapper.GetCachedSQLCount(); Assert.Equal(0, before); await MarsConnection.QueryAsync(cmdDef, splitOn: "b", map: (a, b) => (a, b)); Assert.Equal(0, SqlMapper.GetCachedSQLCount()); } [Fact] public async Task AssertNoCacheWorksForQueryAsync() { const int a = 123, b = 456; var cmdDef = new CommandDefinition("select @a as a, @b as b;", new { a, b }, commandType: CommandType.Text, flags: CommandFlags.NoCache | CommandFlags.Buffered); SqlMapper.PurgeQueryCache(); var before = SqlMapper.GetCachedSQLCount(); Assert.Equal(0, before); await MarsConnection.QueryAsync<(int, int)>(cmdDef); Assert.Equal(0, SqlMapper.GetCachedSQLCount()); } } } ================================================ FILE: tests/Dapper.Tests/ConstructorTests.cs ================================================ using System; using System.Linq; using Xunit; namespace Dapper.Tests { [Collection("ConstructorTests")] public sealed class SystemSqlClientConstructorTests : ConstructorTests { } #if MSSQLCLIENT [Collection("ConstructorTests")] public sealed class MicrosoftSqlClientConstructorTests : ConstructorTests { } #endif public abstract class ConstructorTests : TestBase where TProvider : DatabaseProvider { [Fact] public void TestAbstractInheritance() { var order = connection.Query("select 1 Internal,2 Protected,3 [Public],4 Concrete").First(); Assert.Equal(1, order.Internal); Assert.Equal(2, order.ProtectedVal); Assert.Equal(3, order.Public); Assert.Equal(4, order.Concrete); } [Fact] public void TestMultipleConstructors() { MultipleConstructors mult = connection.Query("select 0 A, 'Dapper' b").First(); Assert.Equal(0, mult.A); Assert.Equal("Dapper", mult.B); } [Fact] public void TestConstructorsWithAccessModifiers() { ConstructorsWithAccessModifiers value = connection.Query("select 0 A, 'Dapper' b").First(); Assert.Equal(1, value.A); Assert.Equal("Dapper!", value.B); } [Fact] public void TestNoDefaultConstructor() { var guid = Guid.NewGuid(); NoDefaultConstructor nodef = connection.Query("select CAST(NULL AS integer) A1, CAST(NULL AS integer) b1, CAST(NULL AS real) f1, 'Dapper' s1, G1 = @id", new { id = guid }).First(); Assert.Equal(0, nodef.A); Assert.Null(nodef.B); Assert.Equal(0, nodef.F); Assert.Equal("Dapper", nodef.S); Assert.Equal(nodef.G, guid); } [Fact] public void TestNoDefaultConstructorWithChar() { const char c1 = 'ą'; const char c3 = 'ó'; NoDefaultConstructorWithChar nodef = connection.Query("select @c1 c1, @c2 c2, @c3 c3", new { c1 = c1, c2 = (char?)null, c3 = c3 }).First(); Assert.Equal(c1, nodef.Char1); Assert.Null(nodef.Char2); Assert.Equal(c3, nodef.Char3); } [Fact] public void TestNoDefaultConstructorWithEnum() { NoDefaultConstructorWithEnum nodef = connection.Query("select cast(2 as smallint) E1, cast(5 as smallint) n1, cast(null as smallint) n2").First(); Assert.Equal(ShortEnum.Two, nodef.E); Assert.Equal(ShortEnum.Five, nodef.NE1); Assert.Null(nodef.NE2); } [Fact] public void ExplicitConstructors() { var rows = connection.Query<_ExplicitConstructors>(@" declare @ExplicitConstructors table ( Field INT NOT NULL PRIMARY KEY IDENTITY(1,1), Field_1 INT NOT NULL); insert @ExplicitConstructors(Field_1) values (1); SELECT * FROM @ExplicitConstructors" ).ToList(); Assert.Single(rows); Assert.Equal(1, rows[0].Field); Assert.Equal(1, rows[0].Field_1); Assert.True(rows[0].GetWentThroughProperConstructor()); } private class _ExplicitConstructors { public int Field { get; set; } public int Field_1 { get; set; } private readonly bool WentThroughProperConstructor; public _ExplicitConstructors() { /* yep */ } [ExplicitConstructor] public _ExplicitConstructors(string foo, int bar) { WentThroughProperConstructor = true; } public bool GetWentThroughProperConstructor() { return WentThroughProperConstructor; } } public static class AbstractInheritance { public abstract class Order { internal int Internal { get; set; } protected int Protected { get; set; } public int Public { get; set; } public int ProtectedVal => Protected; } public class ConcreteOrder : Order { public int Concrete { get; set; } } } private class MultipleConstructors { public MultipleConstructors() { B = default!; } public MultipleConstructors(int a, string b) { A = a + 1; B = b + "!"; } public int A { get; set; } public string B { get; set; } } private class ConstructorsWithAccessModifiers { private ConstructorsWithAccessModifiers() { } public ConstructorsWithAccessModifiers(int a, string b) { A = a + 1; B = b + "!"; } public int A { get; set; } public string? B { get; set; } } private class NoDefaultConstructor { public NoDefaultConstructor(int a1, int? b1, float f1, string s1, Guid G1) { A = a1; B = b1; F = f1; S = s1; G = G1; } public int A { get; set; } public int? B { get; set; } public float F { get; set; } public string S { get; set; } public Guid G { get; set; } } private class NoDefaultConstructorWithChar { public NoDefaultConstructorWithChar(char c1, char? c2, char? c3) { Char1 = c1; Char2 = c2; Char3 = c3; } public char Char1 { get; set; } public char? Char2 { get; set; } public char? Char3 { get; set; } } private class NoDefaultConstructorWithEnum { public NoDefaultConstructorWithEnum(ShortEnum e1, ShortEnum? n1, ShortEnum? n2) { E = e1; NE1 = n1; NE2 = n2; } public ShortEnum E { get; set; } public ShortEnum? NE1 { get; set; } public ShortEnum? NE2 { get; set; } } private class WithPrivateConstructor { public int Foo { get; set; } private WithPrivateConstructor() { } } [Fact] public void TestWithNonPublicConstructor() { var output = connection.Query("select 1 as Foo").First(); Assert.Equal(1, output.Foo); } [Fact] public void CtorWithUnderscores() { var obj = connection.QueryFirst("select 'abc' as FIRST_NAME, 'def' as LAST_NAME"); Assert.NotNull(obj); Assert.Equal("abc", obj.FirstName); Assert.Equal("def", obj.LastName); } [Fact] public void CtorWithoutUnderscores() { DefaultTypeMap.MatchNamesWithUnderscores = true; var obj = connection.QueryFirst("select 'abc' as FIRST_NAME, 'def' as LAST_NAME"); Assert.NotNull(obj); Assert.Equal("abc", obj.FirstName); Assert.Equal("def", obj.LastName); } [Fact] public void Issue1993_PreferPropertyOverField() // https://github.com/DapperLib/Dapper/issues/1993 { var oldValue = DefaultTypeMap.MatchNamesWithUnderscores; try { DefaultTypeMap.MatchNamesWithUnderscores = true; var map = new DefaultTypeMap(typeof(ShowIssue1993)); var first = map.GetMember("field_first"); Assert.NotNull(first); Assert.Null(first.Field); Assert.Equal(nameof(ShowIssue1993.FieldFirst), first.Property?.Name); var last = map.GetMember("field_last"); Assert.NotNull(last); Assert.Null(last.Field); Assert.Equal(nameof(ShowIssue1993.FieldLast), last.Property?.Name); } finally { DefaultTypeMap.MatchNamesWithUnderscores = oldValue; } } [Fact] public void Issue1993_Query() { var oldValue = DefaultTypeMap.MatchNamesWithUnderscores; try { DefaultTypeMap.MatchNamesWithUnderscores = true; var obj = connection.QueryFirst("select 'abc' as field_first, 'def' as field_last"); Assert.Equal("abc", obj.FieldFirst); Assert.Equal("def", obj.FieldLast); Assert.Equal("abc", obj.AltFieldFirst); Assert.Equal("def", obj.AltFieldLast); } finally { DefaultTypeMap.MatchNamesWithUnderscores = oldValue; } } public class ShowIssue1993 { private string _fieldFirst { get; set; } = null!; // not actually a field public string FieldFirst { get => _fieldFirst; set => _fieldFirst = AltFieldFirst = value; } public string FieldLast { get => _fieldLast; set => _fieldLast = AltFieldLast = value; } private string _fieldLast { get; set; } = null!;// not actually a field public string AltFieldFirst { get; set; } = null!; public string AltFieldLast { get; set; } = null!; } class Type_ParamsWithUnderscores { public string FirstName { get; } public string LastName { get; } public Type_ParamsWithUnderscores(string first_name, string last_name) { FirstName = first_name; LastName = last_name; } } class Type_ParamsWithoutUnderscores { public string FirstName { get; } public string LastName { get; } public Type_ParamsWithoutUnderscores(string firstName, string lastName) { FirstName = firstName; LastName = lastName; } } } } ================================================ FILE: tests/Dapper.Tests/Dapper.Tests.csproj ================================================  Dapper.Tests Dapper Core Test Suite net481;net8.0;net10.0 $(DefineConstants);MSSQLCLIENT $(NoWarn);IDE0017;IDE0034;IDE0037;IDE0039;IDE0042;IDE0044;IDE0051;IDE0052;IDE0059;IDE0060;IDE0063;IDE1006;xUnit1004;CA1806;CA1816;CA1822;CA1825;CA2208;CA1861 enable true $(DefineConstants);ENTITY_FRAMEWORK;LINQ2SQL;OLEDB PreserveNewest ================================================ FILE: tests/Dapper.Tests/DataReaderTests.cs ================================================ using System.Collections.Generic; using System.Data.Common; using System.Linq; using Xunit; namespace Dapper.Tests { [Collection("DataReaderTests")] public sealed class SystemSqlClientDataReaderTests : DataReaderTests { } #if MSSQLCLIENT [Collection("DataReaderTests")] public sealed class MicrosoftSqlClientDataReaderTests : DataReaderTests { } #endif public abstract class DataReaderTests : TestBase where TProvider : DatabaseProvider { [Fact] public void GetSameReaderForSameShape_IDataReader() { var origReader = connection.ExecuteReader("select 'abc' as Name, 123 as Id"); #pragma warning disable CS0618 // Type or member is obsolete var origParser = origReader.GetRowParser(typeof(HazNameId)); var typedParser = origReader.GetRowParser(); #pragma warning restore CS0618 // Type or member is obsolete // because wrapped for IDataReader, not same instance each time Assert.False(ReferenceEquals(origParser, typedParser)); var list = origReader.Parse().ToList(); Assert.Single(list); Assert.Equal("abc", list[0].Name); Assert.Equal(123, list[0].Id); origReader.Dispose(); var secondReader = connection.ExecuteReader("select 'abc' as Name, 123 as Id"); #pragma warning disable CS0618 // Type or member is obsolete var secondParser = secondReader.GetRowParser(typeof(HazNameId)); var thirdParser = secondReader.GetRowParser(typeof(HazNameId), 1); #pragma warning restore CS0618 // Type or member is obsolete list = secondReader.Parse().ToList(); Assert.Single(list); Assert.Equal("abc", list[0].Name); Assert.Equal(123, list[0].Id); secondReader.Dispose(); // now: should be different readers, and because wrapped for IDataReader, not same parser Assert.False(ReferenceEquals(origReader, secondReader)); Assert.False(ReferenceEquals(origParser, secondParser)); Assert.False(ReferenceEquals(secondParser, thirdParser)); } [Fact] public void GetSameReaderForSameShape_DbDataReader() { var origReader = Assert.IsAssignableFrom(connection.ExecuteReader("select 'abc' as Name, 123 as Id")); var origParser = origReader.GetRowParser(typeof(HazNameId)); var typedParser = origReader.GetRowParser(); Assert.True(ReferenceEquals(origParser, typedParser)); var list = origReader.Parse().ToList(); Assert.Single(list); Assert.Equal("abc", list[0].Name); Assert.Equal(123, list[0].Id); origReader.Dispose(); var secondReader = Assert.IsAssignableFrom(connection.ExecuteReader("select 'abc' as Name, 123 as Id")); var secondParser = secondReader.GetRowParser(typeof(HazNameId)); var thirdParser = secondReader.GetRowParser(typeof(HazNameId), 1); list = secondReader.Parse().ToList(); Assert.Single(list); Assert.Equal("abc", list[0].Name); Assert.Equal(123, list[0].Id); secondReader.Dispose(); // now: should be different readers, but same parser Assert.False(ReferenceEquals(origReader, secondReader)); Assert.True(ReferenceEquals(origParser, secondParser)); Assert.False(ReferenceEquals(secondParser, thirdParser)); } [Fact] public void TestTreatIntAsABool() { // Test we are consistent with direct call to database, see TypeHandlerTests.TestTreatIntAsABool using(var reader = connection.ExecuteReader("select CAST(1 AS BIT)")) Assert.True(SqlMapper.Parse(reader).Single()); using (var reader = connection.ExecuteReader("select 1")) Assert.True(SqlMapper.Parse(reader).Single()); } [Fact] public void DiscriminatedUnion_IDataReader() { var result = new List(); using (var reader = connection.ExecuteReader(@" select 'abc' as Name, 1 as Type, 3.0 as Value union all select 'def' as Name, 2 as Type, 4.0 as Value")) { if (reader.Read()) { #pragma warning disable CS0618 var toFoo = reader.GetRowParser(typeof(Discriminated_Foo)); var toBar = reader.GetRowParser(typeof(Discriminated_Bar)); #pragma warning restore CS0618 var col = reader.GetOrdinal("Type"); do { switch (reader.GetInt32(col)) { case 1: result.Add(toFoo(reader)); break; case 2: result.Add(toBar(reader)); break; } } while (reader.Read()); } } Assert.Equal(2, result.Count); Assert.Equal(1, result[0].Type); Assert.Equal(2, result[1].Type); var foo = (Discriminated_Foo)result[0]; Assert.Equal("abc", foo.Name); var bar = (Discriminated_Bar)result[1]; Assert.Equal((float)4.0, bar.Value); } [Fact] public void DiscriminatedUnion_DbDataReader() { var result = new List(); using (var reader = Assert.IsAssignableFrom(connection.ExecuteReader(@" select 'abc' as Name, 1 as Type, 3.0 as Value union all select 'def' as Name, 2 as Type, 4.0 as Value"))) { if (reader.Read()) { var toFoo = reader.GetRowParser(typeof(Discriminated_Foo)); var toBar = reader.GetRowParser(typeof(Discriminated_Bar)); var col = reader.GetOrdinal("Type"); do { switch (reader.GetInt32(col)) { case 1: result.Add(toFoo(reader)); break; case 2: result.Add(toBar(reader)); break; } } while (reader.Read()); } } Assert.Equal(2, result.Count); Assert.Equal(1, result[0].Type); Assert.Equal(2, result[1].Type); var foo = (Discriminated_Foo)result[0]; Assert.Equal("abc", foo.Name); var bar = (Discriminated_Bar)result[1]; Assert.Equal((float)4.0, bar.Value); } [Fact] public void DiscriminatedUnionWithMultiMapping_IDataReader() { var result = new List(); using (var reader = connection.ExecuteReader(@" select 'abc' as Name, 1 as Type, 3.0 as Value, 1 as Id, 'zxc' as Name union all select 'def' as Name, 2 as Type, 4.0 as Value, 2 as Id, 'qwe' as Name")) { if (reader.Read()) { var col = reader.GetOrdinal("Type"); var splitOn = reader.GetOrdinal("Id"); #pragma warning disable CS0618 var toFoo = reader.GetRowParser(typeof(DiscriminatedWithMultiMapping_Foo), 0, splitOn); var toBar = reader.GetRowParser(typeof(DiscriminatedWithMultiMapping_Bar), 0, splitOn); var toHaz = reader.GetRowParser(typeof(HazNameId), splitOn, reader.FieldCount - splitOn); #pragma warning restore CS0618 do { DiscriminatedWithMultiMapping_BaseType? obj = null; switch (reader.GetInt32(col)) { case 1: obj = toFoo(reader); break; case 2: obj = toBar(reader); break; } Assert.NotNull(obj); obj!.HazNameIdObject = toHaz(reader); result.Add(obj); } while (reader.Read()); } } Assert.Equal(2, result.Count); Assert.Equal(1, result[0].Type); Assert.Equal(2, result[1].Type); var foo = (DiscriminatedWithMultiMapping_Foo)result[0]; Assert.Equal("abc", foo.Name); Assert.NotNull(foo.HazNameIdObject); Assert.Equal(1, foo.HazNameIdObject.Id); Assert.Equal("zxc", foo.HazNameIdObject!.Name); var bar = (DiscriminatedWithMultiMapping_Bar)result[1]; Assert.Equal((float)4.0, bar.Value); Assert.NotNull(bar.HazNameIdObject); Assert.Equal(2, bar.HazNameIdObject.Id); Assert.Equal("qwe", bar.HazNameIdObject.Name); } [Fact] public void DiscriminatedUnionWithMultiMapping_DbDataReader() { var result = new List(); using (var reader = Assert.IsAssignableFrom(connection.ExecuteReader(@" select 'abc' as Name, 1 as Type, 3.0 as Value, 1 as Id, 'zxc' as Name union all select 'def' as Name, 2 as Type, 4.0 as Value, 2 as Id, 'qwe' as Name"))) { if (reader.Read()) { var col = reader.GetOrdinal("Type"); var splitOn = reader.GetOrdinal("Id"); var toFoo = reader.GetRowParser(typeof(DiscriminatedWithMultiMapping_Foo), 0, splitOn); var toBar = reader.GetRowParser(typeof(DiscriminatedWithMultiMapping_Bar), 0, splitOn); var toHaz = reader.GetRowParser(typeof(HazNameId), splitOn, reader.FieldCount - splitOn); do { DiscriminatedWithMultiMapping_BaseType? obj = null; switch (reader.GetInt32(col)) { case 1: obj = toFoo(reader); break; case 2: obj = toBar(reader); break; } Assert.NotNull(obj); obj.HazNameIdObject = toHaz(reader); result.Add(obj); } while (reader.Read()); } } Assert.Equal(2, result.Count); Assert.Equal(1, result[0].Type); Assert.Equal(2, result[1].Type); var foo = (DiscriminatedWithMultiMapping_Foo)result[0]; Assert.Equal("abc", foo.Name); Assert.NotNull(foo.HazNameIdObject); Assert.Equal(1, foo.HazNameIdObject.Id); Assert.Equal("zxc", foo.HazNameIdObject.Name); var bar = (DiscriminatedWithMultiMapping_Bar)result[1]; Assert.Equal((float)4.0, bar.Value); Assert.NotNull(bar.HazNameIdObject); Assert.Equal(2, bar.HazNameIdObject.Id); Assert.Equal("qwe", bar.HazNameIdObject.Name); } private abstract class Discriminated_BaseType { public abstract int Type { get; } } private class Discriminated_Foo : Discriminated_BaseType { public string? Name { get; set; } public override int Type => 1; } private class Discriminated_Bar : Discriminated_BaseType { public float Value { get; set; } public override int Type => 2; } private abstract class DiscriminatedWithMultiMapping_BaseType : Discriminated_BaseType { public abstract HazNameId? HazNameIdObject { get; set; } } private class DiscriminatedWithMultiMapping_Foo : DiscriminatedWithMultiMapping_BaseType { public override HazNameId? HazNameIdObject { get; set; } public string? Name { get; set; } public override int Type => 1; } private class DiscriminatedWithMultiMapping_Bar : DiscriminatedWithMultiMapping_BaseType { public override HazNameId? HazNameIdObject { get; set; } public float Value { get; set; } public override int Type => 2; } } } ================================================ FILE: tests/Dapper.Tests/DateTimeOnlyTests.cs ================================================ using System; using System.Threading.Tasks; using Xunit; #if NET6_0_OR_GREATER namespace Dapper.Tests; /* we do **NOT** expect this to work against System.Data [Collection("DateTimeOnlyTests")] public sealed class SystemSqlClientDateTimeOnlyTests : DateTimeOnlyTests { } */ #if MSSQLCLIENT && DATEONLY [Collection("DateTimeOnlyTests")] public sealed class MicrosoftSqlClientDateTimeOnlyTests : DateTimeOnlyTests { } #endif public abstract class DateTimeOnlyTests : TestBase where TProvider : DatabaseProvider { public class HazDateTimeOnly { public string Name { get; set; } = ""; public DateOnly Date { get; set; } public TimeOnly Time { get; set; } public DateOnly? NDate { get; set; } public TimeOnly? NTime { get; set; } } [Fact] public void TypedInOut() { var now = DateTime.Now; var args = new HazDateTimeOnly { Name = nameof(TypedInOut), Date = DateOnly.FromDateTime(now), Time = TimeOnly.FromDateTime(now), NDate = DateOnly.FromDateTime(now), NTime = TimeOnly.FromDateTime(now), }; var row = connection.QuerySingle("select @name as [Name], @date as [Date], @time as [Time], @ndate as [NDate], @ntime as [NTime]", args); Assert.Equal(args.Name, row.Name); Assert.Equal(args.Date, row.Date); Assert.Equal(args.Time, row.Time); Assert.Equal(args.NDate, row.NDate); Assert.Equal(args.NTime, row.NTime); } [Fact] public async Task TypedInOutAsync() { var now = DateTime.Now; var args = new HazDateTimeOnly { Name = nameof(TypedInOutAsync), Date = DateOnly.FromDateTime(now), Time = TimeOnly.FromDateTime(now), NDate = DateOnly.FromDateTime(now), NTime = TimeOnly.FromDateTime(now), }; var row = await connection.QuerySingleAsync("select @name as [Name], @date as [Date], @time as [Time], @ndate as [NDate], @ntime as [NTime]", args); Assert.Equal(args.Name, row.Name); Assert.Equal(args.Date, row.Date); Assert.Equal(args.Time, row.Time); Assert.Equal(args.NDate, row.NDate); Assert.Equal(args.NTime, row.NTime); } [Fact] public void UntypedInOut() { var now = DateTime.Now; var args = new DynamicParameters(); var name = nameof(UntypedInOut); var date = DateOnly.FromDateTime(now); var time = TimeOnly.FromDateTime(now); args.Add("name", name); args.Add("date", date); args.Add("time", time); var row = connection.QuerySingle("select @name as [Name], @date as [Date], @time as [Time]", args); Assert.Equal(name, (string)row.Name); // untyped, observation is that these come back as DateTime and TimeSpan Assert.Equal(date, DateOnly.FromDateTime((DateTime)row.Date)); Assert.Equal(time, TimeOnly.FromTimeSpan((TimeSpan)row.Time)); } } #endif ================================================ FILE: tests/Dapper.Tests/DecimalTests.cs ================================================ using System; using System.Data; using System.Linq; using Xunit; namespace Dapper.Tests { [Collection("DecimalTests")] public sealed class SystemSqlClientDecimalTests : DecimalTests { } #if MSSQLCLIENT [Collection("DecimalTests")] public sealed class MicrosoftSqlClientDecimalTests : DecimalTests { } #endif public abstract class DecimalTests : TestBase where TProvider : DatabaseProvider { [Fact] public void Issue261_Decimals() { var parameters = new DynamicParameters(); parameters.Add("c", dbType: DbType.Decimal, direction: ParameterDirection.Output, precision: 10, scale: 5); connection.Execute("create proc #Issue261 @c decimal(10,5) OUTPUT as begin set @c=11.884 end"); connection.Execute("#Issue261", parameters, commandType: CommandType.StoredProcedure); var c = parameters.Get("c"); Assert.Equal(11.884M, c); } [Fact] public void Issue261_Decimals_ADONET_SetViaBaseClass() => Issue261_Decimals_ADONET(true); [Fact] public void Issue261_Decimals_ADONET_SetViaConcreteClass() => Issue261_Decimals_ADONET(false); private void Issue261_Decimals_ADONET(bool setPrecisionScaleViaAbstractApi) { try { using var cmd = connection.CreateCommand(); cmd.CommandText = "create proc #Issue261Direct @c decimal(10,5) OUTPUT as begin set @c=11.884 end"; cmd.ExecuteNonQuery(); } catch { /* we don't care that it already exists */ } using (var cmd = connection.CreateCommand()) { cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = "#Issue261Direct"; var c = cmd.CreateParameter(); c.ParameterName = "c"; c.Direction = ParameterDirection.Output; c.Value = DBNull.Value; c.DbType = DbType.Decimal; if (setPrecisionScaleViaAbstractApi) { IDbDataParameter baseParam = c; baseParam.Precision = 10; baseParam.Scale = 5; } else { c.Precision = 10; c.Scale = 5; } cmd.Parameters.Add(c); cmd.ExecuteNonQuery(); decimal value = (decimal)c.Value; Assert.Equal(11.884M, value); } } [Fact] public void BasicDecimals() { var c = connection.Query("select @c", new { c = 11.884M }).Single(); Assert.Equal(11.884M, c); } [Fact] public void TestDoubleDecimalConversions_SO18228523_RightWay() { var row = connection.Query( "select cast(1 as float) as A, cast(2 as float) as B, cast(3 as decimal) as C, cast(4 as decimal) as D").Single(); Assert.Equal(1.0, row.A); Assert.Equal(2.0, row.B); Assert.Equal(3.0M, row.C); Assert.Equal(4.0M, row.D); } [Fact] public void TestDoubleDecimalConversions_SO18228523_WrongWay() { var row = connection.Query( "select cast(1 as decimal) as A, cast(2 as decimal) as B, cast(3 as float) as C, cast(4 as float) as D").Single(); Assert.Equal(1.0, row.A); Assert.Equal(2.0, row.B); Assert.Equal(3.0M, row.C); Assert.Equal(4.0M, row.D); } [Fact] public void TestDoubleDecimalConversions_SO18228523_Nulls() { var row = connection.Query( "select cast(null as decimal) as A, cast(null as decimal) as B, cast(null as float) as C, cast(null as float) as D").Single(); Assert.Equal(0.0, row.A); Assert.Null(row.B); Assert.Equal(0.0M, row.C); Assert.Null(row.D); } private class HasDoubleDecimal { public double A { get; set; } public double? B { get; set; } public decimal C { get; set; } public decimal? D { get; set; } } } } ================================================ FILE: tests/Dapper.Tests/EnumTests.cs ================================================ using System.Data; using System.Linq; using Xunit; namespace Dapper.Tests { [Collection("EnumTests")] public sealed class SystemSqlClientEnumTests : EnumTests { } #if MSSQLCLIENT [Collection("EnumTests")] public sealed class MicrosoftSqlClientEnumTests : EnumTests { } #endif public abstract class EnumTests : TestBase where TProvider : DatabaseProvider { [Fact] public void TestEnumWeirdness() { Assert.Null(connection.Query("select null as [EnumEnum]").First().EnumEnum); Assert.Equal(TestEnum.Bla, connection.Query("select cast(1 as tinyint) as [EnumEnum]").First().EnumEnum); } [Fact] public void TestEnumStrings() { Assert.Equal(TestEnum.Bla, connection.Query("select 'BLA' as [EnumEnum]").First().EnumEnum); Assert.Equal(TestEnum.Bla, connection.Query("select 'bla' as [EnumEnum]").First().EnumEnum); Assert.Equal(TestEnum.Bla, connection.Query("select 'BLA' as [EnumEnum]").First().EnumEnum); Assert.Equal(TestEnum.Bla, connection.Query("select 'bla' as [EnumEnum]").First().EnumEnum); } [Fact] public void TestEnumParamsWithNullable() { const EnumParam a = EnumParam.A; EnumParam? b = EnumParam.B, c = null; var obj = connection.Query("select @a as A, @b as B, @c as C", new { a, b, c }).Single(); Assert.Equal(EnumParam.A, obj.A); Assert.Equal(EnumParam.B, obj.B); Assert.Null(obj.C); } [Fact] public void TestEnumParamsWithoutNullable() { const EnumParam a = EnumParam.A; const EnumParam b = EnumParam.B, c = 0; var obj = connection.Query("select @a as A, @b as B, @c as C", new { a, b, c }).Single(); Assert.Equal(EnumParam.A, obj.A); Assert.Equal(EnumParam.B, obj.B); Assert.Equal(obj.C, (EnumParam)0); } private enum EnumParam : short { None = 0, A = 1, B = 2 } private class EnumParamObject { public EnumParam A { get; set; } public EnumParam? B { get; set; } public EnumParam? C { get; set; } } private class EnumParamObjectNonNullable { public EnumParam A { get; set; } public EnumParam? B { get; set; } public EnumParam? C { get; set; } } private enum TestEnum : byte { Bla = 1 } private class TestEnumClass { public TestEnum? EnumEnum { get; set; } } private class TestEnumClassNoNull { public TestEnum EnumEnum { get; set; } } [Fact] public void AdoNetEnumValue() { using var cmd = connection.CreateCommand(); cmd.CommandText = "select @foo"; var p = cmd.CreateParameter(); p.ParameterName = "@foo"; p.DbType = DbType.Int32; // it turns out that this is the key piece; setting the DbType p.Value = AnEnum.B; cmd.Parameters.Add(p); object? value = cmd.ExecuteScalar(); Assert.NotNull(value); AnEnum val = (AnEnum)value; Assert.Equal(AnEnum.B, val); } [Fact] public void DapperEnumValue_SqlServer() => Common.DapperEnumValue(connection); private enum SO27024806Enum { Foo = 0, Bar = 1 } private class SO27024806Class { public SO27024806Class(SO27024806Enum myField) { MyField = myField; } public SO27024806Enum MyField { get; set; } } [Fact] public void SO27024806_TestVarcharEnumMemberWithExplicitConstructor() { var foo = connection.Query("SELECT 'Foo' AS myField").Single(); Assert.Equal(SO27024806Enum.Foo, foo.MyField); } } } ================================================ FILE: tests/Dapper.Tests/Helpers/Attributes.cs ================================================ using System; using Xunit.Sdk; namespace Dapper.Tests { /// /// Override for that truncates our DisplayName down. /// /// Attribute that is applied to a method to indicate that it is a fact that should /// be run by the test runner. It can also be extended to support a customized definition /// of a test method. /// /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] [XunitTestCaseDiscoverer("Dapper.Tests.FactDiscoverer", "Dapper.Tests")] public class FactAttribute : Xunit.FactAttribute { } /// /// Override for that truncates our DisplayName down. /// /// Marks a test method as being a data theory. Data theories are tests which are /// fed various bits of data from a data source, mapping to parameters on the test /// method. If the data source contains multiple rows, then the test method is executed /// multiple times (once with each data row). Data is provided by attributes which /// derive from Xunit.Sdk.DataAttribute (notably, Xunit.InlineDataAttribute and Xunit.MemberDataAttribute). /// /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] [XunitTestCaseDiscoverer("Dapper.Tests.TheoryDiscoverer", "Dapper.Tests")] public class TheoryAttribute : Xunit.TheoryAttribute { } [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public sealed class FactLongRunningAttribute : FactAttribute { public FactLongRunningAttribute() { #if !LONG_RUNNING Skip = "Long running"; #endif } public string? Url { get; private set; } } [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class FactRequiredCompatibilityLevelAttribute : FactAttribute { public FactRequiredCompatibilityLevelAttribute(int level) : base() { if (DetectedLevel < level) { Skip = $"Compatibility level {level} required; detected {DetectedLevel}"; } } public const int SqlServer2016 = 130; public static readonly int DetectedLevel; static FactRequiredCompatibilityLevelAttribute() { using var conn = DatabaseProvider.Instance.GetOpenConnection(); try { DetectedLevel = conn.QuerySingle("SELECT compatibility_level FROM sys.databases where name = DB_NAME()"); } catch { /* don't care */ } } } [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class FactUnlessCaseSensitiveDatabaseAttribute : FactAttribute { public FactUnlessCaseSensitiveDatabaseAttribute() : base() { if (IsCaseSensitive) { Skip = "Case sensitive database"; } } public static readonly bool IsCaseSensitive; static FactUnlessCaseSensitiveDatabaseAttribute() { using var conn = DatabaseProvider.Instance.GetOpenConnection(); try { conn.Execute("declare @i int; set @I = 1;"); } catch (Exception ex) when (ex.GetType().Name == "SqlException") { int err = ((dynamic)ex).Number; if (err == 137) IsCaseSensitive = true; else throw; } } } } ================================================ FILE: tests/Dapper.Tests/Helpers/Common.cs ================================================ using System; using System.Data; using System.Data.Common; using Xunit; namespace Dapper.Tests { public static class Common { public static Type GetSomeType() => typeof(SomeType); public static void DapperEnumValue(IDbConnection connection) { // test passing as AsEnum, reading as int var v = (AnEnum)connection.QuerySingle("select @v, @y, @z", new { v = AnEnum.B, y = (AnEnum?)AnEnum.B, z = (AnEnum?)null }); Assert.Equal(AnEnum.B, v); var args = new DynamicParameters(); args.Add("v", AnEnum.B); args.Add("y", AnEnum.B); args.Add("z", null); v = (AnEnum)connection.QuerySingle("select @v, @y, @z", args); Assert.Equal(AnEnum.B, v); // test passing as int, reading as AnEnum var k = (int)connection.QuerySingle("select @v, @y, @z", new { v = (int)AnEnum.B, y = (int?)(int)AnEnum.B, z = (int?)null }); Assert.Equal((int)AnEnum.B, k); args = new DynamicParameters(); args.Add("v", (int)AnEnum.B); args.Add("y", (int)AnEnum.B); args.Add("z", null); k = (int)connection.QuerySingle("select @v, @y, @z", args); Assert.Equal((int)AnEnum.B, k); } public static void TestDateTime(DbConnection connection) { DateTime? now = DateTime.UtcNow; try { connection.Execute("DROP TABLE Persons"); } catch { /* don't care */ } connection.Execute("CREATE TABLE Persons (id int not null, dob datetime null)"); connection.Execute("INSERT Persons (id, dob) values (@id, @dob)", new { id = 7, dob = (DateTime?)null }); connection.Execute("INSERT Persons (id, dob) values (@id, @dob)", new { id = 42, dob = now }); var row = connection.QueryFirstOrDefault( "SELECT id, dob, dob as dob2 FROM Persons WHERE id=@id", new { id = 7 }); Assert.NotNull(row); Assert.Equal(7, row.Id); Assert.Null(row.DoB); Assert.Null(row.DoB2); row = connection.QueryFirstOrDefault( "SELECT id, dob FROM Persons WHERE id=@id", new { id = 42 }); Assert.NotNull(row); Assert.Equal(42, row.Id); row.DoB.Equals(now); row.DoB2.Equals(now); } private class NullableDatePerson { public int Id { get; set; } public DateTime? DoB { get; set; } public DateTime? DoB2 { get; set; } } } } ================================================ FILE: tests/Dapper.Tests/Helpers/IsExternalInit.cs ================================================ namespace System.Runtime.CompilerServices; #if !NET5_0_OR_GREATER internal static class IsExternalInit { } #endif ================================================ FILE: tests/Dapper.Tests/Helpers/SqlServerTypesLoader.cs ================================================ using System; using System.IO; using System.Runtime.InteropServices; namespace Dapper.Tests { /// /// Utility methods related to CLR Types for SQL Server /// internal static class SqlServerTypesLoader { private static readonly object _nativeLoadLock = new object(); private static bool _nativeAssembliesLoaded; [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern IntPtr LoadLibrary(string libname); /// /// Loads the required native assemblies for the current architecture (x86 or x64) /// /// /// Root path of the current application. Use Server.MapPath(".") for ASP.NET applications /// and AppDomain.CurrentDomain.BaseDirectory for desktop applications. /// public static void LoadNativeAssemblies(string rootApplicationPath) { if (_nativeAssembliesLoaded) return; lock (_nativeLoadLock) { if (!_nativeAssembliesLoaded) { var nativeBinaryPath = IntPtr.Size > 4 ? Path.Combine(rootApplicationPath, @"x64\") : Path.Combine(rootApplicationPath, @"x86\"); Console.Write("(from: " + nativeBinaryPath + ")..."); LoadNativeAssembly(nativeBinaryPath, "msvcr120.dll"); LoadNativeAssembly(nativeBinaryPath, "SqlServerSpatial140.dll"); _nativeAssembliesLoaded = true; } } } private static void LoadNativeAssembly(string nativeBinaryPath, string assemblyName) { var path = Path.Combine(nativeBinaryPath, assemblyName); var ptr = LoadLibrary(path); if (ptr == IntPtr.Zero) { throw new Exception(string.Format( "Error loading {0} (ErrorCode: {1})", assemblyName, Marshal.GetLastWin32Error())); } } } } ================================================ FILE: tests/Dapper.Tests/Helpers/TransactedConnection.cs ================================================ using System; using System.Data; namespace Dapper.Tests { public class TransactedConnection : IDbConnection { private readonly IDbConnection _conn; private readonly IDbTransaction _tran; public TransactedConnection(IDbConnection conn, IDbTransaction tran) { _conn = conn; _tran = tran; } public string ConnectionString { get { return _conn?.ConnectionString ?? ""; } #pragma warning disable CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes). set { _conn.ConnectionString = value; } #pragma warning restore CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes). } public int ConnectionTimeout => _conn.ConnectionTimeout; public string Database => _conn.Database; public ConnectionState State => _conn.State; public IDbTransaction BeginTransaction(IsolationLevel il) { throw new NotImplementedException(); } public IDbTransaction BeginTransaction() => _tran; public void ChangeDatabase(string databaseName) => _conn.ChangeDatabase(databaseName); public void Close() => _conn.Close(); public IDbCommand CreateCommand() { // The command inherits the "current" transaction. var command = _conn.CreateCommand(); command.Transaction = _tran; return command; } public void Dispose() => _conn.Dispose(); public void Open() => _conn.Open(); } } ================================================ FILE: tests/Dapper.Tests/Helpers/XunitSkippable.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Xunit.Abstractions; using Xunit.Sdk; namespace Dapper.Tests { public static class Skip { public static void Inconclusive(string reason = "inconclusive") => throw new SkipTestException(reason); public static void If(object obj, string? reason = null) where T : class { if (obj is T) Skip.Inconclusive(reason ?? $"not valid for {typeof(T).FullName}"); } } #pragma warning disable RCS1194 // Implement exception constructors. public class SkipTestException : Exception { public SkipTestException(string reason) : base(reason) { } } #pragma warning restore RCS1194 // Implement exception constructors. public class FactDiscoverer : Xunit.Sdk.FactDiscoverer { public FactDiscoverer(IMessageSink diagnosticMessageSink) : base(diagnosticMessageSink) { } protected override IXunitTestCase CreateTestCase(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) => new SkippableTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod); } public class TheoryDiscoverer : Xunit.Sdk.TheoryDiscoverer { public TheoryDiscoverer(IMessageSink diagnosticMessageSink) : base(diagnosticMessageSink) { } protected override IEnumerable CreateTestCasesForDataRow(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute, object[] dataRow) => new[] { new SkippableTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, dataRow) }; protected override IEnumerable CreateTestCasesForSkip(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute, string skipReason) => new[] { new SkippableTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod) }; protected override IEnumerable CreateTestCasesForTheory(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute) => new[] { new SkippableTheoryTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod) }; protected override IEnumerable CreateTestCasesForSkippedDataRow(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute, object[] dataRow, string skipReason) => new[] { new NamedSkippedDataRowTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, skipReason, dataRow) }; } public class SkippableTestCase : XunitTestCase { protected override string GetDisplayName(IAttributeInfo factAttribute, string displayName) => base.GetDisplayName(factAttribute, displayName).StripName(); [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] public SkippableTestCase() { } public SkippableTestCase(IMessageSink diagnosticMessageSink, TestMethodDisplay defaultMethodDisplay, TestMethodDisplayOptions defaultMethodDisplayOptions, ITestMethod testMethod, object[]? testMethodArguments = null) : base(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod, testMethodArguments) { } public override async Task RunAsync( IMessageSink diagnosticMessageSink, IMessageBus messageBus, object[] constructorArguments, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource) { var skipMessageBus = new SkippableMessageBus(messageBus); var result = await base.RunAsync(diagnosticMessageSink, skipMessageBus, constructorArguments, aggregator, cancellationTokenSource).ConfigureAwait(false); return result.Update(skipMessageBus); } } public class SkippableTheoryTestCase : XunitTheoryTestCase { protected override string GetDisplayName(IAttributeInfo factAttribute, string displayName) => base.GetDisplayName(factAttribute, displayName).StripName(); [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] public SkippableTheoryTestCase() { } public SkippableTheoryTestCase(IMessageSink diagnosticMessageSink, TestMethodDisplay defaultMethodDisplay, TestMethodDisplayOptions defaultMethodDisplayOptions, ITestMethod testMethod) : base(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod) { } public override async Task RunAsync( IMessageSink diagnosticMessageSink, IMessageBus messageBus, object[] constructorArguments, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource) { var skipMessageBus = new SkippableMessageBus(messageBus); var result = await base.RunAsync(diagnosticMessageSink, skipMessageBus, constructorArguments, aggregator, cancellationTokenSource).ConfigureAwait(false); return result.Update(skipMessageBus); } } public class NamedSkippedDataRowTestCase : XunitSkippedDataRowTestCase { protected override string GetDisplayName(IAttributeInfo factAttribute, string displayName) => base.GetDisplayName(factAttribute, displayName).StripName(); [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] public NamedSkippedDataRowTestCase() { } public NamedSkippedDataRowTestCase(IMessageSink diagnosticMessageSink, TestMethodDisplay defaultMethodDisplay, TestMethodDisplayOptions defaultMethodDisplayOptions, ITestMethod testMethod, string skipReason, object[]? testMethodArguments = null) : base(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod, skipReason, testMethodArguments) { } } public class SkippableMessageBus : IMessageBus { private readonly IMessageBus InnerBus; public SkippableMessageBus(IMessageBus innerBus) => InnerBus = innerBus; public int DynamicallySkippedTestCount { get; private set; } public void Dispose() { } public bool QueueMessage(IMessageSinkMessage message) { if (message is ITestFailed testFailed) { var exceptionType = testFailed.ExceptionTypes.FirstOrDefault(); if (exceptionType == typeof(SkipTestException).FullName) { DynamicallySkippedTestCount++; return InnerBus.QueueMessage(new TestSkipped(testFailed.Test, testFailed.Messages.FirstOrDefault())); } } return InnerBus.QueueMessage(message); } } internal static class XUnitExtensions { internal static string StripName(this string name) => name.Replace("Dapper.Tests.", ""); public static RunSummary Update(this RunSummary summary, SkippableMessageBus bus) { if (bus.DynamicallySkippedTestCount > 0) { summary.Failed -= bus.DynamicallySkippedTestCount; summary.Skipped += bus.DynamicallySkippedTestCount; } return summary; } } } ================================================ FILE: tests/Dapper.Tests/LiteralTests.cs ================================================ using System.Linq; using Xunit; namespace Dapper.Tests { [Collection("LiteralTests")] public sealed class SystemSqlClientLiteralTests : LiteralTests { } #if MSSQLCLIENT [Collection("LiteralTests")] public sealed class MicrosoftSqlClientLiteralTests : LiteralTests { } #endif public abstract class LiteralTests : TestBase where TProvider : DatabaseProvider { [Fact] public void LiteralReplacementEnumAndString() { var args = new { x = AnEnum.B, y = 123.45M, z = AnotherEnum.A }; var row = connection.Query("select {=x} as x,{=y} as y,cast({=z} as tinyint) as z", args).Single(); AnEnum x = (AnEnum)(int)row.x; decimal y = row.y; AnotherEnum z = (AnotherEnum)(byte)row.z; Assert.Equal(AnEnum.B, x); Assert.Equal(123.45M, y); Assert.Equal(AnotherEnum.A, z); } [Fact] public void LiteralReplacementDynamicEnumAndString() { var args = new DynamicParameters(); args.Add("x", AnEnum.B); args.Add("y", 123.45M); args.Add("z", AnotherEnum.A); var row = connection.Query("select {=x} as x,{=y} as y,cast({=z} as tinyint) as z", args).Single(); AnEnum x = (AnEnum)(int)row.x; decimal y = row.y; AnotherEnum z = (AnotherEnum)(byte)row.z; Assert.Equal(AnEnum.B, x); Assert.Equal(123.45M, y); Assert.Equal(AnotherEnum.A, z); } [Fact] public void LiteralReplacementBoolean() { var row = connection.Query("select 42 where 1 = {=val}", new { val = true }).SingleOrDefault(); Assert.NotNull(row); Assert.Equal(42, row); row = connection.Query("select 42 where 1 = {=val}", new { val = false }).SingleOrDefault(); Assert.Null(row); } [Fact] public void LiteralReplacementWithIn() { var data = connection.Query("select @x where 1 in @ids and 1 ={=a}", new { x = 1, ids = new[] { 1, 2, 3 }, a = 1 }).ToList(); } private class MyRow { public int x { get; set; } } [Fact] public void LiteralIn() { connection.Execute("create table #literalin(id int not null);"); connection.Execute("insert #literalin (id) values (@id)", new[] { new { id = 1 }, new { id = 2 }, new { id = 3 }, }); var count = connection.Query("select count(1) from #literalin where id in {=ids}", new { ids = new[] { 1, 3, 4 } }).Single(); Assert.Equal(2, count); } [Fact] public void LiteralReplacement() { connection.Execute("create table #literal1 (id int not null, foo int not null)"); connection.Execute("insert #literal1 (id,foo) values ({=id}, @foo)", new { id = 123, foo = 456 }); var rows = new[] { new { id = 1, foo = 2 }, new { id = 3, foo = 4 } }; connection.Execute("insert #literal1 (id,foo) values ({=id}, @foo)", rows); var count = connection.Query("select count(1) from #literal1 where id={=foo}", new { foo = 123 }).Single(); Assert.Equal(1, count); int sum = connection.Query("select sum(id) + sum(foo) from #literal1").Single(); Assert.Equal(123 + 456 + 1 + 2 + 3 + 4, sum); } [Fact] public void LiteralReplacementDynamic() { var args = new DynamicParameters(); args.Add("id", 123); connection.Execute("create table #literal2 (id int not null)"); connection.Execute("insert #literal2 (id) values ({=id})", args); args = new DynamicParameters(); args.Add("foo", 123); var count = connection.Query("select count(1) from #literal2 where id={=foo}", args).Single(); Assert.Equal(1, count); } } } ================================================ FILE: tests/Dapper.Tests/MiscTests.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Data.Common; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.CSharp.RuntimeBinder; using Xunit; #if NETCOREAPP3_1 || NET462 || NET472 namespace System.Runtime.CompilerServices { [EditorBrowsable(EditorBrowsableState.Never)] internal static class IsExternalInit // yeah, don't do this! { } } #endif namespace Dapper.Tests { [Collection("MiscTests")] public sealed class SystemSqlClientMiscTests : MiscTests { } #if MSSQLCLIENT [Collection("MiscTests")] public sealed class MicrosoftSqlClientMiscTests : MiscTests { } #endif public abstract class MiscTests : TestBase where TProvider : DatabaseProvider { [Fact] public void TestNullableGuidSupport() { var guid = connection.Query("select null").First(); Assert.Null(guid); guid = Guid.NewGuid(); var guid2 = connection.Query("select @guid", new { guid }).First(); Assert.Equal(guid, guid2); } [Fact] public void TestNonNullableGuidSupport() { var guid = Guid.NewGuid(); var guid2 = connection.Query("select @guid", new { guid }).First(); Assert.True(guid == guid2); } private struct Car { public enum TrapEnum : int { A = 1, B = 2 } #pragma warning disable 0649 public string Name; #pragma warning restore 0649 public int Age { get; set; } public TrapEnum Trap { get; set; } } private struct CarWithAllProps { public string Name { get; set; } public int Age { get; init; } public Car.TrapEnum Trap { get; init; } } private record PositionalCarRecord(int Age, Car.TrapEnum Trap, string? Name) { public PositionalCarRecord() : this(default, default, default) { } } private record NominalCarRecord { public int Age { get; init; } public Car.TrapEnum Trap { get; init; } public string? Name { get; init; } } [Fact] public void TestStructs() { var car = connection.Query("select 'Ford' Name, 21 Age, 2 Trap").First(); Assert.Equal(21, car.Age); Assert.Equal("Ford", car.Name); Assert.Equal(2, (int)car.Trap); } [Fact] public void TestPositionalRecord() { var car = connection.Query("select 'Ford' Name, 21 Age, 2 Trap").First(); Assert.Equal(21, car.Age); Assert.Equal("Ford", car.Name); Assert.Equal(2, (int)car.Trap); } [Fact] public void TestNominalRecord() { var car = connection.Query("select 'Ford' Name, 21 Age, 2 Trap").First(); Assert.Equal(21, car.Age); Assert.Equal("Ford", car.Name); Assert.Equal(2, (int)car.Trap); } [Fact] public void TestStructAsParam() { var car1 = new CarWithAllProps { Name = "Ford", Age = 21, Trap = Car.TrapEnum.B }; // note Car has Name as a field; parameters only respect properties at the moment var car2 = connection.Query("select @Name Name, @Age Age, @Trap Trap", car1).First(); Assert.Equal(car2.Name, car1.Name); Assert.Equal(car2.Age, car1.Age); Assert.Equal(car2.Trap, car1.Trap); } [Fact] public void SelectListInt() { Assert.Equal(new[] { 1, 2, 3 }, connection.Query("select 1 union all select 2 union all select 3")); } [Fact] public void SelectBinary() { connection.Query("select cast(1 as varbinary(4))").First().SequenceEqual(new byte[] { 1 }); } [Fact] public void TestSchemaChanged() { connection.Execute("create table #dog(Age int, Name nvarchar(max)) insert #dog values(1, 'Alf')"); try { var d = connection.Query("select * from #dog").Single(); Assert.Equal("Alf", d.Name); Assert.Equal(1, d.Age); connection.Execute("alter table #dog drop column Name"); d = connection.Query("select * from #dog").Single(); Assert.Null(d.Name); Assert.Equal(1, d.Age); } finally { connection.Execute("drop table #dog"); } } [Fact] public void TestSchemaChangedViaFirstOrDefault() { connection.Execute("create table #dog(Age int, Name nvarchar(max)) insert #dog values(1, 'Alf')"); try { var d = connection.QueryFirstOrDefault("select * from #dog"); Assert.NotNull(d); Assert.Equal("Alf", d.Name); Assert.Equal(1, d.Age); connection.Execute("alter table #dog drop column Name"); d = connection.QueryFirstOrDefault("select * from #dog"); Assert.NotNull(d); Assert.Null(d.Name); Assert.Equal(1, d.Age); } finally { connection.Execute("drop table #dog"); } } [Fact] public void Test_Single_First_Default() { var sql = "select 0 where 1 = 0;"; // no rows var ex = Assert.Throws(() => connection.QueryFirst(sql)); Assert.Equal("Sequence contains no elements", ex.Message); ex = Assert.Throws(() => connection.QuerySingle(sql)); Assert.Equal("Sequence contains no elements", ex.Message); Assert.Equal(0, connection.QueryFirstOrDefault(sql)); Assert.Equal(0, connection.QuerySingleOrDefault(sql)); sql = "select 1;"; // one row Assert.Equal(1, connection.QueryFirst(sql)); Assert.Equal(1, connection.QuerySingle(sql)); Assert.Equal(1, connection.QueryFirstOrDefault(sql)); Assert.Equal(1, connection.QuerySingleOrDefault(sql)); sql = "select 2 union select 3 order by 1;"; // two rows Assert.Equal(2, connection.QueryFirst(sql)); ex = Assert.Throws(() => connection.QuerySingle(sql)); Assert.Equal("Sequence contains more than one element", ex.Message); Assert.Equal(2, connection.QueryFirstOrDefault(sql)); ex = Assert.Throws(() => connection.QuerySingleOrDefault(sql)); Assert.Equal("Sequence contains more than one element", ex.Message); } /// /// This test is ensuring our "single row" methods also behave like a type being deserialized /// and give a useful error message when the types don't match. /// [Fact] public async Task TestConversionExceptionMessages() { const string sql = "Select Null;"; // Nullable is expected to work if we get a null in all cases // List paths var list = connection.Query(sql); Assert.Null(Assert.Single(list)); list = await connection.QueryAsync(sql); Assert.Null(Assert.Single(list)); // Single row paths Assert.Null(connection.QueryFirst(sql)); Assert.Null(connection.QueryFirstOrDefault(sql)); Assert.Null(connection.QuerySingle(sql)); Assert.Null(connection.QuerySingleOrDefault(sql)); Assert.Null(await connection.QueryFirstAsync(sql)); Assert.Null(await connection.QueryFirstOrDefaultAsync(sql)); Assert.Null(await connection.QuerySingleAsync(sql)); Assert.Null(await connection.QuerySingleOrDefaultAsync(sql)); static async Task TestExceptionsAsync(DbConnection connection, string sql, string exception) { var ex = Assert.Throws(() => connection.Query(sql)); Assert.Equal(exception, ex.Message); ex = Assert.Throws(() => connection.QueryFirst(sql)); Assert.Equal(exception, ex.Message); ex = Assert.Throws(() => connection.QueryFirstOrDefault(sql)); Assert.Equal(exception, ex.Message); ex = Assert.Throws(() => connection.QuerySingle(sql)); Assert.Equal(exception, ex.Message); ex = Assert.Throws(() => connection.QuerySingleOrDefault(sql)); Assert.Equal(exception, ex.Message); ex = await Assert.ThrowsAsync(() => connection.QueryAsync(sql)); Assert.Equal(exception, ex.Message); ex = await Assert.ThrowsAsync(() => connection.QueryFirstAsync(sql)); Assert.Equal(exception, ex.Message); ex = await Assert.ThrowsAsync(() => connection.QueryFirstOrDefaultAsync(sql)); Assert.Equal(exception, ex.Message); ex = await Assert.ThrowsAsync(() => connection.QuerySingleAsync(sql)); Assert.Equal(exception, ex.Message); ex = await Assert.ThrowsAsync(() => connection.QuerySingleOrDefaultAsync(sql)); Assert.Equal(exception, ex.Message); } // Null value throws await TestExceptionsAsync( connection, "Select null as Foo", "Error parsing column 0 (Foo=n/a - Null object cannot be converted to a value type.)"); // Incompatible value throws (testing unnamed column bits here too) await TestExceptionsAsync( connection, "Select 'bar'", "Error parsing column 0 ((Unnamed Column)=bar - String)"); // Null with a full type (testing position too) await TestExceptionsAsync( connection, "Select 1 Id, 'bar' Foo", "Error parsing column 1 (Foo=bar - String)"); // And a ValueTuple! (testing position too) // Still needs love, because we handle ValueTuple differently today // It'll yield a raw: typeof(System.FormatException): Input string was not in a correct format. // Note: not checking the "Select 1 Id, null Foo" case here, because we won't attempt to set the column // ...and there will no error in that case. //await TestExceptionsAsync<(int Id, int Foo)>( // connection, // "Select 1 Id, 'bar' Foo", // "Error parsing column 1 (Foo=bar - String)"); } private class NullTestType { public int Id { get; } public int Foo { get; } } [Fact] public void TestStrings() { Assert.Equal(new[] { "a", "b" }, connection.Query("select 'a' a union select 'b'")); } // see https://stackoverflow.com/questions/16726709/string-format-with-sql-wildcard-causing-dapper-query-to-break [Fact] public void CheckComplexConcat() { const string end_wildcard = @" SELECT * FROM #users16726709 WHERE (first_name LIKE CONCAT(@search_term, '%') OR last_name LIKE CONCAT(@search_term, '%'));"; const string both_wildcards = @" SELECT * FROM #users16726709 WHERE (first_name LIKE CONCAT('%', @search_term, '%') OR last_name LIKE CONCAT('%', @search_term, '%'));"; const string formatted = @" SELECT * FROM #users16726709 WHERE (first_name LIKE {0} OR last_name LIKE {0});"; const string use_end_only = "CONCAT(@search_term, '%')"; const string use_both = "CONCAT('%', @search_term, '%')"; // if true, slower query due to not being able to use indices, but will allow searching inside strings const bool allow_start_wildcards = false; string query = string.Format(formatted, allow_start_wildcards ? use_both : use_end_only); const string term = "F"; // the term the user searched for connection.Execute(@"create table #users16726709 (first_name varchar(200), last_name varchar(200)) insert #users16726709 values ('Fred','Bloggs') insert #users16726709 values ('Tony','Farcus') insert #users16726709 values ('Albert','TenoF')"); // Using Dapper Assert.Equal(2, connection.Query(end_wildcard, new { search_term = term }).Count()); Assert.Equal(3, connection.Query(both_wildcards, new { search_term = term }).Count()); Assert.Equal(2, connection.Query(query, new { search_term = term }).Count()); } [Fact] public void TestExtraFields() { var guid = Guid.NewGuid(); var dog = connection.Query("select '' as Extra, 1 as Age, 0.1 as Name1 , Id = @id", new { id = guid }); Assert.Single(dog); Assert.Equal(1, dog.First().Age); Assert.Equal(dog.First().Id, guid); } [Fact] public void TestStrongType() { var guid = Guid.NewGuid(); var dog = connection.Query("select Age = @Age, Id = @Id", new { Age = (int?)null, Id = guid }); Assert.Single(dog); Assert.Null(dog.First().Age); Assert.Equal(dog.First().Id, guid); } [Fact] public void TestSimpleNull() { Assert.Null(connection.Query("select null").First()); } [Fact] public void TestExpando() { var rows = connection.Query("select 1 A, 2 B union all select 3, 4").ToList(); Assert.Equal(1, (int)rows[0].A); Assert.Equal(2, (int)rows[0].B); Assert.Equal(3, (int)rows[1].A); Assert.Equal(4, (int)rows[1].B); } [Fact] public void TestStringList() { Assert.Equal( new[] { "a", "b", "c" }, connection.Query("select * from (select 'a' as x union all select 'b' union all select 'c') as T where x in @strings", new { strings = new[] { "a", "b", "c" } }) ); Assert.Equal( new string[0], connection.Query("select * from (select 'a' as x union all select 'b' union all select 'c') as T where x in @strings", new { strings = new string[0] }) ); } [Fact] public void TestExecuteCommand() { Assert.Equal(2, connection.Execute(@" set nocount on create table #t(i int) set nocount off insert #t select @a a union all select @b set nocount on drop table #t", new { a = 1, b = 2 })); } [Fact] public void TestExecuteMultipleCommand() { connection.Execute("create table #t(i int)"); try { int tally = connection.Execute("insert #t (i) values(@a)", new[] { new { a = 1 }, new { a = 2 }, new { a = 3 }, new { a = 4 } }); int sum = connection.Query("select sum(i) from #t").First(); Assert.Equal(4, tally); Assert.Equal(10, sum); } finally { connection.Execute("drop table #t"); } } private class Student { public string? Name { get; set; } public int Age { get; set; } } [Fact] public void TestExecuteMultipleCommandStrongType() { connection.Execute("create table #t(Name nvarchar(max), Age int)"); try { int tally = connection.Execute("insert #t (Name,Age) values(@Name, @Age)", new List(2) { new Student{Age = 1, Name = "sam"}, new Student{Age = 2, Name = "bob"} }); int sum = connection.Query("select sum(Age) from #t").First(); Assert.Equal(2, tally); Assert.Equal(3, sum); } finally { connection.Execute("drop table #t"); } } [Fact] public void TestExecuteMultipleCommandObjectArray() { connection.Execute("create table #t(i int)"); int tally = connection.Execute("insert #t (i) values(@a)", new object[] { new { a = 1 }, new { a = 2 }, new { a = 3 }, new { a = 4 } }); int sum = connection.Query("select sum(i) from #t drop table #t").First(); Assert.Equal(4, tally); Assert.Equal(10, sum); } private class TestObj { public int _internal; internal int Internal { set { _internal = value; } } public int _priv; private int Priv { set { _priv = value; } } private int PrivGet => _priv; } [Fact] public void TestSetInternal() { Assert.Equal(10, connection.Query("select 10 as [Internal]").First()._internal); } [Fact] public void TestSetPrivate() { Assert.Equal(10, connection.Query("select 10 as [Priv]").First()._priv); } [Fact] public void TestExpandWithNullableFields() { var row = connection.Query("select null A, 2 B").Single(); Assert.Null((int?)row.A); Assert.Equal(2, (int?)row.B); } [Fact] public void TestEnumeration() { var en = connection.Query("select 1 as one union all select 2 as one", buffered: false); var i = en.GetEnumerator(); i.MoveNext(); bool gotException = false; try { var x = connection.Query("select 1 as one", buffered: false).First(); } catch (Exception) { gotException = true; } while (i.MoveNext()) { } // should not exception, since enumerated en = connection.Query("select 1 as one", buffered: false); Assert.True(gotException); } [Fact] public void TestEnumerationDynamic() { var en = connection.Query("select 1 as one union all select 2 as one", buffered: false); var i = en.GetEnumerator(); i.MoveNext(); bool gotException = false; try { var x = connection.Query("select 1 as one", buffered: false).First(); } catch (Exception) { gotException = true; } while (i.MoveNext()) { } // should not exception, since enumertated en = connection.Query("select 1 as one", buffered: false); Assert.True(gotException); } [Fact] public void TestNakedBigInt() { const long foo = 12345; var result = connection.Query("select @foo", new { foo }).Single(); Assert.Equal(foo, result); } [Fact] public void TestBigIntMember() { const long foo = 12345; var result = connection.Query(@" declare @bar table(Value bigint) insert @bar values (@foo) select * from @bar", new { foo }).Single(); Assert.Equal(foo, result.Value); } private class WithBigInt { public long Value { get; set; } } [Fact] public void TestFieldsAndPrivates() { var data = connection.Query( "select a=1,b=2,c=3,d=4,f='5'").Single(); Assert.Equal(1, data.a); Assert.Equal(2, data.GetB()); Assert.Equal(3, data.c); Assert.Equal(4, data.GetD()); Assert.Equal(5, data.e); } private class TestFieldCaseAndPrivatesEntity { #pragma warning disable IDE1006 // Naming Styles public int a { get; set; } private int b { get; set; } public int GetB() { return b; } public int c = 0; #pragma warning disable RCS1169 // Mark field as read-only. private int d = 0; #pragma warning restore RCS1169 // Mark field as read-only. public int GetD() { return d; } public int e { get; set; } private string f { get { return e.ToString(); } set { e = int.Parse(value); } } #pragma warning restore IDE1006 // Naming Styles } private class InheritanceTest1 { public string? Base1 { get; set; } public string? Base2 { get; private set; } } private class InheritanceTest2 : InheritanceTest1 { public string? Derived1 { get; set; } public string? Derived2 { get; private set; } } [Fact] public void TestInheritance() { // Test that inheritance works. var list = connection.Query("select 'One' as Derived1, 'Two' as Derived2, 'Three' as Base1, 'Four' as Base2"); Assert.Equal("One", list.First().Derived1); Assert.Equal("Two", list.First().Derived2); Assert.Equal("Three", list.First().Base1); Assert.Equal("Four", list.First().Base2); } [Fact] public void ExecuteReader() { var dt = new DataTable(); dt.Load(connection.ExecuteReader("select 3 as [three], 4 as [four]")); Assert.Equal(2, dt.Columns.Count); Assert.Equal("three", dt.Columns[0].ColumnName); Assert.Equal("four", dt.Columns[1].ColumnName); Assert.Equal(1, dt.Rows.Count); Assert.Equal(3, (int)dt.Rows[0][0]); Assert.Equal(4, (int)dt.Rows[0][1]); } [Fact] public void TestDbString() { var obj = connection.Query("select datalength(@a) as a, datalength(@b) as b, datalength(@c) as c, datalength(@d) as d, datalength(@e) as e, datalength(@f) as f", new { a = new DbString { Value = "abcde", IsFixedLength = true, Length = 10, IsAnsi = true }, b = new DbString { Value = "abcde", IsFixedLength = true, Length = 10, IsAnsi = false }, c = new DbString { Value = "abcde", IsFixedLength = false, Length = 10, IsAnsi = true }, d = new DbString { Value = "abcde", IsFixedLength = false, Length = 10, IsAnsi = false }, e = new DbString { Value = "abcde", IsAnsi = true }, f = new DbString { Value = "abcde", IsAnsi = false }, }).First(); Assert.Equal(10, (int)obj.a); Assert.Equal(20, (int)obj.b); Assert.Equal(5, (int)obj.c); Assert.Equal(10, (int)obj.d); Assert.Equal(5, (int)obj.e); Assert.Equal(10, (int)obj.f); } [Fact] public void DbStringNullHandling() { // without lengths var obj = new { x = new DbString("abc"), y = (DbString?)new DbString(null) }; var row = connection.QuerySingle<(string? x,string? y)>("select @x as x, @y as y", obj); Assert.Equal("abc", row.x); Assert.Null(row.y); // with lengths obj = new { x = new DbString("abc", 200), y = (DbString?)new DbString(null, 200) }; row = connection.QuerySingle<(string? x, string? y)>("select @x as x, @y as y", obj); Assert.Equal("abc", row.x); Assert.Null(row.y); // null raw value - give clear message, at least obj = obj with { y = null }; var ex = Assert.Throws(() => connection.QuerySingle<(string? x, string? y)>("select @x as x, @y as y", obj)); Assert.Equal("Member 'y' is an ICustomQueryParameter and cannot be null", ex.Message); } [Fact] public void TestDbStringToString() { Assert.Equal("Dapper.DbString (Value: 'abcde', Length: 10, IsAnsi: True, IsFixedLength: True)", new DbString { Value = "abcde", IsFixedLength = true, Length = 10, IsAnsi = true }.ToString()); Assert.Equal("Dapper.DbString (Value: 'abcde', Length: 10, IsAnsi: False, IsFixedLength: True)", new DbString { Value = "abcde", IsFixedLength = true, Length = 10, IsAnsi = false }.ToString()); Assert.Equal("Dapper.DbString (Value: 'abcde', Length: 10, IsAnsi: True, IsFixedLength: False)", new DbString { Value = "abcde", IsFixedLength = false, Length = 10, IsAnsi = true }.ToString()); Assert.Equal("Dapper.DbString (Value: 'abcde', Length: 10, IsAnsi: False, IsFixedLength: False)", new DbString { Value = "abcde", IsFixedLength = false, Length = 10, IsAnsi = false }.ToString()); Assert.Equal("Dapper.DbString (Value: null, Length: -1, IsAnsi: False, IsFixedLength: False)", new DbString { Value = null }.ToString()); Assert.Equal("Dapper.DbString (Value: 'abcde', Length: -1, IsAnsi: True, IsFixedLength: False)", new DbString { Value = "abcde", IsAnsi = true }.ToString()); Assert.Equal("Dapper.DbString (Value: 'abcde', Length: -1, IsAnsi: False, IsFixedLength: False)", new DbString { Value = "abcde", IsAnsi = false }.ToString()); } [Fact] public void TestDefaultDbStringDbType() { var origDefaultStringDbType = DbString.IsAnsiDefault; try { DbString.IsAnsiDefault = true; var a = new DbString { Value = "abcde" }; var b = new DbString { Value = "abcde", IsAnsi = false }; Assert.True(a.IsAnsi); Assert.False(b.IsAnsi); } finally { DbString.IsAnsiDefault = origDefaultStringDbType; } } [Fact] public void TestFastExpandoSupportsIDictionary() { var row = connection.Query("select 1 A, 'two' B").First() as IDictionary; Assert.NotNull(row); Assert.Equal(1, row["A"]); Assert.Equal("two", row["B"]); } [Fact] public void TestDapperSetsPrivates() { Assert.Equal(1, connection.Query("select 'one' ShadowInDB").First().Shadow); Assert.Equal(1, connection.QueryFirstOrDefault("select 'one' ShadowInDB")?.Shadow); } private class PrivateDan { public int Shadow { get; set; } private string ShadowInDB { set { Shadow = value == "one" ? 1 : 0; } } } [Fact] public void TestUnexpectedDataMessage() { string? msg = null; try { connection.Query("select count(1) where 1 = @Foo", new WithBizarreData { Foo = new GenericUriParser(GenericUriParserOptions.Default), Bar = 23 }).First(); } catch (Exception ex) { msg = ex.Message; } Assert.Equal("The member Foo of type System.GenericUriParser cannot be used as a parameter value", msg); } [Fact] public void TestUnexpectedButFilteredDataMessage() { int i = connection.Query("select @Bar", new WithBizarreData { Foo = new GenericUriParser(GenericUriParserOptions.Default), Bar = 23 }).Single(); Assert.Equal(23, i); } private class WithBizarreData { public GenericUriParser? Foo { get; set; } public int Bar { get; set; } } private class WithCharValue { public char Value { get; set; } public char? ValueNullable { get; set; } } [Fact] public void TestCharInputAndOutput() { const char test = '〠'; char c = connection.Query("select @c", new { c = test }).Single(); Assert.Equal(test, c); var obj = connection.Query("select @Value as Value", new WithCharValue { Value = c }).Single(); Assert.Equal(test, obj.Value); } [Fact] public void TestNullableCharInputAndOutputNonNull() { char? test = '〠'; char? c = connection.Query("select @c", new { c = test }).Single(); Assert.Equal(c, test); var obj = connection.Query("select @ValueNullable as ValueNullable", new WithCharValue { ValueNullable = c }).Single(); Assert.Equal(obj.ValueNullable, test); } [Fact] public void TestNullableCharInputAndOutputNull() { char? test = null; char? c = connection.Query("select @c", new { c = test }).Single(); Assert.Equal(c, test); var obj = connection.Query("select @ValueNullable as ValueNullable", new WithCharValue { ValueNullable = c }).Single(); Assert.Equal(obj.ValueNullable, test); } [Fact] public void WorkDespiteHavingWrongStructColumnTypes() { var hazInt = connection.Query("select cast(1 as bigint) Value").Single(); Assert.Equal(1, hazInt.Value); } private struct CanHazInt { public int Value { get; set; } } [Fact] public void TestInt16Usage() { Assert.Equal((short)42, connection.Query("select cast(42 as smallint)").Single()); Assert.Equal((short?)42, connection.Query("select cast(42 as smallint)").Single()); Assert.Equal((short?)null, connection.Query("select cast(null as smallint)").Single()); Assert.Equal((ShortEnum)42, connection.Query("select cast(42 as smallint)").Single()); Assert.Equal((ShortEnum?)42, connection.Query("select cast(42 as smallint)").Single()); Assert.Equal((ShortEnum?)null, connection.Query("select cast(null as smallint)").Single()); var row = connection.Query( "select cast(1 as smallint) as NonNullableInt16, cast(2 as smallint) as NullableInt16, cast(3 as smallint) as NonNullableInt16Enum, cast(4 as smallint) as NullableInt16Enum") .Single(); Assert.Equal((short)1, row.NonNullableInt16); Assert.Equal((short)2, row.NullableInt16); Assert.Equal(ShortEnum.Three, row.NonNullableInt16Enum); Assert.Equal(ShortEnum.Four, row.NullableInt16Enum); row = connection.Query( "select cast(5 as smallint) as NonNullableInt16, cast(null as smallint) as NullableInt16, cast(6 as smallint) as NonNullableInt16Enum, cast(null as smallint) as NullableInt16Enum") .Single(); Assert.Equal((short)5, row.NonNullableInt16); Assert.Equal((short?)null, row.NullableInt16); Assert.Equal(ShortEnum.Six, row.NonNullableInt16Enum); Assert.Equal((ShortEnum?)null, row.NullableInt16Enum); } [Fact] public void TestInt32Usage() { Assert.Equal((int)42, connection.Query("select cast(42 as int)").Single()); Assert.Equal((int?)42, connection.Query("select cast(42 as int)").Single()); Assert.Equal((int?)null, connection.Query("select cast(null as int)").Single()); Assert.Equal((IntEnum)42, connection.Query("select cast(42 as int)").Single()); Assert.Equal((IntEnum?)42, connection.Query("select cast(42 as int)").Single()); Assert.Equal((IntEnum?)null, connection.Query("select cast(null as int)").Single()); var row = connection.Query( "select cast(1 as int) as NonNullableInt32, cast(2 as int) as NullableInt32, cast(3 as int) as NonNullableInt32Enum, cast(4 as int) as NullableInt32Enum") .Single(); Assert.Equal((int)1, row.NonNullableInt32); Assert.Equal((int)2, row.NullableInt32); Assert.Equal(IntEnum.Three, row.NonNullableInt32Enum); Assert.Equal(IntEnum.Four, row.NullableInt32Enum); row = connection.Query( "select cast(5 as int) as NonNullableInt32, cast(null as int) as NullableInt32, cast(6 as int) as NonNullableInt32Enum, cast(null as int) as NullableInt32Enum") .Single(); Assert.Equal((int)5, row.NonNullableInt32); Assert.Equal((int?)null, row.NullableInt32); Assert.Equal(IntEnum.Six, row.NonNullableInt32Enum); Assert.Equal((IntEnum?)null, row.NullableInt32Enum); } public class WithInt16Values { public short NonNullableInt16 { get; set; } public short? NullableInt16 { get; set; } public ShortEnum NonNullableInt16Enum { get; set; } public ShortEnum? NullableInt16Enum { get; set; } } public class WithInt32Values { public int NonNullableInt32 { get; set; } public int? NullableInt32 { get; set; } public IntEnum NonNullableInt32Enum { get; set; } public IntEnum? NullableInt32Enum { get; set; } } public enum IntEnum : int { Zero = 0, One = 1, Two = 2, Three = 3, Four = 4, Five = 5, Six = 6 } [Fact] public void Issue_40_AutomaticBoolConversion() { var user = connection.Query("select UserId=1,Email='abc',Password='changeme',Active=cast(1 as tinyint)").Single(); Assert.True(user.Active); Assert.Equal(1, user.UserID); Assert.Equal("abc", user.Email); Assert.Equal("changeme", user.Password); } public class Issue40_User { public Issue40_User() { Email = Password = string.Empty; } public int UserID { get; set; } public string Email { get; set; } public string Password { get; set; } public bool Active { get; set; } } [Fact] public void ExecuteFromClosed() { using var conn = GetClosedConnection(); conn.Execute("-- nop"); Assert.Equal(ConnectionState.Closed, conn.State); } [Fact] public void ExecuteInvalidFromClosed() { using var conn = GetClosedConnection(); var ex = Assert.ThrowsAny(() => conn.Execute("nop")); Assert.Equal(ConnectionState.Closed, conn.State); } [Fact] public void QueryFromClosed() { using var conn = GetClosedConnection(); var i = conn.Query("select 1").Single(); Assert.Equal(ConnectionState.Closed, conn.State); Assert.Equal(1, i); } [Fact] public void QueryInvalidFromClosed() { using var conn = GetClosedConnection(); Assert.ThrowsAny(() => conn.Query("select gibberish").Single()); Assert.Equal(ConnectionState.Closed, conn.State); } [Fact] public void TestDynamicMutation() { var obj = connection.Query("select 1 as [a], 2 as [b], 3 as [c]").Single(); Assert.Equal(1, (int)obj.a); IDictionary dict = obj; Assert.Equal(3, dict.Count); Assert.True(dict.Remove("a")); Assert.False(dict.Remove("d")); Assert.Equal(2, dict.Count); dict.Add("d", 4); Assert.Equal(3, dict.Count); Assert.Equal("b,c,d", string.Join(",", dict.Keys.OrderBy(x => x))); Assert.Equal("2,3,4", string.Join(",", dict.OrderBy(x => x.Key).Select(x => x.Value))); Assert.Equal(2, (int)obj.b); Assert.Equal(3, (int)obj.c); Assert.Equal(4, (int)obj.d); try { Assert.Equal(1, (int)obj.a); throw new InvalidOperationException("should have thrown"); } catch (RuntimeBinderException) { // pass } } [Fact] public void TestIssue131() { var results = connection.Query( "SELECT 1 Id, 'Mr' Title, 'John' Surname, 4 AddressCount", (person, addressCount) => person, splitOn: "AddressCount" ).First(); var asDict = (IDictionary)results; Assert.True(asDict.ContainsKey("Id")); Assert.True(asDict.ContainsKey("Title")); Assert.True(asDict.ContainsKey("Surname")); Assert.False(asDict.ContainsKey("AddressCount")); } // see https://stackoverflow.com/questions/13127886/dapper-returns-null-for-singleordefaultdatediff [Fact] public void TestNullFromInt_NoRows() { var result = connection.Query( // case with rows "select DATEDIFF(day, GETUTCDATE(), @date)", new { date = DateTime.UtcNow.AddDays(20) }) .SingleOrDefault(); Assert.Equal(20, result); result = connection.Query( // case without rows "select DATEDIFF(day, GETUTCDATE(), @date) where 1 = 0", new { date = DateTime.UtcNow.AddDays(20) }) .SingleOrDefault(); Assert.Equal(0, result); // zero rows; default of int over zero rows is zero } [Fact] public void TestDapperTableMetadataRetrieval() { // Test for a bug found in CS 51509960 where the following sequence would result in an InvalidOperationException being // thrown due to an attempt to access a disposed of DataReader: // // - Perform a dynamic query that yields no results // - Add data to the source of that query // - Perform a the same query again connection.Execute("CREATE TABLE #sut (value varchar(10) NOT NULL PRIMARY KEY)"); Assert.Equal(Enumerable.Empty(), connection.Query("SELECT value FROM #sut")); Assert.Equal(1, connection.Execute("INSERT INTO #sut (value) VALUES ('test')")); var result = connection.Query("SELECT value FROM #sut"); var first = result.First(); Assert.Equal("test", (string)first.value); } [Fact] public void DbStringAnsi() { var a = connection.Query("select datalength(@x)", new { x = new DbString { Value = "abc", IsAnsi = true } }).Single(); var b = connection.Query("select datalength(@x)", new { x = new DbString { Value = "abc", IsAnsi = false } }).Single(); Assert.Equal(3, a); Assert.Equal(6, b); } private class HasInt32 { public int Value { get; set; } } // https://stackoverflow.com/q/23696254/23354 [Fact] public void DownwardIntegerConversion() { const string sql = "select cast(42 as bigint) as Value"; int i = connection.Query(sql).Single().Value; Assert.Equal(42, i); i = connection.Query(sql).Single(); Assert.Equal(42, i); } [Fact] public void TypeBasedViaDynamic() { Type type = Common.GetSomeType(); dynamic template = Activator.CreateInstance(type)!; dynamic actual = CheetViaDynamic(template, "select @A as [A], @B as [B]", new { A = 123, B = "abc" }); Assert.Equal(((object)actual).GetType(), type); int a = actual.A; string b = actual.B; Assert.Equal(123, a); Assert.Equal("abc", b); } [Fact] public void TypeBasedViaType() { Type type = Common.GetSomeType(); dynamic actual = connection.Query(type, "select @A as [A], @B as [B]", new { A = 123, B = "abc" }).First(); Assert.Equal(((object)actual).GetType(), type); int a = actual.A; string b = actual.B; Assert.Equal(123, a); Assert.Equal("abc", b); } private T CheetViaDynamic(T template, string query, object args) { return connection.Query(query, args).Single(); } [Fact] public void Issue22_ExecuteScalar() { int i = connection.ExecuteScalar("select 123"); Assert.Equal(123, i); i = connection.ExecuteScalar("select cast(123 as bigint)"); Assert.Equal(123, i); long j = connection.ExecuteScalar("select 123"); Assert.Equal(123L, j); j = connection.ExecuteScalar("select cast(123 as bigint)"); Assert.Equal(123L, j); int? k = connection.ExecuteScalar("select @i", new { i = default(int?) }); Assert.Null(k); } [Fact] public void Issue142_FailsNamedStatus() { var row1 = connection.Query("select @Status as [Status]", new { Status = StatusType.Started }).Single(); Assert.Equal(StatusType.Started, row1.Status); var row2 = connection.Query("select @Status as [Status]", new { Status = Status.Started }).Single(); Assert.Equal(Status.Started, row2.Status); } public class Issue142_Status { public StatusType Status { get; set; } } public class Issue142_StatusType { public Status Status { get; set; } } public enum StatusType : byte { NotStarted = 1, Started = 2, Finished = 3 } public enum Status : byte { NotStarted = 1, Started = 2, Finished = 3 } [Fact] public void Issue178_SqlServer() { const string sql = "select count(*) from Issue178"; try { connection.Execute("drop table Issue178"); } catch { /* don't care */ } try { connection.Execute("create table Issue178(id int not null)"); } catch { /* don't care */ } // raw ADO.net using (var sqlCmd = connection.CreateCommand()) { sqlCmd.CommandText = sql; using IDataReader reader1 = sqlCmd.ExecuteReader(); Assert.True(reader1.Read()); Assert.Equal(0, reader1.GetInt32(0)); Assert.False(reader1.Read()); Assert.False(reader1.NextResult()); } // dapper using (var reader2 = connection.ExecuteReader(sql)) { Assert.True(reader2.Read()); Assert.Equal(0, reader2.GetInt32(0)); Assert.False(reader2.Read()); Assert.False(reader2.NextResult()); } } [Fact] public void QueryBasicWithoutQuery() { int? i = connection.Query("print 'not a query'").FirstOrDefault(); Assert.Null(i); } [Fact] public void QueryComplexWithoutQuery() { var obj = connection.Query("print 'not a query'").FirstOrDefault(); Assert.Null(obj); } [FactLongRunning] public void Issue263_Timeout() { var watch = Stopwatch.StartNew(); var i = connection.Query("waitfor delay '00:01:00'; select 42;", commandTimeout: 300, buffered: false).Single(); watch.Stop(); Assert.Equal(42, i); var minutes = watch.ElapsedMilliseconds / 1000 / 60; Assert.True(minutes >= 0.95 && minutes <= 1.05); } [Fact] public void SO30435185_InvalidTypeOwner() { var ex = Assert.Throws(() => { const string sql = @" INSERT INTO #XXX (XXXId, AnotherId, ThirdId, Value, Comment) VALUES (@XXXId, @AnotherId, @ThirdId, @Value, @Comment); select @@rowcount as [Foo]"; var command = new { MyModels = new[] { new {XXXId = 1, AnotherId = 2, ThirdId = 3, Value = "abc", Comment = "def" } } }; var parameters = command .MyModels .Select(model => new { XXXId = model.XXXId, AnotherId = model.AnotherId, ThirdId = model.ThirdId, Value = model.Value, Comment = model.Comment }) .ToArray(); var rowcount = (int)connection.Query(sql, parameters).Single().Foo; Assert.Equal(1, rowcount); }); Assert.Equal("An enumerable sequence of parameters (arrays, lists, etc) is not allowed in this context", ex.Message); } [Fact] public async void SO35470588_WrongValuePidValue() { // nuke, rebuild, and populate the table try { connection.Execute("drop table TPTable"); } catch { /* don't care */ } connection.Execute(@" create table TPTable (Pid int not null primary key identity(1,1), Value int not null); insert TPTable (Value) values (2), (568)"); // fetch the data using the query in the question, then force to a dictionary var rows = (await connection.QueryAsync("select * from TPTable").ConfigureAwait(false)) .ToDictionary(x => x.Pid); // check the number of rows Assert.Equal(2, rows.Count); // check row 1 var row = rows[1]; Assert.Equal(1, row.Pid); Assert.Equal(2, row.Value); // check row 2 row = rows[2]; Assert.Equal(2, row.Pid); Assert.Equal(568, row.Value); } public class TPTable { public int Pid { get; set; } public int Value { get; set; } } [Fact] public void GetOnlyProperties() { var obj = connection.QuerySingle("select 42 as [Id], 'def' as [Name];"); Assert.Equal(42, obj.Id); Assert.Equal("def", obj.Name); } private class HazGetOnly { public int Id { get; } public string Name { get; } = "abc"; } [Fact] public void TestConstructorParametersWithUnderscoredColumns() { DefaultTypeMap.MatchNamesWithUnderscores = true; var obj = connection.QuerySingle("select 42 as [id_property], 'def' as [name_property];"); Assert.Equal(42, obj.IdProperty); Assert.Equal("def", obj.NameProperty); } private class HazGetOnlyAndCtor { public int IdProperty { get; } public string NameProperty { get; } public HazGetOnlyAndCtor(int idProperty, string nameProperty) { IdProperty = idProperty; NameProperty = nameProperty; } } [Fact] public void Issue1164_OverflowExceptionForByte() { const string sql = "select cast(200 as smallint) as [value]"; // 200 more than sbyte.MaxValue but less than byte.MaxValue Issue1164Object obj = connection.QuerySingle>(sql); Assert.StrictEqual(200, obj.Value); } [Fact] public void Issue1164_OverflowExceptionForUInt16() { const string sql = "select cast(40000 as bigint) as [value]"; // 40000 more than short.MaxValue but less than ushort.MaxValue Issue1164Object obj = connection.QuerySingle>(sql); Assert.StrictEqual(40000, obj.Value); } [Fact] public void Issue1164_OverflowExceptionForUInt32() { const string sql = "select cast(4000000000 as bigint) as [value]"; // 4000000000 more than int.MaxValue but less than uint.MaxValue Issue1164Object obj = connection.QuerySingle>(sql); Assert.StrictEqual(4000000000, obj.Value); } [Fact] public void Issue1164_OverflowExceptionForUInt64() { const string sql = "select cast(10000000000000000000.0 as float) as [value]"; // 10000000000000000000 more than long.MaxValue but less than ulong.MaxValue Issue1164Object obj = connection.QuerySingle>(sql); Assert.StrictEqual(10000000000000000000, obj.Value); } private class Issue1164Object { public T Value = default!; } internal record struct One(int OID); internal record struct Two(int OID, string Name); [Fact] public async Task QuerySplitStruct() // https://github.com/DapperLib/Dapper/issues/2005 { var results = await connection.QueryAsync(@"SELECT 1 AS OID, 2 AS OID, 'Name' AS Name", (x,y) => (x,y), splitOn: "OID"); Assert.Single(results); } [Fact] public void SetDynamicProperty_WithReferenceType_Succeeds() { var obj = connection.QueryFirst("select 1 as ExistingProperty"); obj.ExistingProperty = "foo"; Assert.Equal("foo", (string)obj.ExistingProperty); obj.NewProperty = new Uri("http://example.net/"); Assert.Equal(new Uri("http://example.net/"), (Uri)obj.NewProperty); } [Fact] public void SetDynamicProperty_WithBoxedValueType_Succeeds() { var obj = connection.QueryFirst("select 'foo' as ExistingProperty"); obj.ExistingProperty = (object)1; Assert.Equal(1, (int)obj.ExistingProperty); obj.NewProperty = (object)true; Assert.True(obj.NewProperty); } [Fact] public void SetDynamicProperty_WithValueType_Succeeds() { var obj = connection.QueryFirst("select 'foo' as ExistingProperty"); obj.ExistingProperty = 1; Assert.Equal(1, (int)obj.ExistingProperty); obj.NewProperty = true; Assert.True(obj.NewProperty); } } } ================================================ FILE: tests/Dapper.Tests/MultiMapTests.cs ================================================ using System; using System.Collections.Generic; using System.Data; using System.Linq; using Xunit; namespace Dapper.Tests { [Collection("MultiMapTests")] public sealed class SystemSqlClientMultiMapTests : MultiMapTests { } #if MSSQLCLIENT [Collection("MultiMapTests")] public sealed class MicrosoftSqlClientMultiMapTests : MultiMapTests { } #endif public abstract class MultiMapTests : TestBase where TProvider : DatabaseProvider { [Fact] public void ParentChildIdentityAssociations() { var lookup = new Dictionary(); var parents = connection.Query("select 1 as [Id], 1 as [Id] union all select 1,2 union all select 2,3 union all select 1,4 union all select 3,5", (parent, child) => { if (!lookup.TryGetValue(parent.Id, out Parent? found)) { lookup.Add(parent.Id, found = parent); } found.Children.Add(child); return found; }).Distinct().ToDictionary(p => p.Id); Assert.Equal(3, parents.Count); Assert.True(parents[1].Children.Select(c => c.Id).SequenceEqual(new[] { 1, 2, 4 })); Assert.True(parents[2].Children.Select(c => c.Id).SequenceEqual(new[] { 3 })); Assert.True(parents[3].Children.Select(c => c.Id).SequenceEqual(new[] { 5 })); } private class Parent { public int Id { get; set; } public readonly List Children = new List(); } private class Child { public int Id { get; set; } } [Fact] public void TestMultiMap() { const string createSql = @" create table #Users (Id int, Name varchar(20)) create table #Posts (Id int, OwnerId int, Content varchar(20)) insert #Users values(99, 'Sam') insert #Users values(2, 'I am') insert #Posts values(1, 99, 'Sams Post1') insert #Posts values(2, 99, 'Sams Post2') insert #Posts values(3, null, 'no ones post') "; connection.Execute(createSql); try { const string sql = @"select * from #Posts p left join #Users u on u.Id = p.OwnerId Order by p.Id"; var data = connection.Query(sql, (post, user) => { post.Owner = user; return post; }).ToList(); var p = data[0]; Assert.Equal("Sams Post1", p.Content); Assert.Equal(1, p.Id); Assert.NotNull(p.Owner); Assert.Equal("Sam", p.Owner.Name); Assert.Equal(99, p.Owner.Id); Assert.Null(data[2].Owner); } finally { connection.Execute("drop table #Users drop table #Posts"); } } [Fact] public void TestSchemaChangedMultiMap() { connection.Execute("create table #dog(Age int, Name nvarchar(max)) insert #dog values(1, 'Alf')"); try { var tuple = connection.Query>("select * from #dog d1 join #dog d2 on 1=1", Tuple.Create, splitOn: "Age").Single(); Assert.Equal("Alf", tuple.Item1.Name); Assert.Equal(1, tuple.Item1.Age); Assert.Equal("Alf", tuple.Item2.Name); Assert.Equal(1, tuple.Item2.Age); connection.Execute("alter table #dog drop column Name"); tuple = connection.Query>("select * from #dog d1 join #dog d2 on 1=1", Tuple.Create, splitOn: "Age").Single(); Assert.Null(tuple.Item1.Name); Assert.Equal(1, tuple.Item1.Age); Assert.Null(tuple.Item2.Name); Assert.Equal(1, tuple.Item2.Age); } finally { connection.Execute("drop table #dog"); } } [Fact] public void TestReadMultipleIntegersWithSplitOnAny() { Assert.Equal( new[] { Tuple.Create(1, 2, 3), Tuple.Create(4, 5, 6) }, connection.Query>("select 1,2,3 union all select 4,5,6", Tuple.Create, splitOn: "*") ); } private class Multi1 { public int Id { get; set; } } private class Multi2 { public int Id { get; set; } } [Fact] public void QueryMultimapFromClosed() { using var conn = GetClosedConnection(); Assert.Equal(ConnectionState.Closed, conn.State); var i = conn.Query("select 2 as [Id], 3 as [Id]", (x, y) => x.Id + y.Id).Single(); Assert.Equal(ConnectionState.Closed, conn.State); Assert.Equal(5, i); } [Fact] public void TestMultiMapThreeTypesWithGridReader() { const string createSql = @" create table #Users (Id int, Name varchar(20)) create table #Posts (Id int, OwnerId int, Content varchar(20)) create table #Comments (Id int, PostId int, CommentData varchar(20)) insert #Users values(99, 'Sam') insert #Users values(2, 'I am') insert #Posts values(1, 99, 'Sams Post1') insert #Posts values(2, 99, 'Sams Post2') insert #Posts values(3, null, 'no ones post') insert #Comments values(1, 1, 'Comment 1')"; connection.Execute(createSql); try { const string sql = @"SELECT p.* FROM #Posts p select p.*, u.Id, u.Name + '0' Name, c.Id, c.CommentData from #Posts p left join #Users u on u.Id = p.OwnerId left join #Comments c on c.PostId = p.Id where p.Id = 1 Order by p.Id"; var grid = connection.QueryMultiple(sql); var post1 = grid.Read().ToList(); var post2 = grid.Read((post, user, comment) => { post.Owner = user; post.Comment = comment; return post; }).SingleOrDefault(); Assert.NotNull(post2); Assert.NotNull(post2.Comment); Assert.Equal(1, post2.Comment.Id); Assert.NotNull(post2.Owner); Assert.Equal(99, post2.Owner.Id); } finally { connection.Execute("drop table #Users drop table #Posts drop table #Comments"); } } [Fact] public void TestMultiMapperIsNotConfusedWithUnorderedCols() { var result = connection.Query>("select 1 as Id, 2 as BarId, 3 as BarId, 'a' as Name", Tuple.Create, splitOn: "BarId").First(); Assert.Equal(1, result.Item1.Id); Assert.Equal(2, result.Item1.BarId); Assert.Equal(3, result.Item2.BarId); Assert.Equal("a", result.Item2.Name); } [Fact] public void TestMultiMapperSplitOnError() { var ex = Assert.Throws(() => connection.Query>("select 1 as Id, 2 as BarId", Tuple.Create, splitOn: "DoesntExist").First()); Assert.StartsWith("Multi-map error: splitOn column 'DoesntExist' was not found - please ensure your splitOn parameter is set and in the correct order", ex.Message); } [Fact] public void TestMultiMapDynamic() { const string createSql = @" create table #Users (Id int, Name varchar(20)) create table #Posts (Id int, OwnerId int, Content varchar(20)) insert #Users values(99, 'Sam') insert #Users values(2, 'I am') insert #Posts values(1, 99, 'Sams Post1') insert #Posts values(2, 99, 'Sams Post2') insert #Posts values(3, null, 'no ones post') "; connection.Execute(createSql); const string sql = @"select * from #Posts p left join #Users u on u.Id = p.OwnerId Order by p.Id"; var data = connection.Query(sql, (post, user) => { post.Owner = user; return post; }).ToList(); var p = data[0]; // hairy extension method support for dynamics Assert.Equal("Sams Post1", (string)p.Content); Assert.Equal(1, (int)p.Id); Assert.Equal("Sam", (string)p.Owner.Name); Assert.Equal(99, (int)p.Owner.Id); Assert.Null((object)data[2].Owner); connection.Execute("drop table #Users drop table #Posts"); } [Fact] public void TestMultiMapWithSplit() // https://stackoverflow.com/q/6056778/23354 { const string sql = "select 1 as id, 'abc' as name, 2 as id, 'def' as name"; var product = connection.Query(sql, (prod, cat) => { prod.Category = cat; return prod; }).First(); // assertions Assert.Equal(1, product.Id); Assert.Equal("abc", product.Name); Assert.NotNull(product.Category); Assert.Equal(2, product.Category.Id); Assert.Equal("def", product.Category.Name); } [Fact] public void TestMultiMapWithSplitWithNullValue() // https://stackoverflow.com/q/10744728/449906 { const string sql = "select 1 as id, 'abc' as name, NULL as description, 'def' as name"; var product = connection.Query(sql, (prod, cat) => { prod.Category = cat; return prod; }, splitOn: "description").First(); // assertions Assert.Equal(1, product.Id); Assert.Equal("abc", product.Name); Assert.Null(product.Category); } [Fact] public void TestMultiMapWithSplitWithNullValueAndSpoofColumn() // https://stackoverflow.com/q/10744728/449906 { const string sql = "select 1 as id, 'abc' as name, 1 as spoof, NULL as description, 'def' as name"; var product = connection.Query(sql, (prod, cat) => { prod.Category = cat; return prod; }, splitOn: "spoof").First(); // assertions Assert.Equal(1, product.Id); Assert.Equal("abc", product.Name); Assert.NotNull(product.Category); Assert.Equal(0, product.Category.Id); Assert.Equal("def", product.Category.Name); Assert.Null(product.Category.Description); } [Fact] public void TestMultiMappingVariations() { const string sql = "select 1 as Id, 'a' as Content, 2 as Id, 'b' as Content, 3 as Id, 'c' as Content, 4 as Id, 'd' as Content, 5 as Id, 'e' as Content"; var order = connection.Query(sql, (o, owner, creator) => { o.Owner = owner; o.Creator = creator; return o; }).First(); Assert.Equal(order.Id, 1); Assert.Equal(order.Content, "a"); Assert.Equal(order.Owner.Id, 2); Assert.Equal(order.Owner.Content, "b"); Assert.Equal(order.Creator.Id, 3); Assert.Equal(order.Creator.Content, "c"); order = connection.Query(sql, (o, owner, creator, address) => { o.Owner = owner; o.Creator = creator; o.Owner.Address = address; return o; }).First(); Assert.Equal(order.Id, 1); Assert.Equal(order.Content, "a"); Assert.Equal(order.Owner.Id, 2); Assert.Equal(order.Owner.Content, "b"); Assert.Equal(order.Creator.Id, 3); Assert.Equal(order.Creator.Content, "c"); Assert.Equal(order.Owner.Address.Id, 4); Assert.Equal(order.Owner.Address.Content, "d"); order = connection.Query(sql, (a, b, c, d, e) => { a.B = b; a.C = c; a.C.D = d; a.E = e; return a; }).First(); Assert.Equal(order.Id, 1); Assert.Equal(order.Content, "a"); Assert.Equal(order.B.Id, 2); Assert.Equal(order.B.Content, "b"); Assert.Equal(order.C.Id, 3); Assert.Equal(order.C.Content, "c"); Assert.Equal(order.C.D.Id, 4); Assert.Equal(order.C.D.Content, "d"); Assert.Equal(order.E.Id, 5); Assert.Equal(order.E.Content, "e"); } private class UserWithConstructor { public UserWithConstructor(int id, string name) { Ident = id; FullName = name; } public int Ident { get; set; } public string FullName { get; set; } } private class PostWithConstructor { public PostWithConstructor(int id, int ownerid, string content) { Ident = id; FullContent = content; } public int Ident { get; set; } public UserWithConstructor? Owner { get; set; } public string FullContent { get; set; } public Comment? Comment { get; set; } } [Fact] public void TestMultiMapWithConstructor() { const string createSql = @" create table #Users (Id int, Name varchar(20)) create table #Posts (Id int, OwnerId int, Content varchar(20)) insert #Users values(99, 'Sam') insert #Users values(2, 'I am') insert #Posts values(1, 99, 'Sams Post1') insert #Posts values(2, 99, 'Sams Post2') insert #Posts values(3, null, 'no ones post')"; connection.Execute(createSql); try { const string sql = @"select * from #Posts p left join #Users u on u.Id = p.OwnerId Order by p.Id"; PostWithConstructor[] data = connection.Query(sql, (post, user) => { post.Owner = user; return post; }).ToArray(); var p = data.First(); Assert.Equal("Sams Post1", p.FullContent); Assert.Equal(1, p.Ident); Assert.NotNull(p.Owner); Assert.Equal("Sam", p.Owner.FullName); Assert.Equal(99, p.Owner.Ident); Assert.Null(data[2].Owner); } finally { connection.Execute("drop table #Users drop table #Posts"); } } [Fact] public void TestMultiMapArbitraryMaps() { // please excuse the trite example, but it is easier to follow than a more real-world one const string createSql = @" create table #ReviewBoards (Id int, Name varchar(20), User1Id int, User2Id int, User3Id int, User4Id int, User5Id int, User6Id int, User7Id int, User8Id int, User9Id int) create table #Users (Id int, Name varchar(20)) insert #Users values(1, 'User 1') insert #Users values(2, 'User 2') insert #Users values(3, 'User 3') insert #Users values(4, 'User 4') insert #Users values(5, 'User 5') insert #Users values(6, 'User 6') insert #Users values(7, 'User 7') insert #Users values(8, 'User 8') insert #Users values(9, 'User 9') insert #ReviewBoards values(1, 'Review Board 1', 1, 2, 3, 4, 5, 6, 7, 8, 9) "; connection.Execute(createSql); try { const string sql = @" select rb.Id, rb.Name, u1.*, u2.*, u3.*, u4.*, u5.*, u6.*, u7.*, u8.*, u9.* from #ReviewBoards rb inner join #Users u1 on u1.Id = rb.User1Id inner join #Users u2 on u2.Id = rb.User2Id inner join #Users u3 on u3.Id = rb.User3Id inner join #Users u4 on u4.Id = rb.User4Id inner join #Users u5 on u5.Id = rb.User5Id inner join #Users u6 on u6.Id = rb.User6Id inner join #Users u7 on u7.Id = rb.User7Id inner join #Users u8 on u8.Id = rb.User8Id inner join #Users u9 on u9.Id = rb.User9Id "; var types = new[] { typeof(ReviewBoard), typeof(User), typeof(User), typeof(User), typeof(User), typeof(User), typeof(User), typeof(User), typeof(User), typeof(User) }; Func mapper = (objects) => { var board = (ReviewBoard)objects[0]; board.User1 = (User)objects[1]; board.User2 = (User)objects[2]; board.User3 = (User)objects[3]; board.User4 = (User)objects[4]; board.User5 = (User)objects[5]; board.User6 = (User)objects[6]; board.User7 = (User)objects[7]; board.User8 = (User)objects[8]; board.User9 = (User)objects[9]; return board; }; var data = connection.Query(sql, types, mapper).ToList(); var p = data[0]; Assert.Equal(1, p.Id); Assert.Equal("Review Board 1", p.Name); Assert.NotNull(p.User1); Assert.NotNull(p.User2); Assert.NotNull(p.User3); Assert.NotNull(p.User4); Assert.NotNull(p.User5); Assert.NotNull(p.User6); Assert.NotNull(p.User7); Assert.NotNull(p.User8); Assert.NotNull(p.User9); Assert.Equal(1, p.User1.Id); Assert.Equal(2, p.User2.Id); Assert.Equal(3, p.User3.Id); Assert.Equal(4, p.User4.Id); Assert.Equal(5, p.User5.Id); Assert.Equal(6, p.User6.Id); Assert.Equal(7, p.User7.Id); Assert.Equal(8, p.User8.Id); Assert.Equal(9, p.User9.Id); Assert.Equal("User 1", p.User1.Name); Assert.Equal("User 2", p.User2.Name); Assert.Equal("User 3", p.User3.Name); Assert.Equal("User 4", p.User4.Name); Assert.Equal("User 5", p.User5.Name); Assert.Equal("User 6", p.User6.Name); Assert.Equal("User 7", p.User7.Name); Assert.Equal("User 8", p.User8.Name); Assert.Equal("User 9", p.User9.Name); } finally { connection.Execute("drop table #Users drop table #ReviewBoards"); } } [Fact] public void TestMultiMapGridReader() { const string createSql = @" create table #Users (Id int, Name varchar(20)) create table #Posts (Id int, OwnerId int, Content varchar(20)) insert #Users values(99, 'Sam') insert #Users values(2, 'I am') insert #Posts values(1, 99, 'Sams Post1') insert #Posts values(2, 99, 'Sams Post2') insert #Posts values(3, null, 'no ones post') "; connection.Execute(createSql); const string sql = @"select p.*, u.Id, u.Name + '0' Name from #Posts p left join #Users u on u.Id = p.OwnerId Order by p.Id select p.*, u.Id, u.Name + '1' Name from #Posts p left join #Users u on u.Id = p.OwnerId Order by p.Id "; var grid = connection.QueryMultiple(sql); for (int i = 0; i < 2; i++) { var data = grid.Read((post, user) => { post.Owner = user; return post; }).ToList(); var p = data[0]; Assert.Equal("Sams Post1", p.Content); Assert.Equal(1, p.Id); Assert.NotNull(p.Owner); Assert.Equal(p.Owner.Name, "Sam" + i); Assert.Equal(99, p.Owner.Id); Assert.Null(data[2].Owner); } connection.Execute("drop table #Users drop table #Posts"); } [Fact] public void TestFlexibleMultiMapping() { const string sql = @"select 1 as PersonId, 'bob' as Name, 2 as AddressId, 'abc street' as Name, 1 as PersonId, 3 as Id, 'fred' as Name "; var personWithAddress = connection.Query> (sql, Tuple.Create, splitOn: "AddressId,Id").First(); Assert.Equal(1, personWithAddress.Item1.PersonId); Assert.Equal("bob", personWithAddress.Item1.Name); Assert.Equal(2, personWithAddress.Item2.AddressId); Assert.Equal("abc street", personWithAddress.Item2.Name); Assert.Equal(1, personWithAddress.Item2.PersonId); Assert.Equal(3, personWithAddress.Item3.Id); Assert.Equal("fred", personWithAddress.Item3.Name); } [Fact] public void TestMultiMappingWithSplitOnSpaceBetweenCommas() { const string sql = @"select 1 as PersonId, 'bob' as Name, 2 as AddressId, 'abc street' as Name, 1 as PersonId, 3 as Id, 'fred' as Name "; var personWithAddress = connection.Query> (sql, Tuple.Create, splitOn: "AddressId, Id").First(); Assert.Equal(1, personWithAddress.Item1.PersonId); Assert.Equal("bob", personWithAddress.Item1.Name); Assert.Equal(2, personWithAddress.Item2.AddressId); Assert.Equal("abc street", personWithAddress.Item2.Name); Assert.Equal(1, personWithAddress.Item2.PersonId); Assert.Equal(3, personWithAddress.Item3.Id); Assert.Equal("fred", personWithAddress.Item3.Name); } private class Extra { public int Id { get; set; } public string? Name { get; set; } } [Fact] public void TestMultiMappingWithNonReturnedProperty() { const string sql = @"select 1 as PostId, 'Title' as Title, 2 as BlogId, 'Blog' as Title"; var postWithBlog = connection.Query(sql, (p, b) => { p.Blog = b; return p; }, splitOn: "BlogId").First(); Assert.Equal(1, postWithBlog.PostId); Assert.Equal("Title", postWithBlog.Title); Assert.NotNull(postWithBlog.Blog); Assert.Equal(2, postWithBlog.Blog.BlogId); Assert.Equal("Blog", postWithBlog.Blog.Title); } private class Post_DupeProp { public int PostId { get; set; } public string? Title { get; set; } public int BlogId { get; set; } public Blog_DupeProp? Blog { get; set; } } private class Blog_DupeProp { public int BlogId { get; set; } public string? Title { get; set; } } // see https://stackoverflow.com/questions/16955357/issue-about-dapper [Fact] public void TestSplitWithMissingMembers() { var result = connection.Query( @"select 123 as ID, 'abc' as Title, cast('01 Feb 2013' as datetime) as CreateDate, 'ghi' as Name, 'def' as Phone", (T, P) => { T.Author = P; return T; }, null, null, true, "ID,Name").Single(); Assert.Equal(123, result.ID); Assert.Equal("abc", result.Title); Assert.Equal(new DateTime(2013, 2, 1), result.CreateDate); Assert.Null(result.Name); Assert.Null(result.Content); Assert.NotNull(result.Author); Assert.Equal("def", result.Author.Phone); Assert.Equal("ghi", result.Author.Name); Assert.Equal(0, result.Author.ID); Assert.Null(result.Author.Address); } public class Profile { public int ID { get; set; } public string? Name { get; set; } public string? Phone { get; set; } public string? Address { get; set; } //public ExtraInfo Extra { get; set; } } public class Topic { public int ID { get; set; } public string? Title { get; set; } public DateTime CreateDate { get; set; } public string? Content { get; set; } public int UID { get; set; } public int TestColum { get; set; } public string? Name { get; set; } public Profile? Author { get; set; } //public Attachment Attach { get; set; } } [Fact] public void TestInvalidSplitCausesNiceError() { try { connection.Query("select 1 A, 2 B, 3 C", (x, y) => x); } catch (ArgumentException) { // expecting an app exception due to multi mapping being bodged } try { connection.Query("select 1 A, 2 B, 3 C", (x, y) => x); } catch (ArgumentException) { // expecting an app exception due to multi mapping being bodged } } } } ================================================ FILE: tests/Dapper.Tests/NullTests.cs ================================================ using Xunit; using System.Linq; namespace Dapper.Tests { [Collection(NonParallelDefinition.Name)] public sealed class SystemSqlClientNullTests : NullTests { } #if MSSQLCLIENT [Collection(NonParallelDefinition.Name)] public sealed class MicrosoftSqlClientNullTests : NullTests { } #endif public abstract class NullTests : TestBase where TProvider : DatabaseProvider { [Fact] public void TestNullableDefault() { TestNullable(false); } [Fact] public void TestNullableApplyNulls() { TestNullable(true); } private void TestNullable(bool applyNulls) { bool oldSetting = SqlMapper.Settings.ApplyNullValues; try { SqlMapper.Settings.ApplyNullValues = applyNulls; SqlMapper.PurgeQueryCache(); var data = connection.Query(@" declare @data table(Id int not null, A int null, B int null, C varchar(20), D int null, E int null) insert @data (Id, A, B, C, D, E) values (1,null,null,null,null,null), (2,42,42,'abc',2,2) select * from @data").ToDictionary(_ => _.Id); var obj = data[2]; Assert.Equal(2, obj.Id); Assert.Equal(42, obj.A); Assert.Equal(42, obj.B); Assert.Equal("abc", obj.C); Assert.Equal(AnEnum.A, obj.D); Assert.Equal(AnEnum.A, obj.E); obj = data[1]; Assert.Equal(1, obj.Id); if (applyNulls) { Assert.Equal(2, obj.A); // cannot be null Assert.Null(obj.B); Assert.Null(obj.C); Assert.Equal(AnEnum.B, obj.D); Assert.Null(obj.E); } else { Assert.Equal(2, obj.A); Assert.Equal(2, obj.B); Assert.Equal("def", obj.C); Assert.Equal(AnEnum.B, obj.D); Assert.Equal(AnEnum.B, obj.E); } } finally { SqlMapper.Settings.ApplyNullValues = oldSetting; } } private class NullTestClass { public int Id { get; set; } public int A { get; set; } public int? B { get; set; } public string C { get; set; } public AnEnum D { get; set; } public AnEnum? E { get; set; } public NullTestClass() { A = 2; B = 2; C = "def"; D = AnEnum.B; E = AnEnum.B; } } } } ================================================ FILE: tests/Dapper.Tests/ParameterTests.cs ================================================ using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Data.Common; using System.Data.SqlTypes; using System.Diagnostics; using System.Dynamic; using System.Globalization; using System.Linq; using System.Text.RegularExpressions; using Xunit; #if ENTITY_FRAMEWORK using System.Data.Entity.Spatial; using Microsoft.SqlServer.Types; #endif [assembly: CollectionBehavior(DisableTestParallelization = true)] namespace Dapper.Tests { [Collection(NonParallelDefinition.Name)] // because it creates SQL types that compete between the two providers public sealed class SystemSqlClientParameterTests : ParameterTests { } #if MSSQLCLIENT [Collection(NonParallelDefinition.Name)] // because it creates SQL types that compete between the two providers public sealed class MicrosoftSqlClientParameterTests : ParameterTests { } #endif public abstract class ParameterTests : TestBase where TProvider : DatabaseProvider { public class DbDynamicParams : SqlMapper.IDynamicParameters, IEnumerable { private readonly List parameters = new List(); public IEnumerator GetEnumerator() { return parameters.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public void Add(IDbDataParameter value) { parameters.Add(value); } void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Identity identity) { foreach (IDbDataParameter parameter in parameters) command.Parameters.Add(parameter); } } public class DbCustomParam : SqlMapper.ICustomQueryParameter { private readonly IDbDataParameter _sqlParameter; public DbCustomParam(IDbDataParameter sqlParameter) { _sqlParameter = sqlParameter; } public void AddParameter(IDbCommand command, string name) { command.Parameters.Add(_sqlParameter); } } private static IEnumerable CreateSqlDataRecordList(IDbCommand command, IEnumerable numbers) { #pragma warning disable CS0618 // Type or member is obsolete if (command is System.Data.SqlClient.SqlCommand) return CreateSqlDataRecordList_SD(numbers); #pragma warning restore CS0618 // Type or member is obsolete if (command is Microsoft.Data.SqlClient.SqlCommand) return CreateSqlDataRecordList_MD(numbers); throw new ArgumentException(nameof(command)); } private static IEnumerable CreateSqlDataRecordList(IDbConnection connection, IEnumerable numbers) { #pragma warning disable CS0618 // Type or member is obsolete if (connection is System.Data.SqlClient.SqlConnection) return CreateSqlDataRecordList_SD(numbers); #pragma warning restore CS0618 // Type or member is obsolete if (connection is Microsoft.Data.SqlClient.SqlConnection) return CreateSqlDataRecordList_MD(numbers); throw new ArgumentException(nameof(connection)); } #pragma warning disable CS0618 // Type or member is obsolete private static List CreateSqlDataRecordList_SD(IEnumerable numbers) { var number_list = new List(); // Create an SqlMetaData object that describes our table type. Microsoft.SqlServer.Server.SqlMetaData[] tvp_definition = { new Microsoft.SqlServer.Server.SqlMetaData("n", SqlDbType.Int) }; foreach (int n in numbers) { // Create a new record, using the metadata array above. var rec = new Microsoft.SqlServer.Server.SqlDataRecord(tvp_definition); rec.SetInt32(0, n); // Set the value. number_list.Add(rec); // Add it to the list. } return number_list; } #pragma warning restore CS0618 // Type or member is obsolete private static List CreateSqlDataRecordList_MD(IEnumerable numbers) { var number_list = new List(); // Create an SqlMetaData object that describes our table type. Microsoft.Data.SqlClient.Server.SqlMetaData[] tvp_definition = { new Microsoft.Data.SqlClient.Server.SqlMetaData("n", SqlDbType.Int) }; foreach (int n in numbers) { // Create a new record, using the metadata array above. var rec = new Microsoft.Data.SqlClient.Server.SqlDataRecord(tvp_definition); rec.SetInt32(0, n); // Set the value. number_list.Add(rec); // Add it to the list. } return number_list; } private class IntDynamicParam : SqlMapper.IDynamicParameters { private readonly IEnumerable numbers; public IntDynamicParam(IEnumerable numbers) { this.numbers = numbers; } public void AddParameters(IDbCommand command, SqlMapper.Identity identity) { command.CommandType = CommandType.StoredProcedure; var number_list = CreateSqlDataRecordList(command, numbers); AddStructured(command, number_list); } } private class IntCustomParam : SqlMapper.ICustomQueryParameter { private readonly IEnumerable numbers; public IntCustomParam(IEnumerable numbers) { this.numbers = numbers; } public void AddParameter(IDbCommand command, string name) { command.CommandType = CommandType.StoredProcedure; var number_list = CreateSqlDataRecordList(command, numbers); // Add the table parameter. AddStructured(command, number_list); } } private static IDbDataParameter AddStructured(IDbCommand command, object value) { #pragma warning disable CS0618 // Type or member is obsolete if (command is System.Data.SqlClient.SqlCommand sdcmd) { var p = sdcmd.Parameters.Add("integers", SqlDbType.Structured); p.Direction = ParameterDirection.Input; p.TypeName = "int_list_type"; p.Value = value; return p; } #pragma warning restore CS0618 // Type or member is obsolete else if (command is Microsoft.Data.SqlClient.SqlCommand mdcmd) { var p = mdcmd.Parameters.Add("integers", SqlDbType.Structured); p.Direction = ParameterDirection.Input; p.TypeName = "int_list_type"; p.Value = value; return p; } else throw new ArgumentException(nameof(command)); } /* TODO: * public void TestMagicParam() { // magic params allow you to pass in single params without using an anon class // this test fails for now, but I would like to support a single param by parsing the sql with regex and remapping. var first = connection.Query("select @a as a", 1).First(); Assert.Equal(first.a, 1); } * */ [Fact] public void TestDoubleParam() { Assert.Equal(0.1d, connection.Query("select @d", new { d = 0.1d }).First()); } [Fact] public void TestBoolParam() { Assert.False(connection.Query("select @b", new { b = false }).First()); } // http://code.google.com/p/dapper-dot-net/issues/detail?id=70 // https://connect.microsoft.com/VisualStudio/feedback/details/381934/sqlparameter-dbtype-dbtype-time-sets-the-parameter-to-sqldbtype-datetime-instead-of-sqldbtype-time [Fact] public void TestTimeSpanParam() { Assert.Equal(connection.Query("select @ts", new { ts = TimeSpan.FromMinutes(42) }).First(), TimeSpan.FromMinutes(42)); } [Fact] public void PassInIntArray() { Assert.Equal( new[] { 1, 2, 3 }, connection.Query("select * from (select 1 as Id union all select 2 union all select 3) as X where Id in @Ids", new { Ids = new int[] { 1, 2, 3 }.AsEnumerable() }) ); } [Fact] public void PassInEmptyIntArray() { Assert.Equal( Array.Empty(), connection.Query("select * from (select 1 as Id union all select 2 union all select 3) as X where Id in @Ids", new { Ids = Array.Empty() }) ); } [Fact] public void TestExecuteCommandWithHybridParameters() { var p = new DynamicParameters(new { a = 1, b = 2 }); p.Add("c", dbType: DbType.Int32, direction: ParameterDirection.Output); connection.Execute("set @c = @a + @b", p); Assert.Equal(3, p.Get("@c")); } [Fact] public void GuidIn_SO_24177902() { // invent and populate Guid a = Guid.NewGuid(), b = Guid.NewGuid(), c = Guid.NewGuid(), d = Guid.NewGuid(); connection.Execute("create table #foo (i int, g uniqueidentifier)"); connection.Execute("insert #foo(i,g) values(@i,@g)", new[] { new { i = 1, g = a }, new { i = 2, g = b }, new { i = 3, g = c },new { i = 4, g = d }}); // check that rows 2&3 yield guids b&c var guids = connection.Query("select g from #foo where i in (2,3)").ToArray(); Assert.Equal(2, guids.Length); Assert.DoesNotContain(a, guids); Assert.Contains(b, guids); Assert.Contains(c, guids); Assert.DoesNotContain(d, guids); // in query on the guids var rows = connection.Query("select * from #foo where g in @guids order by i", new { guids }) .Select(row => new { i = (int)row.i, g = (Guid)row.g }).ToArray(); Assert.Equal(2, rows.Length); Assert.Equal(2, rows[0].i); Assert.Equal(b, rows[0].g); Assert.Equal(3, rows[1].i); Assert.Equal(c, rows[1].g); } [FactUnlessCaseSensitiveDatabase] public void TestParameterInclusionNotSensitiveToCurrentCulture() { // note this might fail if your database server is case-sensitive CultureInfo current = ActiveCulture; try { ActiveCulture = new CultureInfo("tr-TR"); connection.Query("select @pid", new { PId = 1 }).Single(); } finally { ActiveCulture = current; } } [Fact] public void TestMassiveStrings() { var str = new string('X', 20000); Assert.Equal(connection.Query("select @a", new { a = str }).First(), str); } [Fact] public void TestTVPWithAnonymousObject() { try { connection.Execute("CREATE TYPE int_list_type AS TABLE (n int NOT NULL PRIMARY KEY)"); connection.Execute("CREATE PROC get_ints @integers int_list_type READONLY AS select * from @integers"); var nums = connection.Query("get_ints", new { integers = new IntCustomParam(new int[] { 1, 2, 3 }) }, commandType: CommandType.StoredProcedure).ToList(); Assert.Equal(1, nums[0]); Assert.Equal(2, nums[1]); Assert.Equal(3, nums[2]); Assert.Equal(3, nums.Count); } finally { try { connection.Execute("DROP PROC get_ints"); } finally { connection.Execute("DROP TYPE int_list_type"); } } } [Fact] public void TestTVPWithAnonymousEmptyObject() { try { connection.Execute("CREATE TYPE int_list_type AS TABLE (n int NOT NULL PRIMARY KEY)"); connection.Execute("CREATE PROC get_ints @integers int_list_type READONLY AS select * from @integers"); var nums = connection.Query("get_ints", new { integers = new IntCustomParam(new int[] { }) }, commandType: CommandType.StoredProcedure).ToList(); Assert.Equal(1, nums[0]); Assert.Equal(2, nums[1]); Assert.Equal(3, nums[2]); Assert.Equal(3, nums.Count); } catch (ArgumentException ex) { Assert.True(string.Compare(ex.Message, "There are no records in the SqlDataRecord enumeration. To send a table-valued parameter with no rows, use a null reference for the value instead.") == 0); } finally { try { connection.Execute("DROP PROC get_ints"); } finally { connection.Execute("DROP TYPE int_list_type"); } } } // SQL Server specific test to demonstrate TVP [Fact] public void TestTVP() { try { connection.Execute("CREATE TYPE int_list_type AS TABLE (n int NOT NULL PRIMARY KEY)"); connection.Execute("CREATE PROC get_ints @integers int_list_type READONLY AS select * from @integers"); var nums = connection.Query("get_ints", new IntDynamicParam(new int[] { 1, 2, 3 })).ToList(); Assert.Equal(1, nums[0]); Assert.Equal(2, nums[1]); Assert.Equal(3, nums[2]); Assert.Equal(3, nums.Count); } finally { try { try { connection.Execute("DROP PROC get_ints"); } catch { } } finally { try { connection.Execute("DROP TYPE int_list_type"); } catch { } } } } private class DynamicParameterWithIntTVP : DynamicParameters, SqlMapper.IDynamicParameters { private readonly IEnumerable numbers; public DynamicParameterWithIntTVP(IEnumerable numbers) { this.numbers = numbers; } public new void AddParameters(IDbCommand command, SqlMapper.Identity identity) { base.AddParameters(command, identity); command.CommandType = CommandType.StoredProcedure; var number_list = CreateSqlDataRecordList(command, numbers); // Add the table parameter. AddStructured(command, number_list); } } [Fact] public void TestTVPWithAdditionalParams() { try { connection.Execute("CREATE TYPE int_list_type AS TABLE (n int NOT NULL PRIMARY KEY)"); connection.Execute("CREATE PROC get_values @integers int_list_type READONLY, @stringParam varchar(20), @dateParam datetime AS select i.*, @stringParam as stringParam, @dateParam as dateParam from @integers i"); var dynamicParameters = new DynamicParameterWithIntTVP(new int[] { 1, 2, 3 }); dynamicParameters.AddDynamicParams(new { stringParam = "stringParam", dateParam = new DateTime(2012, 1, 1) }); var results = connection.Query("get_values", dynamicParameters, commandType: CommandType.StoredProcedure).ToList(); Assert.Equal(3, results.Count); for (int i = 0; i < results.Count; i++) { var result = results[i]; Assert.Equal(i + 1, result.n); Assert.Equal("stringParam", result.stringParam); Assert.Equal(new DateTime(2012, 1, 1), result.dateParam); } } finally { try { connection.Execute("DROP PROC get_values"); } finally { connection.Execute("DROP TYPE int_list_type"); } } } [Fact] public void TestSqlDataRecordListParametersWithAsTableValuedParameter() { try { connection.Execute("CREATE TYPE int_list_type AS TABLE (n int NOT NULL PRIMARY KEY)"); connection.Execute("CREATE PROC get_ints @integers int_list_type READONLY AS select * from @integers"); var records = CreateSqlDataRecordList(connection, new int[] { 1, 2, 3 }); var nums = connection.Query("get_ints", new { integers = records.AsTableValuedParameter() }, commandType: CommandType.StoredProcedure).ToList(); Assert.Equal(new int[] { 1, 2, 3 }, nums); nums = connection.Query("select * from @integers", new { integers = records.AsTableValuedParameter("int_list_type") }).ToList(); Assert.Equal(new int[] { 1, 2, 3 }, nums); try { connection.Query("select * from @integers", new { integers = records.AsTableValuedParameter() }).First(); throw new InvalidOperationException(); } catch (Exception ex) { ex.Message.Equals("The table type parameter 'ids' must have a valid type name."); } } finally { try { connection.Execute("DROP PROC get_ints"); } finally { connection.Execute("DROP TYPE int_list_type"); } } } [Fact] public void TestEmptySqlDataRecordListParametersWithAsTableValuedParameter() { try { connection.Execute("CREATE TYPE int_list_type AS TABLE (n int NOT NULL PRIMARY KEY)"); connection.Execute("CREATE PROC get_ints @integers int_list_type READONLY AS select * from @integers"); var emptyRecord = CreateSqlDataRecordList(connection, Enumerable.Empty()); var nums = connection.Query("get_ints", new { integers = emptyRecord.AsTableValuedParameter() }, commandType: CommandType.StoredProcedure).ToList(); Assert.True(nums.Count == 0); } finally { try { connection.Execute("DROP PROC get_ints"); } finally { connection.Execute("DROP TYPE int_list_type"); } } } [Fact] public void TestSqlDataRecordListParametersWithTypeHandlers() { try { connection.Execute("CREATE TYPE int_list_type AS TABLE (n int NOT NULL PRIMARY KEY)"); connection.Execute("CREATE PROC get_ints @integers int_list_type READONLY AS select * from @integers"); // Variable type has to be IEnumerable for TypeHandler to kick in. object args; #pragma warning disable CS0618 // Type or member is obsolete if (connection is System.Data.SqlClient.SqlConnection) { IEnumerable records = CreateSqlDataRecordList_SD(new int[] { 1, 2, 3 }); args = new { integers = records }; } #pragma warning restore CS0618 // Type or member is obsolete else if (connection is Microsoft.Data.SqlClient.SqlConnection) { IEnumerable records = CreateSqlDataRecordList_MD(new int[] { 1, 2, 3 }); args = new { integers = records }; } else { throw new ArgumentException(nameof(connection)); } var nums = connection.Query("get_ints", args, commandType: CommandType.StoredProcedure).ToList(); Assert.Equal(new int[] { 1, 2, 3 }, nums); try { connection.Query("select * from @integers", args).First(); throw new InvalidOperationException(); } catch (Exception ex) { ex.Message.Equals("The table type parameter 'ids' must have a valid type name."); } } finally { try { connection.Execute("DROP PROC get_ints"); } finally { connection.Execute("DROP TYPE int_list_type"); } } } [Fact] public void DataTableParameters() { try { connection.Execute("drop proc #DataTableParameters"); } catch { /* don't care */ } try { connection.Execute("drop table #DataTableParameters"); } catch { /* don't care */ } try { connection.Execute("drop type MyTVPType"); } catch { /* don't care */ } connection.Execute("create type MyTVPType as table (id int)"); connection.Execute("create proc #DataTableParameters @ids MyTVPType readonly as select count(1) from @ids"); var table = new DataTable { Columns = { { "id", typeof(int) } }, Rows = { { 1 }, { 2 }, { 3 } } }; int count = connection.Query("#DataTableParameters", new { ids = table.AsTableValuedParameter() }, commandType: CommandType.StoredProcedure).First(); Assert.Equal(3, count); count = connection.Query("select count(1) from @ids", new { ids = table.AsTableValuedParameter("MyTVPType") }).First(); Assert.Equal(3, count); try { connection.Query("select count(1) from @ids", new { ids = table.AsTableValuedParameter() }).First(); throw new InvalidOperationException(); } catch (Exception ex) { ex.Message.Equals("The table type parameter 'ids' must have a valid type name."); } } [Fact] public void SO29533765_DataTableParametersViaDynamicParameters() { try { connection.Execute("drop proc #DataTableParameters"); } catch { /* don't care */ } try { connection.Execute("drop table #DataTableParameters"); } catch { /* don't care */ } try { connection.Execute("drop type MyTVPType"); } catch { /* don't care */ } connection.Execute("create type MyTVPType as table (id int)"); connection.Execute("create proc #DataTableParameters @ids MyTVPType readonly as select count(1) from @ids"); var table = new DataTable { TableName = "MyTVPType", Columns = { { "id", typeof(int) } }, Rows = { { 1 }, { 2 }, { 3 } } }; table.SetTypeName(table.TableName); // per SO29533765 IDictionary args = new Dictionary(1) { ["ids"] = table }; int count = connection.Query("#DataTableParameters", args, commandType: CommandType.StoredProcedure).First(); Assert.Equal(3, count); count = connection.Query("select count(1) from @ids", args).First(); Assert.Equal(3, count); } [Fact] public void SO26468710_InWithTVPs() { // this is just to make it re-runnable; normally you only do this once try { connection.Execute("drop type MyIdList"); } catch { /* don't care */ } connection.Execute("create type MyIdList as table(id int);"); var ids = new DataTable { Columns = { { "id", typeof(int) } }, Rows = { { 1 }, { 3 }, { 5 } } }; ids.SetTypeName("MyIdList"); int sum = connection.Query(@" declare @tmp table(id int not null); insert @tmp (id) values(1), (2), (3), (4), (5), (6), (7); select * from @tmp t inner join @ids i on i.id = t.id", new { ids }).Sum(); Assert.Equal(9, sum); } [Fact] public void DataTableParametersWithExtendedProperty() { try { connection.Execute("drop proc #DataTableParameters"); } catch { /* don't care */ } try { connection.Execute("drop table #DataTableParameters"); } catch { /* don't care */ } try { connection.Execute("drop type MyTVPType"); } catch { /* don't care */ } connection.Execute("create type MyTVPType as table (id int)"); connection.Execute("create proc #DataTableParameters @ids MyTVPType readonly as select count(1) from @ids"); var table = new DataTable { Columns = { { "id", typeof(int) } }, Rows = { { 1 }, { 2 }, { 3 } } }; table.SetTypeName("MyTVPType"); // <== extended metadata int count = connection.Query("#DataTableParameters", new { ids = table }, commandType: CommandType.StoredProcedure).First(); Assert.Equal(3, count); count = connection.Query("select count(1) from @ids", new { ids = table }).First(); Assert.Equal(3, count); try { connection.Query("select count(1) from @ids", new { ids = table }).First(); throw new InvalidOperationException(); } catch (Exception ex) { ex.Message.Equals("The table type parameter 'ids' must have a valid type name."); } } [Fact] public void SupportInit() { var obj = connection.Query("select 'abc' as Value").Single(); Assert.Equal("abc", obj.Value); Assert.Equal(31, obj.Flags); } public class WithInit : ISupportInitialize { public string? Value { get; set; } public int Flags { get; set; } void ISupportInitialize.BeginInit() => Flags++; void ISupportInitialize.EndInit() => Flags += 30; } [Fact] public void SO29596645_TvpProperty() { try { connection.Execute("CREATE TYPE SO29596645_ReminderRuleType AS TABLE (id int NOT NULL)"); } catch { /* don't care */ } connection.Execute(@"create proc #SO29596645_Proc (@Id int, @Rules SO29596645_ReminderRuleType READONLY) as begin select @Id + ISNULL((select sum(id) from @Rules), 0); end"); var obj = new SO29596645_OrganisationDTO(); int val = connection.Query("#SO29596645_Proc", obj.Rules, commandType: CommandType.StoredProcedure).Single(); // 4 + 9 + 7 = 20 Assert.Equal(20, val); } private class SO29596645_RuleTableValuedParameters : SqlMapper.IDynamicParameters { private readonly string parameterName; public SO29596645_RuleTableValuedParameters(string parameterName) { this.parameterName = parameterName; } public void AddParameters(IDbCommand command, SqlMapper.Identity identity) { Debug.WriteLine("> AddParameters"); var p = command.CreateParameter(); p.ParameterName = "Id"; p.Value = 7; command.Parameters.Add(p); var table = new DataTable { Columns = { { "Id", typeof(int) } }, Rows = { { 4 }, { 9 } } }; p = command.CreateParameter(); p.ParameterName = "Rules"; p.Value = table; command.Parameters.Add(p); Debug.WriteLine("< AddParameters"); } } private class SO29596645_OrganisationDTO { public SO29596645_RuleTableValuedParameters Rules { get; } public SO29596645_OrganisationDTO() { Rules = new SO29596645_RuleTableValuedParameters("@Rules"); } } #if ENTITY_FRAMEWORK private class HazGeo { public int Id { get; set; } public DbGeography? Geo { get; set; } public DbGeometry? Geometry { get; set; } } private class HazSqlGeo { public int Id { get; set; } public SqlGeography? Geo { get; set; } public SqlGeometry? Geometry { get; set; } } [Fact] public void DBGeography_SO24405645_SO24402424() { SkipIfMsDataClient(); EntityFramework.Handlers.Register(); connection.Execute("create table #Geo (id int, geo geography, geometry geometry)"); var obj = new HazGeo { Id = 1, Geo = DbGeography.LineFromText("LINESTRING(-122.360 47.656, -122.343 47.656 )", 4326), Geometry = DbGeometry.LineFromText("LINESTRING (100 100, 20 180, 180 180)", 0) }; connection.Execute("insert #Geo(id, geo, geometry) values (@Id, @Geo, @Geometry)", obj); var row = connection.Query("select * from #Geo where id=1").SingleOrDefault(); Assert.NotNull(row); Assert.Equal(1, row.Id); Assert.NotNull(row.Geo); Assert.NotNull(row.Geometry); } [Fact] public void SqlGeography_SO25538154() { SkipIfMsDataClient(); SqlMapper.ResetTypeHandlers(); connection.Execute("create table #SqlGeo (id int, geo geography, geometry geometry)"); var obj = new HazSqlGeo { Id = 1, Geo = SqlGeography.STLineFromText(new SqlChars(new SqlString("LINESTRING(-122.360 47.656, -122.343 47.656 )")), 4326), Geometry = SqlGeometry.STLineFromText(new SqlChars(new SqlString("LINESTRING (100 100, 20 180, 180 180)")), 0) }; connection.Execute("insert #SqlGeo(id, geo, geometry) values (@Id, @Geo, @Geometry)", obj); var row = connection.Query("select * from #SqlGeo where id=1").SingleOrDefault(); Assert.NotNull(row); Assert.Equal(1, row.Id); Assert.NotNull(row.Geo); Assert.NotNull(row.Geometry); } [Fact] public void NullableSqlGeometry() { SqlMapper.ResetTypeHandlers(); connection.Execute("create table #SqlNullableGeo (id int, geometry geometry null)"); var obj = new HazSqlGeo { Id = 1, Geometry = null }; connection.Execute("insert #SqlNullableGeo(id, geometry) values (@Id, @Geometry)", obj); var row = connection.Query("select * from #SqlNullableGeo where id=1").SingleOrDefault(); Assert.NotNull(row); Assert.Equal(1, row.Id); Assert.Null(row.Geometry); } [Fact] public void SqlHierarchyId_SO18888911() { SkipIfMsDataClient(); SqlMapper.ResetTypeHandlers(); var row = connection.Query("select 3 as [Id], hierarchyid::Parse('/1/2/3/') as [Path]").Single(); Assert.Equal(3, row.Id); Assert.NotEqual(default(SqlHierarchyId), row.Path); var val = connection.Query("select @Path", row).Single(); Assert.NotEqual(default(SqlHierarchyId), val); } public class HazSqlHierarchy { public int Id { get; set; } public SqlHierarchyId Path { get; set; } } #endif [Fact] public void TestDynamicParameters() { var args = new DbDynamicParams { Provider.CreateRawParameter("foo", 123), Provider.CreateRawParameter("bar", "abc") }; var result = connection.Query("select Foo=@foo, Bar=@bar", args).Single(); int foo = result.Foo; string bar = result.Bar; Assert.Equal(123, foo); Assert.Equal("abc", bar); } [Fact] public void TestDynamicParametersReuse() { var args = new DbDynamicParams { Provider.CreateRawParameter("foo", 123), Provider.CreateRawParameter("bar", "abc") }; var result1 = connection.Query("select Foo=@foo, Bar=@bar", args).Single(); var result2 = connection.Query("select Foo=@foo, Bar=@bar", args).Single(); Assert.Equal(123, result1.Foo); Assert.Equal("abc", result1.Bar); Assert.Equal(123, result2.Foo); Assert.Equal("abc", result2.Bar); } [Fact] public void TestCustomParameter() { var args = new { foo = new DbCustomParam(Provider.CreateRawParameter("foo", 123)), bar = "abc" }; var result = connection.Query("select Foo=@foo, Bar=@bar", args).Single(); int foo = result.Foo; string bar = result.Bar; Assert.Equal(123, foo); Assert.Equal("abc", bar); } [Fact] public void TestCustomParameterReuse() { var args = new { foo = new DbCustomParam(Provider.CreateRawParameter("foo", 123)), bar = "abc" }; var result1 = connection.Query("select Foo=@foo, Bar=@bar", args).Single(); var result2 = connection.Query("select Foo=@foo, Bar=@bar", args).Single(); Assert.Equal(123, result1.Foo); Assert.Equal("abc", result1.Bar); Assert.Equal(123, result2.Foo); Assert.Equal("abc", result2.Bar); } [Fact] public void TestDynamicParamNullSupport() { var p = new DynamicParameters(); p.Add("@b", dbType: DbType.Int32, direction: ParameterDirection.Output); connection.Execute("select @b = null", p); Assert.Null(p.Get("@b")); } [Fact] public void TestAppendingAnonClasses() { var p = new DynamicParameters(); p.AddDynamicParams(new { A = 1, B = 2 }); p.AddDynamicParams(new { C = 3, D = 4 }); var result = connection.Query("select @A a,@B b,@C c,@D d", p).Single(); Assert.Equal(1, (int)result.a); Assert.Equal(2, (int)result.b); Assert.Equal(3, (int)result.c); Assert.Equal(4, (int)result.d); } [Fact] public void TestAppendingADictionary() { var dictionary = new Dictionary(2) { ["A"] = 1, ["B"] = "two" }; var p = new DynamicParameters(); p.AddDynamicParams(dictionary); var result = connection.Query("select @A a, @B b", p).Single(); Assert.Equal(1, (int)result.a); Assert.Equal("two", (string)result.b); } [Fact] public void TestAppendingAnExpandoObject() { dynamic expando = new ExpandoObject(); expando.A = 1; expando.B = "two"; var p = new DynamicParameters(); p.AddDynamicParams(expando); var result = connection.Query("select @A a, @B b", p).Single(); Assert.Equal(1, (int)result.a); Assert.Equal("two", (string)result.b); } [Fact] public void TestAppendingAList() { var p = new DynamicParameters(); var list = new int[] { 1, 2, 3 }; p.AddDynamicParams(new { list }); var result = connection.Query("select * from (select 1 A union all select 2 union all select 3) X where A in @list", p).ToList(); Assert.Equal(1, result[0]); Assert.Equal(2, result[1]); Assert.Equal(3, result[2]); } [Fact] public void TestAppendingAListAsDictionary() { var p = new DynamicParameters(); var list = new int[] { 1, 2, 3 }; var args = new Dictionary(1) { ["ids"] = list }; p.AddDynamicParams(args); var result = connection.Query("select * from (select 1 A union all select 2 union all select 3) X where A in @ids", p).ToList(); Assert.Equal(1, result[0]); Assert.Equal(2, result[1]); Assert.Equal(3, result[2]); } [Fact] public void TestAppendingAListByName() { DynamicParameters p = new DynamicParameters(); var list = new int[] { 1, 2, 3 }; p.Add("ids", list); var result = connection.Query("select * from (select 1 A union all select 2 union all select 3) X where A in @ids", p).ToList(); Assert.Equal(1, result[0]); Assert.Equal(2, result[1]); Assert.Equal(3, result[2]); } [Fact] public void ParameterizedInWithOptimizeHint() { const string sql = @" select count(1) from( select 1 as x union all select 2 union all select 5) y where y.x in @vals option (optimize for (@vals unKnoWn))"; int count = connection.Query(sql, new { vals = new[] { 1, 2, 3, 4 } }).Single(); Assert.Equal(2, count); count = connection.Query(sql, new { vals = new[] { 1 } }).Single(); Assert.Equal(1, count); count = connection.Query(sql, new { vals = new int[0] }).Single(); Assert.Equal(0, count); } [Fact] public void TestProcedureWithTimeParameter() { var p = new DynamicParameters(); p.Add("a", TimeSpan.FromHours(10), dbType: DbType.Time); connection.Execute(@"CREATE PROCEDURE #TestProcWithTimeParameter @a TIME AS BEGIN SELECT @a END"); Assert.Equal(connection.Query("#TestProcWithTimeParameter", p, commandType: CommandType.StoredProcedure).First(), new TimeSpan(10, 0, 0)); } [Fact] public void TestUniqueIdentifier() { var guid = Guid.NewGuid(); var result = connection.Query("declare @foo uniqueidentifier set @foo = @guid select @foo", new { guid }).Single(); Assert.Equal(guid, result); } [Fact] public void TestNullableUniqueIdentifierNonNull() { Guid? guid = Guid.NewGuid(); var result = connection.Query("declare @foo uniqueidentifier set @foo = @guid select @foo", new { guid }).Single(); Assert.Equal(guid, result); } [Fact] public void TestNullableUniqueIdentifierNull() { Guid? guid = null; var result = connection.Query("declare @foo uniqueidentifier set @foo = @guid select @foo", new { guid }).Single(); Assert.Equal(guid, result); } [Fact] public void TestSupportForDynamicParameters() { var p = new DynamicParameters(); p.Add("name", "bob"); p.Add("age", dbType: DbType.Int32, direction: ParameterDirection.Output); Assert.Equal("bob", connection.Query("set @age = 11 select @name", p).First()); Assert.Equal(11, p.Get("age")); } [Fact] public void TestSupportForDynamicParametersOutputExpressions() { var bob = new Person { Name = "bob", PersonId = 1, Address = new Address { PersonId = 2 } }; var p = new DynamicParameters(bob); p.Output(bob, b => b.PersonId); p.Output(bob, b => b.Occupation); p.Output(bob, b => b.NumberOfLegs); p.Output(bob, b => b.Address!.Name); p.Output(bob, b => b.Address!.PersonId); connection.Execute(@" SET @Occupation = 'grillmaster' SET @PersonId = @PersonId + 1 SET @NumberOfLegs = @NumberOfLegs - 1 SET @AddressName = 'bobs burgers' SET @AddressPersonId = @PersonId", p); Assert.Equal("grillmaster", bob.Occupation); Assert.Equal(2, bob.PersonId); Assert.Equal(1, bob.NumberOfLegs); Assert.Equal("bobs burgers", bob.Address.Name); Assert.Equal(2, bob.Address.PersonId); } [Fact] public void TestSupportForDynamicParametersOutputExpressions_Scalar() { using (var connection = GetOpenConnection()) { var bob = new Person { Name = "bob", PersonId = 1, Address = new Address { PersonId = 2 } }; var p = new DynamicParameters(bob); p.Output(bob, b => b.PersonId); p.Output(bob, b => b.Occupation); p.Output(bob, b => b.NumberOfLegs); p.Output(bob, b => b.Address!.Name); p.Output(bob, b => b.Address!.PersonId); var result = (int)connection.ExecuteScalar(@" SET @Occupation = 'grillmaster' SET @PersonId = @PersonId + 1 SET @NumberOfLegs = @NumberOfLegs - 1 SET @AddressName = 'bobs burgers' SET @AddressPersonId = @PersonId select 42", p)!; Assert.Equal("grillmaster", bob.Occupation); Assert.Equal(2, bob.PersonId); Assert.Equal(1, bob.NumberOfLegs); Assert.Equal("bobs burgers", bob.Address.Name); Assert.Equal(2, bob.Address.PersonId); Assert.Equal(42, result); } } [Fact] public void TestSupportForDynamicParametersOutputExpressions_Query_Buffered() { using (var connection = GetOpenConnection()) { var bob = new Person { Name = "bob", PersonId = 1, Address = new Address { PersonId = 2 } }; var p = new DynamicParameters(bob); p.Output(bob, b => b.PersonId); p.Output(bob, b => b.Occupation); p.Output(bob, b => b.NumberOfLegs); p.Output(bob, b => b.Address!.Name); p.Output(bob, b => b.Address!.PersonId); var result = connection.Query(@" SET @Occupation = 'grillmaster' SET @PersonId = @PersonId + 1 SET @NumberOfLegs = @NumberOfLegs - 1 SET @AddressName = 'bobs burgers' SET @AddressPersonId = @PersonId select 42", p, buffered: true).Single(); Assert.Equal("grillmaster", bob.Occupation); Assert.Equal(2, bob.PersonId); Assert.Equal(1, bob.NumberOfLegs); Assert.Equal("bobs burgers", bob.Address.Name); Assert.Equal(2, bob.Address.PersonId); Assert.Equal(42, result); } } [Fact] public void TestSupportForDynamicParametersOutputExpressions_Query_NonBuffered() { using (var connection = GetOpenConnection()) { var bob = new Person { Name = "bob", PersonId = 1, Address = new Address { PersonId = 2 } }; var p = new DynamicParameters(bob); p.Output(bob, b => b.PersonId); p.Output(bob, b => b.Occupation); p.Output(bob, b => b.NumberOfLegs); p.Output(bob, b => b.Address!.Name); p.Output(bob, b => b.Address!.PersonId); var result = connection.Query(@" SET @Occupation = 'grillmaster' SET @PersonId = @PersonId + 1 SET @NumberOfLegs = @NumberOfLegs - 1 SET @AddressName = 'bobs burgers' SET @AddressPersonId = @PersonId select 42", p, buffered: false).Single(); Assert.Equal("grillmaster", bob.Occupation); Assert.Equal(2, bob.PersonId); Assert.Equal(1, bob.NumberOfLegs); Assert.Equal("bobs burgers", bob.Address.Name); Assert.Equal(2, bob.Address.PersonId); Assert.Equal(42, result); } } [Fact] public void TestSupportForDynamicParametersOutputExpressions_QueryMultiple() { using (var connection = GetOpenConnection()) { var bob = new Person { Name = "bob", PersonId = 1, Address = new Address { PersonId = 2 } }; var p = new DynamicParameters(bob); p.Output(bob, b => b.PersonId); p.Output(bob, b => b.Occupation); p.Output(bob, b => b.NumberOfLegs); p.Output(bob, b => b.Address!.Name); p.Output(bob, b => b.Address!.PersonId); int x, y; using (var multi = connection.QueryMultiple(@" SET @Occupation = 'grillmaster' SET @PersonId = @PersonId + 1 SET @NumberOfLegs = @NumberOfLegs - 1 SET @AddressName = 'bobs burgers' select 42 select 17 SET @AddressPersonId = @PersonId", p)) { x = multi.Read().Single(); y = multi.Read().Single(); } Assert.Equal("grillmaster", bob.Occupation); Assert.Equal(2, bob.PersonId); Assert.Equal(1, bob.NumberOfLegs); Assert.Equal("bobs burgers", bob.Address.Name); Assert.Equal(2, bob.Address.PersonId); Assert.Equal(42, x); Assert.Equal(17, y); } } [Fact] public void TestSupportForExpandoObjectParameters() { dynamic p = new ExpandoObject(); p.name = "bob"; object parameters = p; string result = connection.Query("select @name", parameters).First(); Assert.Equal("bob", result); } [Fact] public void SO25069578_DynamicParams_Procs() { var parameters = new DynamicParameters(); parameters.Add("foo", "bar"); // parameters = new DynamicParameters(parameters); try { connection.Execute("drop proc SO25069578"); } catch { /* don't care */ } connection.Execute("create proc SO25069578 @foo nvarchar(max) as select @foo as [X]"); var tran = connection.BeginTransaction(); // gist used transaction; behaves the same either way, though var row = connection.Query("SO25069578", parameters, commandType: CommandType.StoredProcedure, transaction: tran).Single(); tran.Rollback(); Assert.Equal("bar", row.X); } public class HazX { public string? X { get; set; } } [Fact] public void SO25297173_DynamicIn() { const string query = @" declare @table table(value int not null); insert @table values(1); insert @table values(2); insert @table values(3); insert @table values(4); insert @table values(5); insert @table values(6); insert @table values(7); SELECT value FROM @table WHERE value IN @myIds"; var queryParams = new Dictionary(1) { ["myIds"] = new[] { 5, 6 } }; var dynamicParams = new DynamicParameters(queryParams); List result = connection.Query(query, dynamicParams).ToList(); Assert.Equal(2, result.Count); Assert.Contains(5, result); Assert.Contains(6, result); } [Fact] public void Test_AddDynamicParametersRepeatedShouldWork() { var args = new DynamicParameters(); args.AddDynamicParams(new { Foo = 123 }); args.AddDynamicParams(new { Foo = 123 }); int i = connection.Query("select @Foo", args).Single(); Assert.Equal(123, i); } [Fact] public void Test_AddDynamicParametersRepeatedIfParamTypeIsDbStiringShouldWork() { var foo = new DbString() { Value = "123" }; var args = new DynamicParameters(); args.AddDynamicParams(new { Foo = foo }); args.AddDynamicParams(new { Foo = foo }); int i = connection.Query("select @Foo", args).Single(); Assert.Equal(123, i); } [Fact] public void AllowIDictionaryParameters() { var parameters = new Dictionary(1) { ["param1"] = 0 }; connection.Query("SELECT @param1", parameters); } [Fact] public void TestParameterWithIndexer() { connection.Execute(@"create proc #TestProcWithIndexer @A int as begin select @A end"); var item = connection.Query("#TestProcWithIndexer", new ParameterWithIndexer(), commandType: CommandType.StoredProcedure).Single(); } public class ParameterWithIndexer { public int A { get; set; } public virtual string? this[string columnName] { get { return null; } set { } } } [Fact] public void TestMultipleParametersWithIndexer() { var order = connection.Query("select 1 A,2 B").First(); Assert.Equal(1, order.A); Assert.Equal(2, order.B); } public class MultipleParametersWithIndexer : MultipleParametersWithIndexerDeclaringType { public int A { get; set; } } public class MultipleParametersWithIndexerDeclaringType { public object? this[object field] { get { return null; } set { } } public object? this[object field, int index] { get { return null; } set { } } public int B { get; set; } } [Fact] public void Issue182_BindDynamicObjectParametersAndColumns() { connection.Execute("create table #Dyno ([Id] uniqueidentifier primary key, [Name] nvarchar(50) not null, [Foo] bigint not null);"); var guid = Guid.NewGuid(); var orig = new Dyno { Name = "T Rex", Id = guid, Foo = 123L }; var result = connection.Execute("insert into #Dyno ([Id], [Name], [Foo]) values (@Id, @Name, @Foo);", orig); var fromDb = connection.Query("select * from #Dyno where Id=@Id", orig).Single(); Assert.Equal((Guid)fromDb.Id, guid); Assert.Equal("T Rex", fromDb.Name); Assert.NotNull(fromDb.Foo); Assert.Equal(123L, (long)fromDb.Foo); } public class Dyno { public dynamic? Id { get; set; } public string? Name { get; set; } public object? Foo { get; set; } } [Fact] public void Issue151_ExpandoObjectArgsQuery() { dynamic args = new ExpandoObject(); args.Id = 123; args.Name = "abc"; var row = connection.Query("select @Id as [Id], @Name as [Name]", (object)args).Single(); Assert.Equal(123, (int)row.Id); Assert.Equal("abc", (string)row.Name); } [Fact] public void Issue151_ExpandoObjectArgsExec() { dynamic args = new ExpandoObject(); args.Id = 123; args.Name = "abc"; connection.Execute("create table #issue151 (Id int not null, Name nvarchar(20) not null)"); Assert.Equal(1, connection.Execute("insert #issue151 values(@Id, @Name)", (object)args)); var row = connection.Query("select Id, Name from #issue151").Single(); Assert.Equal(123, (int)row.Id); Assert.Equal("abc", (string)row.Name); } [Fact] public void Issue192_InParameterWorksWithSimilarNames() { var rows = connection.Query(@" declare @Issue192 table ( Field INT NOT NULL PRIMARY KEY IDENTITY(1,1), Field_1 INT NOT NULL); insert @Issue192(Field_1) values (1), (2), (3); SELECT * FROM @Issue192 WHERE Field IN @Field AND Field_1 IN @Field_1", new { Field = new[] { 1, 2 }, Field_1 = new[] { 2, 3 } }).Single(); Assert.Equal(2, (int)rows.Field); Assert.Equal(2, (int)rows.Field_1); } [Fact] public void Issue192_InParameterWorksWithSimilarNamesWithUnicode() { var rows = connection.Query(@" declare @Issue192 table ( Field INT NOT NULL PRIMARY KEY IDENTITY(1,1), Field_1 INT NOT NULL); insert @Issue192(Field_1) values (1), (2), (3); SELECT * FROM @Issue192 WHERE Field IN @µ AND Field_1 IN @µµ", new { µ = new[] { 1, 2 }, µµ = new[] { 2, 3 } }).Single(); Assert.Equal(2, (int)rows.Field); Assert.Equal(2, (int)rows.Field_1); } [FactUnlessCaseSensitiveDatabase] public void Issue220_InParameterCanBeSpecifiedInAnyCase() { // note this might fail if your database server is case-sensitive Assert.Equal( new[] { 1 }, connection.Query("select * from (select 1 as Id) as X where Id in @ids", new { Ids = new[] { 1 } }) ); } [Fact] public void SO30156367_DynamicParamsWithoutExec() { var dbParams = new DynamicParameters(); dbParams.Add("Field1", 1); var value = dbParams.Get("Field1"); Assert.Equal(1, value); } [Fact] public void RunAllStringSplitTestsDisabled() { RunAllStringSplitTests(-1, 1500); } [FactRequiredCompatibilityLevel(FactRequiredCompatibilityLevelAttribute.SqlServer2016)] public void RunAllStringSplitTestsEnabled() { RunAllStringSplitTests(10, 4500); } private void RunAllStringSplitTests(int stringSplit, int max = 150) { int oldVal = SqlMapper.Settings.InListStringSplitCount; try { SqlMapper.Settings.InListStringSplitCount = stringSplit; try { connection.Execute("drop table #splits"); } catch { /* don't care */ } int count = connection.QuerySingle("create table #splits (i int not null);" + string.Concat(Enumerable.Range(-max, max * 3).Select(i => $"insert #splits (i) values ({i});")) + "select count(1) from #splits"); Assert.Equal(count, 3 * max); for (int i = 0; i < max; Incr(ref i)) { try { var vals = Enumerable.Range(1, i); var list = connection.Query("select i from #splits where i in @vals", new { vals }).AsList(); Assert.Equal(list.Count, i); Assert.Equal(list.Sum(), vals.Sum()); } catch (Exception ex) { throw new InvalidOperationException($"Error when i={i}: {ex.Message}", ex); } } } finally { SqlMapper.Settings.InListStringSplitCount = oldVal; } } private static void Incr(ref int i) { if (i <= 15) i++; else if (i <= 80) i += 5; else if (i <= 200) i += 10; else if (i <= 1000) i += 50; else i += 100; } [Fact] public void Issue601_InternationalParameterNamesWork() { // regular parameter var result = connection.QuerySingle("select @æøå٦", new { æøå٦ = 42 }); Assert.Equal(42, result); } [FactLongRunning] public void TestListExpansionPadding_Enabled() => TestListExpansionPadding(true); [FactLongRunning] public void TestListExpansionPadding_Disabled() => TestListExpansionPadding(false); [Theory] [InlineData(true)] [InlineData(false)] public void OleDbParamFilterFails(bool legacyParameterToken) { SqlMapper.PurgeQueryCache(); var oldValue = SqlMapper.Settings.SupportLegacyParameterTokens; try { SqlMapper.Settings.SupportLegacyParameterTokens = legacyParameterToken; if (legacyParameterToken) // OLE DB parameter support enabled; can false-positive { Assert.Throws(() => GetValue(connection)); } else // OLE DB parameter support disabled; more reliable { Assert.Equal("this ? could be awkward", GetValue(connection)); } } finally { SqlMapper.Settings.SupportLegacyParameterTokens = oldValue; } static string GetValue(DbConnection connection) => connection.QuerySingle("select 'this ? could be awkward'", new TypeWithDodgyProperties()); } private void TestListExpansionPadding(bool enabled) { bool oldVal = SqlMapper.Settings.PadListExpansions; try { SqlMapper.Settings.PadListExpansions = enabled; Assert.Equal(4096, connection.ExecuteScalar(@" create table #ListExpansion(id int not null identity(1,1), value int null); insert #ListExpansion (value) values (null); declare @loop int = 0; while (@loop < 12) begin -- double it insert #ListExpansion (value) select value from #ListExpansion; set @loop = @loop + 1; end select count(1) as [Count] from #ListExpansion")); var list = new List(); int nextId = 1, batchCount; var rand = new Random(12345); const int SQL_SERVER_MAX_PARAMS = 2095; TestListForExpansion(list, enabled); // test while empty while (list.Count < SQL_SERVER_MAX_PARAMS) { try { if (list.Count <= 20) batchCount = 1; else if (list.Count <= 200) batchCount = rand.Next(1, 40); else batchCount = rand.Next(1, 100); for (int j = 0; j < batchCount && list.Count < SQL_SERVER_MAX_PARAMS; j++) list.Add(nextId++); TestListForExpansion(list, enabled); } catch (Exception ex) { throw new InvalidOperationException($"Failure with {list.Count} items: {ex.Message}", ex); } } } finally { SqlMapper.Settings.PadListExpansions = oldVal; } } private void TestListForExpansion(List list, bool enabled) { var row = connection.QuerySingle(@" declare @hits int, @misses int, @count int; select @count = count(1) from #ListExpansion; select @hits = count(1) from #ListExpansion where id in @ids ; select @misses = count(1) from #ListExpansion where not id in @ids ; declare @query nvarchar(max) = N' in @ids '; -- ok, I confess to being pleased with this hack ;p select @hits as [Hits], (@count - @misses) as [Misses], @query as [Query]; ", new { ids = list }); int hits = row.Hits, misses = row.Misses; string query = row.Query; int argCount = Regex.Matches(query, "@ids[0-9]").Count; int expectedCount = GetExpectedListExpansionCount(list.Count, enabled); Assert.Equal(hits, list.Count); Assert.Equal(misses, list.Count); Assert.Equal(argCount, expectedCount); } private static int GetExpectedListExpansionCount(int count, bool enabled) { if (!enabled) return count; if (count <= 5 || count > 2070) return count; int padFactor; if (count <= 150) padFactor = 10; else if (count <= 750) padFactor = 50; else if (count <= 2000) padFactor = 100; else if (count <= 2070) padFactor = 10; else padFactor = 200; int blocks = count / padFactor, delta = count % padFactor; if (delta != 0) blocks++; return blocks * padFactor; } [Fact] public void Issue1907_SqlDecimalPreciseValues() { bool close = false; try { if (connection.State != ConnectionState.Open) { connection.Open(); close = true; } connection.Execute(@" create table #Issue1907 ( Id int not null primary key identity(1,1), Value numeric(30,15) not null);"); const string PreciseValue = "999999999999999.999999999999999"; SqlDecimal sentValue = SqlDecimal.Parse(PreciseValue), recvValue; connection.Execute("insert #Issue1907 (Value) values (@value)", new { value = sentValue }); // access via vendor-specific API; if this fails, nothing else can work using (var wrappedReader = connection.ExecuteReader("select Id, Value from #Issue1907")) { var reader = Assert.IsAssignableFrom(wrappedReader).Reader; Assert.True(reader.Read()); if (reader is Microsoft.Data.SqlClient.SqlDataReader msReader) { recvValue = msReader.GetSqlDecimal(1); } #pragma warning disable CS0618 // Type or member is obsolete else if (reader is System.Data.SqlClient.SqlDataReader sdReader) { recvValue = sdReader.GetSqlDecimal(1); } #pragma warning restore CS0618 // Type or member is obsolete else { throw new InvalidOperationException($"unexpected reader type: {reader.GetType().FullName}"); } Assert.Equal(sentValue, recvValue); Assert.Equal(PreciseValue, recvValue.ToString()); Assert.False(reader.Read()); Assert.False(reader.NextResult()); } // access via generic API using (var wrappedReader = connection.ExecuteReader("select Id, Value from #Issue1907")) { var reader = Assert.IsAssignableFrom(Assert.IsAssignableFrom(wrappedReader).Reader); Assert.True(reader.Read()); recvValue = reader.GetFieldValue(1); Assert.Equal(sentValue, recvValue); Assert.Equal(PreciseValue, recvValue.ToString()); Assert.False(reader.Read()); Assert.False(reader.NextResult()); } // prove that we **cannot** fix ExecuteScalar, because ADO.NET itself doesn't work for that Assert.Throws(() => { using var cmd = connection.CreateCommand(); cmd.CommandText = "select Value from #Issue1907"; cmd.CommandType = CommandType.Text; _ = cmd.ExecuteScalar(); }); // prove that simple read: works recvValue = connection.QuerySingle("select Value from #Issue1907"); Assert.Equal(sentValue, recvValue); Assert.Equal(PreciseValue, recvValue.ToString()); recvValue = connection.QuerySingle("select Value from #Issue1907")!.Value; Assert.Equal(sentValue, recvValue); Assert.Equal(PreciseValue, recvValue.ToString()); // prove that object read: works recvValue = connection.QuerySingle("select Id, Value from #Issue1907").Value; Assert.Equal(sentValue, recvValue); Assert.Equal(PreciseValue, recvValue.ToString()); recvValue = connection.QuerySingle("select Id, Value from #Issue1907").Value!.Value; Assert.Equal(sentValue, recvValue); Assert.Equal(PreciseValue, recvValue.ToString()); // prove that value-tuple read: works recvValue = connection.QuerySingle<(int Id, SqlDecimal Value)>("select Id, Value from #Issue1907").Value; Assert.Equal(sentValue, recvValue); Assert.Equal(PreciseValue, recvValue.ToString()); recvValue = connection.QuerySingle<(int Id, SqlDecimal? Value)>("select Id, Value from #Issue1907").Value!.Value; Assert.Equal(sentValue, recvValue); Assert.Equal(PreciseValue, recvValue.ToString()); } finally { if (close) connection.Close(); } } class HazSqlDecimal { public int Id { get; set; } public SqlDecimal Value { get; set; } } class HazNullableSqlDecimal { public int Id { get; set; } public SqlDecimal? Value { get; set; } } class TypeWithDodgyProperties { public string Name => throw new NotSupportedException(); } } } ================================================ FILE: tests/Dapper.Tests/ProcedureTests.cs ================================================ using System; using System.Data; using System.Linq; using System.Threading.Tasks; using Xunit; namespace Dapper.Tests { [Collection("ProcedureTests")] public sealed class SystemSqlClientProcedureTests : ProcedureTests { } #if MSSQLCLIENT [Collection("ProcedureTests")] public sealed class MicrosoftSqlClientProcedureTests : ProcedureTests { } #endif public abstract class ProcedureTests : TestBase where TProvider : DatabaseProvider { [Fact] public void TestProcWithOutParameter() { connection.Execute( @"CREATE PROCEDURE #TestProcWithOutParameter @ID int output, @Foo varchar(100), @Bar int AS SET @ID = @Bar + LEN(@Foo)"); var obj = new { ID = 0, Foo = "abc", Bar = 4 }; var args = new DynamicParameters(obj); args.Add("ID", 0, direction: ParameterDirection.Output); connection.Execute("#TestProcWithOutParameter", args, commandType: CommandType.StoredProcedure); Assert.Equal(7, args.Get("ID")); } [Fact] public void TestProcWithOutAndReturnParameter() { connection.Execute( @"CREATE PROCEDURE #TestProcWithOutAndReturnParameter @ID int output, @Foo varchar(100), @Bar int AS SET @ID = @Bar + LEN(@Foo) RETURN 42"); var obj = new { ID = 0, Foo = "abc", Bar = 4 }; var args = new DynamicParameters(obj); args.Add("ID", 0, direction: ParameterDirection.Output); args.Add("result", 0, direction: ParameterDirection.ReturnValue); connection.Execute("#TestProcWithOutAndReturnParameter", args, commandType: CommandType.StoredProcedure); Assert.Equal(7, args.Get("ID")); Assert.Equal(42, args.Get("result")); } [Fact] public void TestIssue17648290() { var p = new DynamicParameters(); const int code = 1, getMessageControlId = 2; p.Add("@Code", code); p.Add("@MessageControlID", getMessageControlId); p.Add("@SuccessCode", dbType: DbType.Int32, direction: ParameterDirection.Output); p.Add("@ErrorDescription", dbType: DbType.String, direction: ParameterDirection.Output, size: 255); connection.Execute( @"CREATE PROCEDURE #up_MessageProcessed_get @Code varchar(10), @MessageControlID varchar(22), @SuccessCode int OUTPUT, @ErrorDescription varchar(255) OUTPUT AS BEGIN Select 2 as MessageProcessID, 38349348 as StartNum, 3874900 as EndNum, GETDATE() as StartDate, GETDATE() as EndDate SET @SuccessCode = 0 SET @ErrorDescription = 'Completed successfully' END"); var result = connection.Query(sql: "#up_MessageProcessed_get", param: p, commandType: CommandType.StoredProcedure); var row = result.Single(); Assert.Equal(2, (int)row.MessageProcessID); Assert.Equal(38349348, (int)row.StartNum); Assert.Equal(3874900, (int)row.EndNum); DateTime startDate = row.StartDate, endDate = row.EndDate; Assert.Equal(0, p.Get("SuccessCode")); Assert.Equal("Completed successfully", p.Get("ErrorDescription")); } [Theory] [InlineData(CommandType.StoredProcedure)] [InlineData(null)] // auto public void InferProcedure(CommandType? commandType) { connection.Execute("CREATE PROCEDURE #InferProcedure @id int AS BEGIN SELECT -@id END"); var result = connection.QuerySingle("#InferProcedure", new { id = 42 }, commandType: commandType); Assert.Equal(-42, result); } [Theory] [InlineData(CommandType.Text)] [InlineData(null)] // auto public void InferNotProcedure(CommandType? commandType) { connection.Execute("CREATE PROCEDURE #InferNotProcedure @id int AS BEGIN SELECT -@id END"); var result = connection.QuerySingle("EXEC #InferNotProcedure @id", new { id = 42 }, commandType: commandType); Assert.Equal(-42, result); } [Fact] public void SO24605346_ProcsAndStrings() { connection.Execute( @"create proc #GetPracticeRebateOrderByInvoiceNumber @TaxInvoiceNumber nvarchar(20) as select @TaxInvoiceNumber as [fTaxInvoiceNumber]"); const string InvoiceNumber = "INV0000000028PPN"; var result = connection.Query("#GetPracticeRebateOrderByInvoiceNumber", new { TaxInvoiceNumber = InvoiceNumber }, commandType: CommandType.StoredProcedure).FirstOrDefault(); Assert.NotNull(result); Assert.Equal("INV0000000028PPN", result.TaxInvoiceNumber); } private class PracticeRebateOrders { public string? fTaxInvoiceNumber; [System.Xml.Serialization.XmlElement(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] public string? TaxInvoiceNumber { get { return fTaxInvoiceNumber; } set { fTaxInvoiceNumber = value; } } } [Fact] public void Issue327_ReadEmptyProcedureResults() { // Actually testing for not erroring here on the mapping having no rows to map on in Read(); connection.Execute(@" CREATE PROCEDURE #TestEmptyResults AS SELECT Top 0 1 Id, 'Bob' Name; SELECT Top 0 'Billy Goat' Creature, 'Unicorn' SpiritAnimal, 'Rainbow' Location;"); var query = connection.QueryMultiple("#TestEmptyResults", commandType: CommandType.StoredProcedure); var result1 = query.Read(); var result2 = query.Read(); Assert.False(result1.Any()); Assert.False(result2.Any()); } private class Issue327_Person { public int Id { get; set; } public string? Name { get; set; } } private class Issue327_Magic { public string? Creature { get; set; } public string? SpiritAnimal { get; set; } public string? Location { get; set; } } [Fact] public void TestProcSupport() { var p = new DynamicParameters(); p.Add("a", 11); p.Add("b", dbType: DbType.Int32, direction: ParameterDirection.Output); p.Add("c", dbType: DbType.Int32, direction: ParameterDirection.ReturnValue); connection.Execute(@" create proc #TestProc @a int, @b int output as begin set @b = 999 select 1111 return @a end"); Assert.Equal(1111, connection.Query("#TestProc", p, commandType: CommandType.StoredProcedure).First()); Assert.Equal(11, p.Get("c")); Assert.Equal(999, p.Get("b")); } // https://stackoverflow.com/q/8593871 [Fact] public void TestListOfAnsiStrings() { var results = connection.Query("select * from (select 'a' str union select 'b' union select 'c') X where str in @strings", new { strings = new[] { new DbString { IsAnsi = true, Value = "a" }, new DbString { IsAnsi = true, Value = "b" } } }).ToList(); Assert.Equal(2, results.Count); results.Sort(); Assert.Equal("a", results[0]); Assert.Equal("b", results[1]); } [Fact] public void TestDateTime2PrecisionPreservedInDynamicParameters() { const string tempSPName = "#" + nameof(TestDateTime2PrecisionPreservedInDynamicParameters); DateTime datetimeDefault = new(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc); DateTime datetime2 = datetimeDefault.AddTicks(1); // Add 100 ns Assert.True(datetimeDefault < datetime2); connection.Execute( $@"create proc {tempSPName} @a datetime2, @b datetime2 output as begin set @b = @a select DATEADD(ns, -100, @b) end"); var p = new DynamicParameters(); // Note: parameters declared as DateTime2 p.Add("a", datetime2, dbType: DbType.DateTime2, direction: ParameterDirection.Input); p.Add("b", dbType: DbType.DateTime2, direction: ParameterDirection.Output); DateTime fromSelect = connection.Query(tempSPName, p, commandType: CommandType.StoredProcedure).First(); Assert.Equal(datetimeDefault, fromSelect); Assert.Equal(datetime2, p.Get("b")); } [Theory] [InlineData(null)] [InlineData(DbType.DateTime)] public void TestDateTime2LosePrecisionInDynamicParameters(DbType? dbType) { const string tempSPName = "#" + nameof(TestDateTime2LosePrecisionInDynamicParameters); DateTime datetimeDefault = new(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc); DateTime datetime2 = datetimeDefault.AddTicks(1); // Add 100 ns Assert.True(datetimeDefault < datetime2); connection.Execute( $@"create proc {tempSPName} @a datetime2, @b datetime2 output as begin set @b = DATEADD(ns, 100, @a) select @b end"); var p = new DynamicParameters(); // Note: input parameter declared as DateTime (or implicitly as this) but SP has DateTime2 p.Add("a", datetime2, dbType: dbType, direction: ParameterDirection.Input); p.Add("b", dbType: DbType.DateTime, direction: ParameterDirection.Output); DateTime fromSelect = connection.Query(tempSPName, p, commandType: CommandType.StoredProcedure).First(); // @a truncates to datetimeDefault when passed into SP by DynamicParameters, add 100ns and it comes out as DateTime2 Assert.Equal(datetime2, fromSelect); // @b gets set to datetime2 value but is truncated back to DbType.DateTime by DynamicParameter's Output declaration Assert.Equal(datetimeDefault, p.Get("b")); } [Fact] public async Task Issue591_NoResultsAsync() { const string tempSPName = "#" + nameof(Issue591_NoResultsAsync); var result = await connection.QueryAsync( $@"create proc {tempSPName} as begin -- basically a failed if statement, so the select is not happening and the stored proc return nothing if 1=0 begin select 1 as Num end end exec {tempSPName}"); Assert.Empty(result); } [Theory] [InlineData(" ")] [InlineData("\u00A0")] // nbsp [InlineData("\u202F")] // narrow nbsp [InlineData("\u2000")] // n quad [InlineData("\t")] [InlineData("\r")] [InlineData("\n")] public async Task Issue1986_AutoProc_Whitespace(string space) { var sql = "select!42".Replace("!", space); var result = await connection.QuerySingleAsync(sql); Assert.Equal(42, result); } [Theory] [InlineData("foo", CommandType.StoredProcedure)] [InlineData("foo;", CommandType.Text)] [InlineData("foo bar", CommandType.Text)] [InlineData("foo bar;", CommandType.Text)] [InlineData("vacuum", CommandType.Text)] [InlineData("vacuum;", CommandType.Text)] [InlineData("FOO", CommandType.StoredProcedure)] [InlineData("FOO;", CommandType.Text)] [InlineData("FOO BAR", CommandType.Text)] [InlineData("FOO BAR;", CommandType.Text)] [InlineData("VACUUM", CommandType.Text)] [InlineData("VACUUM;", CommandType.Text)] [InlineData("cOmmiT", CommandType.Text)] [InlineData("rOllbAck", CommandType.Text)] [InlineData("reVErt", CommandType.Text)] // comments imply text [InlineData("foo--bar", CommandType.Text)] [InlineData("foo/*bar*/", CommandType.Text)] public void InferCommandType(string sql, CommandType commandType) { Assert.Equal(commandType, CommandDefinition.InferCommandType(sql)); } } } ================================================ FILE: tests/Dapper.Tests/ProviderTests.cs ================================================ using System; using System.Data.Common; using Dapper.ProviderTools; using Xunit; namespace Dapper.Tests { public class ProviderTests { [Fact] public void BulkCopy_SystemDataSqlClient() { #pragma warning disable CS0618 // Type or member is obsolete using var conn = new System.Data.SqlClient.SqlConnection(); Test(conn); #pragma warning restore CS0618 // Type or member is obsolete } [Fact] public void BulkCopy_MicrosoftDataSqlClient() { using var conn = new Microsoft.Data.SqlClient.SqlConnection(); Test(conn); } [Fact] public void ClientId_SystemDataSqlClient() => TestClientId(); [Fact] public void ClearPool_SystemDataSqlClient() => ClearPool(); [Fact] public void ClearAllPools_SystemDataSqlClient() => ClearAllPools(); #if MSSQLCLIENT [Fact] public void ClientId_MicrosoftDataSqlClient() => TestClientId(); [Fact] public void ClearPool_MicrosoftDataSqlClient() => ClearPool(); [Fact] public void ClearAllPools_MicrosoftDataSqlClient() => ClearAllPools(); #endif private static void TestClientId() where T : SqlServerDatabaseProvider, new() { var provider = new T(); using var conn = provider.GetOpenConnection(); Assert.True(conn.TryGetClientConnectionId(out var id)); Assert.NotEqual(Guid.Empty, id); } private static void ClearPool() where T : SqlServerDatabaseProvider, new() { var provider = new T(); using var conn = provider.GetOpenConnection(); Assert.True(conn.TryClearPool()); } private static void ClearAllPools() where T : SqlServerDatabaseProvider, new() { var provider = new T(); using var conn = provider.GetOpenConnection(); Assert.True(conn.TryClearAllPools()); } private static void Test(DbConnection connection) { using var bcp = BulkCopy.TryCreate(connection); Assert.NotNull(bcp); Assert.IsType(bcp.Wrapped); bcp.EnableStreaming = true; } [Theory] [InlineData(51000, 51000, true)] [InlineData(51000, 43, false)] public void DbNumber_SystemData(int create, int test, bool result) => Test(create, test, result); #if MSSQLCLIENT [Theory] [InlineData(51000, 51000, true)] [InlineData(51000, 43, false)] public void DbNumber_MicrosoftData(int create, int test, bool result) => Test(create, test, result); #endif private static void Test(int create, int test, bool result) where T : SqlServerDatabaseProvider, new() { var provider = new T(); using var conn = provider.GetOpenConnection(); try { conn.Execute("throw @create, 'boom', 1;", new { create }); Assert.False(true); } catch(DbException err) { Assert.Equal(result, err.IsNumber(test)); } } } } ================================================ FILE: tests/Dapper.Tests/Providers/DuckDBTests.cs ================================================ using System; using System.Data.Common; using DuckDB.NET.Data; using Xunit; namespace Dapper.Tests { public class DuckDBProvider : DatabaseProvider { public override DbProviderFactory Factory => DuckDBClientFactory.Instance; public override string GetConnectionString() => "Data Source=:memory:"; } public abstract class DuckDBTypeTestBase : TestBase { protected DuckDBConnection GetDuckDBConnection(bool open = true) => (DuckDBConnection)(open ? Provider.GetOpenConnection() : Provider.GetClosedConnection()); [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class FactDuckDBAttribute : FactAttribute { public override string? Skip { get { return unavailable ?? base.Skip; } set { base.Skip = value; } } private static readonly string? unavailable; static FactDuckDBAttribute() { try { using var _ = DatabaseProvider.Instance.GetOpenConnection(); } catch (Exception ex) { unavailable = $"DuckDB is unavailable: {ex.Message}"; } } } } public class DuckDBTests : DuckDBTypeTestBase { [FactDuckDB] public void DuckDBNamedParameter() { using var connection = GetDuckDBConnection(); var result = connection.QueryFirst("Select $foo", new {foo = 42}); Assert.Equal(42, result); } [FactDuckDB] public void DuckDBPositionalParameter() { using var connection = GetDuckDBConnection(); var dp = new DynamicParameters(); dp.Add("?", 42); var result = connection.QueryFirst("Select ?", dp); Assert.Equal(42, result); } } } ================================================ FILE: tests/Dapper.Tests/Providers/EntityFrameworkTests.cs ================================================ #if ENTITY_FRAMEWORK using System; using System.Data.Entity.Spatial; using System.Linq; using Xunit; namespace Dapper.Tests.Providers { public sealed class SystemSqlClientEntityFrameworkTests : EntityFrameworkTests { } #if MSSQLCLIENT public sealed class MicrosoftSqlClientEntityFrameworkTests : EntityFrameworkTests { } #endif [Collection("TypeHandlerTests")] public abstract class EntityFrameworkTests : TestBase where TProvider : DatabaseProvider { public EntityFrameworkTests() { EntityFramework.Handlers.Register(); } [Fact] public void Issue570_DbGeo_HasValues() { SkipIfMsDataClient(); EntityFramework.Handlers.Register(); const string redmond = "POINT (-122.1215 47.6740)"; DbGeography point = DbGeography.PointFromText(redmond, DbGeography.DefaultCoordinateSystemId); DbGeography orig = point.Buffer(20); var fromDb = connection.QuerySingle("declare @geos table(geo geography); insert @geos(geo) values(@val); select * from @geos", new { val = orig }); Assert.NotNull(fromDb.Area); Assert.Equal(orig.Area, fromDb.Area); } [Fact] public void Issue22_ExecuteScalar_EntityFramework() { SkipIfMsDataClient(); var geo = DbGeography.LineFromText("LINESTRING(-122.360 47.656, -122.343 47.656 )", 4326); var geo2 = connection.ExecuteScalar("select @geo", new { geo }); Assert.NotNull(geo2); } [Fact] public void TestGeometryParsingRetainsSrid() { const int srid = 27700; var s = $@"DECLARE @EdinburghPoint GEOMETRY = geometry::STPointFromText('POINT(258647 665289)', {srid}); SELECT @EdinburghPoint"; var edinPoint = connection.Query(s).Single(); Assert.NotNull(edinPoint); Assert.Equal(srid, edinPoint.CoordinateSystemId); } [Fact] public void TestGeographyParsingRetainsSrid() { const int srid = 4324; var s = $@"DECLARE @EdinburghPoint GEOGRAPHY = geography::STPointFromText('POINT(-3.19 55.95)', {srid}); SELECT @EdinburghPoint"; var edinPoint = connection.Query(s).Single(); Assert.NotNull(edinPoint); Assert.Equal(srid, edinPoint.CoordinateSystemId); } } } #endif ================================================ FILE: tests/Dapper.Tests/Providers/FirebirdTests.cs ================================================ using FirebirdSql.Data.FirebirdClient; using System.Data; using System.Data.Common; using System.Linq; using Xunit; namespace Dapper.Tests.Providers { /// /// If Docker Desktop is installed, run the following command to start a container suitable for the tests. /// /// docker run -d -p 3050:3050 --name Dapper.Tests.Firebird -e FIREBIRD_DATABASE=database -e ISC_PASSWORD=masterkey jacobalberty/firebird /// /// public class FirebirdProvider : DatabaseProvider { public override DbProviderFactory Factory => FirebirdClientFactory.Instance; public override string GetConnectionString() => "initial catalog=localhost:database;user id=SYSDBA;password=masterkey"; } public class FirebirdTests : TestBase { private FbConnection GetOpenFirebirdConnection() => (FbConnection)Provider.GetOpenConnection(); [Fact(Skip = "Bug in Firebird; a PR to fix it has been submitted")] public void Issue178_Firebird() { using var connection = GetOpenFirebirdConnection(); const string sql = "select count(*) from Issue178"; try { connection.Execute("drop table Issue178"); } catch { /* don't care */ } connection.Execute("create table Issue178(id int not null)"); connection.Execute("insert into Issue178(id) values(42)"); // raw ADO.net using (var sqlCmd = new FbCommand(sql, connection)) using (var reader1 = sqlCmd.ExecuteReader()) { Assert.True(reader1.Read()); Assert.Equal(1, reader1.GetInt32(0)); Assert.False(reader1.Read()); Assert.False(reader1.NextResult()); } // dapper using (var reader2 = connection.ExecuteReader(sql)) { Assert.True(reader2.Read()); Assert.Equal(1, reader2.GetInt32(0)); Assert.False(reader2.Read()); Assert.False(reader2.NextResult()); } var count = connection.Query(sql).Single(); Assert.Equal(1, count); } } } ================================================ FILE: tests/Dapper.Tests/Providers/Linq2SqlTests.cs ================================================ #if LINQ2SQL using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Xunit; namespace Dapper.Tests { public sealed class SystemSqlClientLinq2SqlTests : Linq2SqlTests { } #if MSSQLCLIENT public sealed class MicrosoftSqlClientLinq2SqlTests : Linq2SqlTests { } #endif public abstract class Linq2SqlTests : TestBase where TProvider : DatabaseProvider { [Fact] public void TestLinqBinaryToClass() { byte[] orig = new byte[20]; new Random(123456).NextBytes(orig); var input = new System.Data.Linq.Binary(orig); var output = connection.Query("select @input as [Value]", new { input }).First().Value; Assert.Equal(orig, output?.ToArray()); } [Fact] public void TestLinqBinaryRaw() { byte[] orig = new byte[20]; new Random(123456).NextBytes(orig); var input = new System.Data.Linq.Binary(orig); var output = connection.Query("select @input as [Value]", new { input }).First(); Assert.Equal(orig, output.ToArray()); } private class WithBinary { public System.Data.Linq.Binary? Value { get; set; } } private class NoDefaultConstructorWithBinary { public System.Data.Linq.Binary Value { get; set; } public int Ynt { get; set; } public NoDefaultConstructorWithBinary(System.Data.Linq.Binary val) { Value = val; } } [Fact] public void TestNoDefaultConstructorBinary() { byte[] orig = new byte[20]; new Random(123456).NextBytes(orig); var input = new System.Data.Linq.Binary(orig); var output = connection.Query("select @input as val", new { input }).First().Value; Assert.Equal(orig, output.ToArray()); } } } #endif ================================================ FILE: tests/Dapper.Tests/Providers/MySQLTests.cs ================================================ using System; using System.Data; using System.Data.Common; using System.Linq; using System.Threading.Tasks; using Xunit; namespace Dapper.Tests { /// /// If Docker Desktop is installed, run the following command to start a container suitable for the tests. /// /// docker run -d -p 3306:3306 --name Dapper.Tests.MySQL -e MYSQL_DATABASE=tests -e MYSQL_USER=test -e MYSQL_PASSWORD=pass -e MYSQL_ROOT_PASSWORD=pass mysql /// /// public sealed class MySqlProvider : DatabaseProvider { public override DbProviderFactory Factory => MySqlConnector.MySqlConnectorFactory.Instance; public override string GetConnectionString() => GetConnectionString("MySqlConnectionString", "Server=localhost;Database=tests;Uid=test;Pwd=pass;"); public DbConnection GetMySqlConnection(bool open = true, bool convertZeroDatetime = false, bool allowZeroDatetime = false) { string cs = GetConnectionString(); var csb = Factory.CreateConnectionStringBuilder()!; csb.ConnectionString = cs; ((dynamic)csb).AllowZeroDateTime = allowZeroDatetime; ((dynamic)csb).ConvertZeroDateTime = convertZeroDatetime; var conn = Factory.CreateConnection()!; conn.ConnectionString = csb.ConnectionString; if (open) conn.Open(); return conn; } } public class MySQLTests : TestBase { [FactMySql] public void DapperEnumValue_Mysql() { using var conn = Provider.GetMySqlConnection(); Common.DapperEnumValue(conn); } [FactMySql] public void Issue552_SignedUnsignedBooleans() { using (var conn = Provider.GetMySqlConnection(true, false, false)) { conn.Execute(@" CREATE TEMPORARY TABLE IF NOT EXISTS `bar` ( `id` INT NOT NULL, `bool_val` BOOL NULL, PRIMARY KEY (`id`)); truncate table bar; insert bar (id, bool_val) values (1, null); insert bar (id, bool_val) values (2, 0); insert bar (id, bool_val) values (3, 1); insert bar (id, bool_val) values (4, null); insert bar (id, bool_val) values (5, 1); insert bar (id, bool_val) values (6, 0); insert bar (id, bool_val) values (7, null); insert bar (id, bool_val) values (8, 1);"); var rows = conn.Query("select * from bar;").ToDictionary(x => x.Id); Assert.Null(rows[1].Bool_Val); Assert.False(rows[2].Bool_Val); Assert.True(rows[3].Bool_Val); Assert.Null(rows[4].Bool_Val); Assert.True(rows[5].Bool_Val); Assert.False(rows[6].Bool_Val); Assert.Null(rows[7].Bool_Val); Assert.True(rows[8].Bool_Val); } } private class MySqlHasBool { public int Id { get; set; } public bool? Bool_Val { get; set; } } [FactMySql] public void Issue295_NullableDateTime_MySql_Default() { using var conn = Provider.GetMySqlConnection(true, false, false); Common.TestDateTime(conn); } [FactMySql] public void Issue295_NullableDateTime_MySql_ConvertZeroDatetime() { using var conn = Provider.GetMySqlConnection(true, true, false); Common.TestDateTime(conn); } [FactMySql(Skip = "See https://github.com/DapperLib/Dapper/issues/295, AllowZeroDateTime=True is not supported")] public void Issue295_NullableDateTime_MySql_AllowZeroDatetime() { using (var conn = Provider.GetMySqlConnection(true, false, true)) { Common.TestDateTime(conn); } } [FactMySql(Skip = "See https://github.com/DapperLib/Dapper/issues/295, AllowZeroDateTime=True is not supported")] public void Issue295_NullableDateTime_MySql_ConvertAllowZeroDatetime() { using var conn = Provider.GetMySqlConnection(true, true, true); Common.TestDateTime(conn); } [FactMySql] public void Issue426_SO34439033_DateTimeGainsTicks() { using var conn = Provider.GetMySqlConnection(true, true, true); try { conn.Execute("drop table Issue426_Test"); } catch { /* don't care */ } try { conn.Execute("create table Issue426_Test (Id int not null, Time time not null)"); } catch { /* don't care */ } const long ticks = 553440000000; const int Id = 426; var localObj = new Issue426_Test { Id = Id, Time = TimeSpan.FromTicks(ticks) // from code example }; conn.Execute("replace into Issue426_Test values (@Id,@Time)", localObj); var dbObj = conn.Query("select * from Issue426_Test where Id = @id", new { id = Id }).Single(); Assert.Equal(Id, dbObj.Id); Assert.Equal(ticks, dbObj.Time?.Ticks); } [FactMySql] public void SO36303462_Tinyint_Bools() { using var conn = Provider.GetMySqlConnection(true, true, true); try { conn.Execute("drop table SO36303462_Test"); } catch { /* don't care */ } conn.Execute("create table SO36303462_Test (Id int not null, IsBold tinyint not null);"); conn.Execute("insert SO36303462_Test (Id, IsBold) values (1,1);"); conn.Execute("insert SO36303462_Test (Id, IsBold) values (2,0);"); conn.Execute("insert SO36303462_Test (Id, IsBold) values (3,1);"); var rows = conn.Query("select * from SO36303462_Test").ToDictionary(x => x.Id); Assert.Equal(3, rows.Count); Assert.True(rows[1].IsBold); Assert.False(rows[2].IsBold); Assert.True(rows[3].IsBold); } [FactMySql] public void Issue1277_ReaderSync() { using var conn = Provider.GetMySqlConnection(); try { conn.Execute("drop table Issue1277_Test"); } catch { /* don't care */ } conn.Execute("create table Issue1277_Test (Id int not null, IsBold tinyint not null);"); conn.Execute("insert Issue1277_Test (Id, IsBold) values (1,1);"); conn.Execute("insert Issue1277_Test (Id, IsBold) values (2,0);"); conn.Execute("insert Issue1277_Test (Id, IsBold) values (3,1);"); using (var reader = conn.ExecuteReader( "select * from Issue1277_Test where Id < @id", new { id = 42 })) { var table = new DataTable(); table.Load(reader); Assert.Equal(2, table.Columns.Count); Assert.Equal(3, table.Rows.Count); } } [FactMySql] public async Task Issue1277_ReaderAsync() { using var conn = Provider.GetMySqlConnection(); try { await conn.ExecuteAsync("drop table Issue1277_Test"); } catch { /* don't care */ } await conn.ExecuteAsync("create table Issue1277_Test (Id int not null, IsBold tinyint not null);"); await conn.ExecuteAsync("insert Issue1277_Test (Id, IsBold) values (1,1);"); await conn.ExecuteAsync("insert Issue1277_Test (Id, IsBold) values (2,0);"); await conn.ExecuteAsync("insert Issue1277_Test (Id, IsBold) values (3,1);"); using (var reader = await conn.ExecuteReaderAsync( "select * from Issue1277_Test where Id < @id", new { id = 42 })) { var table = new DataTable(); table.Load(reader); Assert.Equal(2, table.Columns.Count); Assert.Equal(3, table.Rows.Count); } } private class SO36303462 { public int Id { get; set; } public bool IsBold { get; set; } } public class Issue426_Test { public long Id { get; set; } public TimeSpan? Time { get; set; } } [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class FactMySqlAttribute : FactAttribute { public override string? Skip { get { return unavailable ?? base.Skip; } set { base.Skip = value; } } private static readonly string? unavailable; static FactMySqlAttribute() { try { using (DatabaseProvider.Instance.GetMySqlConnection(true)) { /* just trying to see if it works */ } } catch (Exception ex) { unavailable = $"MySql is unavailable: {ex.Message}"; } } } } } ================================================ FILE: tests/Dapper.Tests/Providers/OLDEBTests.cs ================================================ #if OLEDB using System; using System.Data.Common; using System.Data.OleDb; using System.Linq; using Xunit; namespace Dapper.Tests { public class OLEDBProvider : DatabaseProvider { public override DbProviderFactory Factory => OleDbFactory.Instance; public override string GetConnectionString() => GetConnectionString("OLEDBConnectionString", "Provider=SQLOLEDB;Data Source=.;Initial Catalog=tempdb;Integrated Security=SSPI"); } public class OLDEBTests : TestBase { public OleDbConnection GetOleDbConnection() => (OleDbConnection) Provider.GetOpenConnection(); // see https://stackoverflow.com/q/18847510/23354 [Fact] public void TestOleDbParameters() { using var conn = GetOleDbConnection(); var row = conn.Query("select Id = ?, Age = ?", new { foo = 12, bar = 23 } // these names DO NOT MATTER!!! ).Single(); int age = row.Age; int id = row.Id; Assert.Equal(23, age); Assert.Equal(12, id); } [Fact] public void PseudoPositionalParameters_Simple() { using var connection = GetOleDbConnection(); int value = connection.Query("select ?x? + ?y_2? + ?z?", new { x = 1, y_2 = 3, z = 5, z2 = 24 }).Single(); Assert.Equal(9, value); } [Fact] public void Issue601_InternationalParameterNamesWork_OleDb() { // pseudo-positional using var connection = GetOleDbConnection(); int value = connection.QuerySingle("select ?æøå٦?", new { æøå٦ = 42 }); } [Fact] public void PseudoPositionalParameters_Dynamic() { using var connection = GetOleDbConnection(); var args = new DynamicParameters(); args.Add("x", 1); args.Add("y_2", 3); args.Add("z", 5); args.Add("z2", 24); int value = connection.Query("select ?x? + ?y_2? + ?z?", args).Single(); Assert.Equal(9, value); } [Fact] public void PseudoPositionalParameters_ReusedParameter() { using var connection = GetOleDbConnection(); var ex = Assert.Throws(() => connection.Query("select ?x? + ?y_2? + ?x?", new { x = 1, y_2 = 3 }).Single()); Assert.Equal("When passing parameters by position, each parameter can only be referenced once", ex.Message); } [Fact] public void Issue569_SO38527197_PseudoPositionalParameters_In_And_Other_Condition() { const string sql = @"select s1.value as id, s2.value as score from string_split('1,2,3,4,5',',') s1, string_split('1,2,3,4,5',',') s2 where s1.value in ?ids? and s2.value = ?score?"; using var connection = GetOleDbConnection(); const int score = 2; int[] ids = { 1, 2, 5, 7 }; var list = connection.Query(sql, new { ids, score }).AsList(); list.Sort(); Assert.Equal("1,2,5", string.Join(",", list)); } [Fact] public void Issue569_SO38527197_PseudoPositionalParameters_In() { using var connection = GetOleDbConnection(); int[] ids = { 1, 2, 5, 7 }; var list = connection.Query("select * from string_split('1,2,3,4,5',',') where value in ?ids?", new { ids }).AsList(); list.Sort(); Assert.Equal("1,2,5", string.Join(",", list)); } [Fact] public void PseudoPositional_CanUseVariable() { using var connection = GetOleDbConnection(); const int id = 42; var row = connection.QuerySingle("declare @id int = ?id?; select @id as [A], @id as [B];", new { id }); int a = (int)row.A; int b = (int)row.B; Assert.Equal(42, a); Assert.Equal(42, b); } [Fact] public void PseudoPositional_CannotUseParameterMultipleTimes() { using var connection = GetOleDbConnection(); var ex = Assert.Throws(() => { const int id = 42; connection.QuerySingle("select ?id? as [A], ?id? as [B];", new { id }); }); Assert.Equal("When passing parameters by position, each parameter can only be referenced once", ex.Message); } [Fact] public void PseudoPositionalParameters_ExecSingle() { using var connection = GetOleDbConnection(); var data = new { x = 6 }; connection.Execute("create table #named_single(val int not null)"); int count = connection.Execute("insert #named_single (val) values (?x?)", data); int? sum = (int?)connection.ExecuteScalar("select sum(val) from #named_single"); Assert.NotNull(sum); Assert.Equal(1, count); Assert.Equal(6, sum); } [Fact] public void PseudoPositionalParameters_ExecMulti() { using var connection = GetOleDbConnection(); var data = new[] { new { x = 1, y = 1 }, new { x = 3, y = 1 }, new { x = 6, y = 1 }, }; connection.Execute("create table #named_multi(val int not null)"); int count = connection.Execute("insert #named_multi (val) values (?x?)", data); int? sum = (int?)connection.ExecuteScalar("select sum(val) from #named_multi"); Assert.Equal(3, count); Assert.NotNull(sum); Assert.Equal(10, sum); } [Fact] public void Issue457_NullParameterValues() { const string sql = @" DECLARE @since DATETIME, @customerCode nvarchar(10) SET @since = ? -- ODBC parameter SET @customerCode = ? -- ODBC parameter SELECT @since as [Since], @customerCode as [Code]"; using var connection = GetOleDbConnection(); DateTime? since = null; // DateTime.Now.Date; const string? code = null; // "abc"; var row = connection.QuerySingle(sql, new { since, customerCode = code }); var a = (DateTime?)row.Since; var b = (string)row.Code; Assert.Equal(since, a); Assert.Equal(code, b); } [Fact] public void Issue457_NullParameterValues_Named() { const string sql = @" DECLARE @since DATETIME, @customerCode nvarchar(10) SET @since = ?since? -- ODBC parameter SET @customerCode = ?customerCode? -- ODBC parameter SELECT @since as [Since], @customerCode as [Code]"; using var connection = GetOleDbConnection(); DateTime? since = null; // DateTime.Now.Date; const string? code = null; // "abc"; var row = connection.QuerySingle(sql, new { since, customerCode = code }); var a = (DateTime?)row.Since; var b = (string)row.Code; Assert.Equal(since, a); Assert.Equal(code, b); } [Fact] public async void Issue457_NullParameterValues_MultiAsync() { const string sql = @" DECLARE @since DATETIME, @customerCode nvarchar(10) SET @since = ? -- ODBC parameter SET @customerCode = ? -- ODBC parameter SELECT @since as [Since], @customerCode as [Code]"; using var connection = GetOleDbConnection(); DateTime? since = null; // DateTime.Now.Date; const string? code = null; // "abc"; using (var multi = await connection.QueryMultipleAsync(sql, new { since, customerCode = code }).ConfigureAwait(false)) { var row = await multi.ReadSingleAsync().ConfigureAwait(false); var a = (DateTime?)row.Since; var b = (string?)row.Code; Assert.Equal(since, a); Assert.Equal(code, b); } } [Fact] public async void Issue457_NullParameterValues_MultiAsync_Named() { const string sql = @" DECLARE @since DATETIME, @customerCode nvarchar(10) SET @since = ?since? -- ODBC parameter SET @customerCode = ?customerCode? -- ODBC parameter SELECT @since as [Since], @customerCode as [Code]"; using var connection = GetOleDbConnection(); DateTime? since = null; // DateTime.Now.Date; const string? code = null; // "abc"; using (var multi = await connection.QueryMultipleAsync(sql, new { since, customerCode = code }).ConfigureAwait(false)) { var row = await multi.ReadSingleAsync().ConfigureAwait(false); var a = (DateTime?)row.Since; var b = (string)row.Code; Assert.Equal(since, a); Assert.Equal(code, b); } } } } #endif ================================================ FILE: tests/Dapper.Tests/Providers/PostgresqlTests.cs ================================================ using System; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Linq; using Xunit; namespace Dapper.Tests { /// /// If Docker Desktop is installed, run the following command to start a container suitable for the tests. /// /// docker run -d -p 5432:5432 --name Dapper.Tests.PostgreSQL -e POSTGRES_DB=dappertest -e POSTGRES_USER=dappertest -e POSTGRES_PASSWORD=dapperpass postgres /// /// public class PostgresProvider : DatabaseProvider { public override DbProviderFactory Factory => Npgsql.NpgsqlFactory.Instance; public override string GetConnectionString() => GetConnectionString("PostgesConnectionString", "Server=localhost;Port=5432;User Id=dappertest;Password=dapperpass;Database=dappertest"); } public class PostgresqlTests : TestBase { private Npgsql.NpgsqlConnection GetOpenNpgsqlConnection() => (Npgsql.NpgsqlConnection)Provider.GetOpenConnection(); private class Cat { public int Id { get; set; } public string? Breed { get; set; } public string? Name { get; set; } } private readonly Cat[] Cats = { new Cat() { Breed = "Abyssinian", Name="KACTUS"}, new Cat() { Breed = "Aegean cat", Name="KADAFFI"}, new Cat() { Breed = "American Bobtail", Name="KANJI"}, new Cat() { Breed = "Balinese", Name="MACARONI"}, new Cat() { Breed = "Bombay", Name="MACAULAY"}, new Cat() { Breed = "Burmese", Name="MACBETH"}, new Cat() { Breed = "Chartreux", Name="MACGYVER"}, new Cat() { Breed = "German Rex", Name="MACKENZIE"}, new Cat() { Breed = "Javanese", Name="MADISON"}, new Cat() { Breed = "Persian", Name="MAGNA"} }; [FactPostgresql] public void TestPostgresqlArrayParameters() { using var conn = GetOpenNpgsqlConnection(); var transaction = conn.BeginTransaction(); conn.Execute("create table tcat ( id serial not null, breed character varying(20) not null, name character varying (20) not null);"); conn.Execute("insert into tcat(breed, name) values(:Breed, :Name) ", Cats); var r = conn.Query("select * from tcat where id=any(:catids)", new { catids = new[] { 1, 3, 5 } }); Assert.Equal(3, r.Count()); Assert.Equal(1, r.Count(c => c.Id == 1)); Assert.Equal(1, r.Count(c => c.Id == 3)); Assert.Equal(1, r.Count(c => c.Id == 5)); transaction.Rollback(); } [FactPostgresql] public void TestPostgresqlListParameters() { using var conn = GetOpenNpgsqlConnection(); var transaction = conn.BeginTransaction(); conn.Execute("create table tcat ( id serial not null, breed character varying(20) not null, name character varying (20) not null);"); conn.Execute("insert into tcat(breed, name) values(:Breed, :Name) ", new List(Cats)); var r = conn.Query("select * from tcat where id=any(:catids)", new { catids = new List { 1, 3, 5 } }); Assert.Equal(3, r.Count()); Assert.Equal(1, r.Count(c => c.Id == 1)); Assert.Equal(1, r.Count(c => c.Id == 3)); Assert.Equal(1, r.Count(c => c.Id == 5)); transaction.Rollback(); } private class CharTable { public int Id { get; set; } public char CharColumn { get; set; } } [FactPostgresql] public void TestPostgresqlChar() { using var conn = GetOpenNpgsqlConnection(); var transaction = conn.BeginTransaction(); conn.Execute("create table chartable (id serial not null, charcolumn \"char\" not null);"); conn.Execute("insert into chartable(charcolumn) values('a');"); var r = conn.Query("select * from chartable"); Assert.Single(r); Assert.Equal('a', r.Single().CharColumn); transaction.Rollback(); } [FactPostgresql] public void TestPostgresqlSelectArray() { using var conn = GetOpenNpgsqlConnection(); var r = conn.Query("select array[1,2,3]").ToList(); Assert.Single(r); Assert.Equal(new[] { 1, 2, 3 }, r.Single()); } [FactPostgresql] public void TestPostgresqlDateTimeUsage() { using var conn = GetOpenNpgsqlConnection(); DateTime now = DateTime.UtcNow; DateTime? nilA = now, nilB = null; _ = conn.ExecuteScalar("SELECT @now, @nilA, @nilB::timestamp", new { now, nilA, nilB }); } [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class FactPostgresqlAttribute : FactAttribute { public override string? Skip { get { return unavailable ?? base.Skip; } set { base.Skip = value; } } private static readonly string? unavailable; static FactPostgresqlAttribute() { try { using (DatabaseProvider.Instance.GetOpenConnection()) { /* just trying to see if it works */ } } catch (Exception ex) { unavailable = $"Postgresql is unavailable: {ex.Message}"; } } } } } ================================================ FILE: tests/Dapper.Tests/Providers/SnowflakeTests.cs ================================================ #if !NETFRAMEWORK // platform not supported exception using System; using System.Collections.Generic; using System.IO; using Snowflake.Data.Client; using Xunit; using Xunit.Abstractions; namespace Dapper.Tests { public class SnowflakeTests { static readonly string? s_ConnectionString; static SnowflakeTests() { SqlMapper.Settings.UseIncrementalPseudoPositionalParameterNames = true; try { // this *probably* won't exist (TODO: can we get a test account?) s_ConnectionString = File.ReadAllText(@"c:\Code\SnowflakeConnectionString.txt").Trim(); } catch { } } public SnowflakeTests(ITestOutputHelper output) => Output = output; private ITestOutputHelper Output { get; } private static SnowflakeDbConnection GetConnection() { if (string.IsNullOrWhiteSpace(s_ConnectionString)) Skip.Inconclusive("no snowflake connection-string"); return new SnowflakeDbConnection { ConnectionString = s_ConnectionString }; } [Fact] public void Connect() { using var connection = GetConnection(); connection.Open(); } [Fact] public void BasicQuery() { using var connection = GetConnection(); var nations = connection.Query(@"SELECT * FROM NATION").AsList(); Assert.NotEmpty(nations); Output.WriteLine($"nations: {nations.Count}"); foreach (var nation in nations) { Output.WriteLine($"{nation.N_NATIONKEY}: {nation.N_NAME} (region: {nation.N_REGIONKEY}), {nation.N_COMMENT}"); } } [Fact] public void ParameterizedQuery() { using var connection = GetConnection(); const int region = 1; var nations = connection.Query(@"SELECT * FROM NATION WHERE N_REGIONKEY=?region?", new { region }).AsList(); Assert.NotEmpty(nations); Output.WriteLine($"nations: {nations.Count}"); foreach (var nation in nations) { Output.WriteLine($"{nation.N_NATIONKEY}: {nation.N_NAME} (region: {nation.N_REGIONKEY}), {nation.N_COMMENT}"); } } public class Nation { public int N_NATIONKEY { get; set; } public string? N_NAME{ get; set; } public int N_REGIONKEY { get; set; } public string? N_COMMENT { get; set; } } } } #endif ================================================ FILE: tests/Dapper.Tests/Providers/SqliteTests.cs ================================================ using Microsoft.Data.Sqlite; using System; using System.Data.Common; using System.Linq; using System.Threading; using System.Threading.Tasks; using Xunit; namespace Dapper.Tests { public class SqliteProvider : DatabaseProvider { public override DbProviderFactory Factory => SqliteFactory.Instance; public override string GetConnectionString() => "Data Source=:memory:"; } public abstract class SqliteTypeTestBase : TestBase { protected SqliteConnection GetSQLiteConnection(bool open = true) => (SqliteConnection)(open ? Provider.GetOpenConnection() : Provider.GetClosedConnection()); [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class FactSqliteAttribute : FactAttribute { public override string? Skip { get { return unavailable ?? base.Skip; } set { base.Skip = value; } } private static readonly string? unavailable; static FactSqliteAttribute() { try { using var _ = DatabaseProvider.Instance.GetOpenConnection(); } catch (Exception ex) { unavailable = $"Sqlite is unavailable: {ex.Message}"; } } } } [Collection(NonParallelDefinition.Name)] public class SqliteTypeHandlerTests : SqliteTypeTestBase { [FactSqlite] public void Issue466_SqliteHatesOptimizations() { using var connection = GetSQLiteConnection(); SqlMapper.ResetTypeHandlers(); var row = connection.Query("select 42 as Id").First(); Assert.Equal(42, row.Id); row = connection.Query("select 42 as Id").First(); Assert.Equal(42, row.Id); SqlMapper.ResetTypeHandlers(); row = connection.QueryFirst("select 42 as Id"); Assert.Equal(42, row.Id); row = connection.QueryFirst("select 42 as Id"); Assert.Equal(42, row.Id); } [FactSqlite] public async Task Issue466_SqliteHatesOptimizations_Async() { using var connection = GetSQLiteConnection(); SqlMapper.ResetTypeHandlers(); var row = (await connection.QueryAsync("select 42 as Id").ConfigureAwait(false)).First(); Assert.Equal(42, row.Id); row = (await connection.QueryAsync("select 42 as Id").ConfigureAwait(false)).First(); Assert.Equal(42, row.Id); SqlMapper.ResetTypeHandlers(); row = await connection.QueryFirstAsync("select 42 as Id").ConfigureAwait(false); Assert.Equal(42, row.Id); row = await connection.QueryFirstAsync("select 42 as Id").ConfigureAwait(false); Assert.Equal(42, row.Id); } } public class SqliteTests : SqliteTypeTestBase { [FactSqlite] public void DapperEnumValue_Sqlite() { using var connection = GetSQLiteConnection(); Common.DapperEnumValue(connection); } [FactSqlite] public void Isse467_SqliteLikesParametersWithPrefix() { Isse467_SqliteParameterNaming(true); } [FactSqlite] public void Isse467_SqliteLikesParametersWithoutPrefix() { // see issue 375 / 467; note: fixed from RC2 onwards Isse467_SqliteParameterNaming(false); } private void Isse467_SqliteParameterNaming(bool prefix) { using var connection = GetSQLiteConnection(); var cmd = connection.CreateCommand(); cmd.CommandText = "select @foo"; const SqliteType type = SqliteType.Integer; cmd.Parameters.Add(prefix ? "@foo" : "foo", type).Value = 42; var i = Convert.ToInt32(cmd.ExecuteScalar()); Assert.Equal(42, i); } [FactSqlite] public void DateTimeIsParsedWithInvariantCulture() { connection.Execute("CREATE TABLE [PersonWithDob] ([Id] integer primary key autoincrement, [DoB] DATETIME not null )"); var localMorning = DateTime.Parse("2019-07-31 01:00:00"); var culture = Thread.CurrentThread.CurrentCulture; try { connection.Execute("INSERT INTO [PersonWithDob] ([DoB]) VALUES (@DoB)", new PersonWithDob { DoB = localMorning }); // Before we read the column, use Farsi this is a way to ensure the // InvariantCulture is used as otherwise it would fail because Farsi // is not able to parse a DateTime that is formatted with Invariant var farsi = System.Globalization.CultureInfo.GetCultureInfo("fa-IR"); Thread.CurrentThread.CurrentCulture = farsi; Thread.CurrentThread.CurrentUICulture = farsi; var person = connection.QueryFirst("SELECT * FROM [PersonWithDob]"); Assert.Equal(localMorning, person.DoB); } finally { Thread.CurrentThread.CurrentCulture = culture; Thread.CurrentThread.CurrentUICulture = culture; connection.Execute("DROP TABLE [PersonWithDob]"); } } private class PersonWithDob { public int Id { get; set; } public DateTime DoB { get; set; } } } } ================================================ FILE: tests/Dapper.Tests/QueryMultipleTests.cs ================================================ using System; using System.Collections.Generic; using System.Data; using System.Linq; using Xunit; namespace Dapper.Tests { [Collection("QueryMultipleTests")] public sealed class SystemSqlClientQueryMultipleTests : QueryMultipleTests { } #if MSSQLCLIENT [Collection("QueryMultipleTests")] public sealed class MicrosoftSqlClientQueryMultipleTests : QueryMultipleTests { } #endif public abstract class QueryMultipleTests : TestBase where TProvider : DatabaseProvider { [Fact] public void TestQueryMultipleBuffered() { using var grid = connection.QueryMultiple("select 1; select 2; select @x; select 4", new { x = 3 }); var a = grid.Read(); var b = grid.Read(); var c = grid.Read(); var d = grid.Read(); Assert.Equal(1, a.Single()); Assert.Equal(2, b.Single()); Assert.Equal(3, c.Single()); Assert.Equal(4, d.Single()); } [Fact] public void TestMultiConversion() { using SqlMapper.GridReader multi = connection.QueryMultiple("select Cast(1 as BigInt) Col1; select Cast(2 as BigInt) Col2"); Assert.Equal(1, multi.Read().Single()); Assert.Equal(2, multi.Read().Single()); } [Fact] public void TestQueryMultipleNonBufferedIncorrectOrder() { using var grid = connection.QueryMultiple("select 1; select 2; select @x; select 4", new { x = 3 }); var a = grid.Read(false); try { var b = grid.Read(false); throw new InvalidOperationException(); // should have thrown } catch (InvalidOperationException) { // that's expected } } [Fact] public void TestQueryMultipleNonBufferedCorrectOrder() { using var grid = connection.QueryMultiple("select 1; select 2; select @x; select 4", new { x = 3 }); var a = grid.Read(false).Single(); var b = grid.Read(false).Single(); var c = grid.Read(false).Single(); var d = grid.Read(false).Single(); Assert.Equal(1, a); Assert.Equal(2, b); Assert.Equal(3, c); Assert.Equal(4, d); } [Fact] public void TestMultiReaderBasic() { const string sql = "select 1 as Id union all select 2 as Id select 'abc' as name select 1 as Id union all select 2 as Id"; int i, j; string s; using (var multi = connection.QueryMultiple(sql)) { i = multi.Read().First(); s = multi.Read().Single(); j = multi.Read().Sum(); } Assert.Equal(1, i); Assert.Equal("abc", s); Assert.Equal(3, j); } [Fact] public void TestReadDynamicWithGridReader() { const string createSql = @" create table #Users (Id int, Name varchar(20)) create table #Posts (Id int, OwnerId int, Content varchar(20)) insert #Users values(99, 'Sam') insert #Users values(2, 'I am') insert #Posts values(1, 99, 'Sams Post1') insert #Posts values(2, 99, 'Sams Post2') insert #Posts values(3, null, 'no ones post')"; try { connection.Execute(createSql); const string sql = @"SELECT * FROM #Users ORDER BY Id SELECT * FROM #Posts ORDER BY Id DESC"; var grid = connection.QueryMultiple(sql); var users = grid.Read().ToList(); var posts = grid.Read().ToList(); Assert.Equal(2, users.Count); Assert.Equal(3, posts.Count); Assert.Equal(2, (int)users[0].Id); Assert.Equal(3, (int)posts[0].Id); } finally { connection.Execute("drop table #Users drop table #Posts"); } } [Fact] public void Issue268_ReturnQueryMultiple() { connection.Execute(@"create proc #TestProc268 (@a int, @b int, @c int)as begin select @a; select @b return @c; end"); var p = new DynamicParameters(new { a = 1, b = 2, c = 3 }); p.Add("RetVal", dbType: DbType.Int32, direction: ParameterDirection.ReturnValue); using (var reader = connection.QueryMultiple("#TestProc268", p, commandType: CommandType.StoredProcedure)) { reader.Read(); } var retVal = p.Get("RetVal"); Assert.Equal(3, retVal); } [Fact] public void Issue524_QueryMultiple_Cast() { // aka: Read should work even if the data is a // using regular API Assert.Equal(42, connection.Query("select cast(42 as bigint)").Single()); Assert.Equal(42, connection.QuerySingle("select cast(42 as bigint)")); // using multi-reader API using var reader = connection.QueryMultiple("select cast(42 as bigint); select cast(42 as bigint)"); Assert.Equal(42, reader.Read().Single()); Assert.Equal(42, reader.ReadSingle()); } [Fact] public void QueryMultipleFromClosed() { using var conn = GetClosedConnection(); using (var multi = conn.QueryMultiple("select 1; select 'abc';")) { Assert.Equal(1, multi.Read().Single()); Assert.Equal("abc", multi.Read().Single()); } Assert.Equal(ConnectionState.Closed, conn.State); } [Fact] public void QueryMultiple2FromClosed() { using var conn = GetClosedConnection(); Assert.Equal(ConnectionState.Closed, conn.State); using (var multi = conn.QueryMultiple("select 1 select 2 select 3")) { Assert.Equal(1, multi.Read().Single()); Assert.Equal(2, multi.Read().Single()); // not reading 3 is intentional here } Assert.Equal(ConnectionState.Closed, conn.State); } [Fact] public void SO35554284_QueryMultipleUntilConsumed() { using var reader = connection.QueryMultiple("select 1 as Id; select 2 as Id; select 3 as Id;"); var items = new List(); while (!reader.IsConsumed) { items.AddRange(reader.Read()); } Assert.Equal(3, items.Count); Assert.Equal(1, items[0].Id); Assert.Equal(2, items[1].Id); Assert.Equal(3, items[2].Id); } [Fact] public void QueryMultipleInvalidFromClosed() { using var conn = GetClosedConnection(); Assert.ThrowsAny(() => conn.QueryMultiple("select gibberish")); Assert.Equal(ConnectionState.Closed, conn.State); } [Fact] public void TestMultiSelectWithSomeEmptyGridsUnbuffered() => TestMultiSelectWithSomeEmptyGrids(false); [Fact] public void TestMultiSelectWithSomeEmptyGridsBuffered() => TestMultiSelectWithSomeEmptyGrids(true); private void TestMultiSelectWithSomeEmptyGrids(bool buffered) { using var reader = connection.QueryMultiple("select 1; select 2 where 1 = 0; select 3 where 1 = 0; select 4;"); var one = reader.Read(buffered: buffered).ToArray(); var two = reader.Read(buffered: buffered).ToArray(); var three = reader.Read(buffered: buffered).ToArray(); var four = reader.Read(buffered: buffered).ToArray(); try { // only returned four grids; expect a fifth read to fail reader.Read(buffered: buffered); throw new InvalidOperationException("this should not have worked!"); } catch (ObjectDisposedException ex) { // expected; success Assert.Equal("The reader has been disposed; this can happen after all data has been consumed\r\nObject name: 'Dapper.SqlMapper+GridReader'.", ex.Message, ignoreLineEndingDifferences: true); } Assert.Single(one); Assert.Equal(1, one[0]); Assert.Empty(two); Assert.Empty(three); Assert.Single(four); Assert.Equal(4, four[0]); } [Fact] public void TypeBasedViaTypeMulti() { Type type = Common.GetSomeType(); dynamic first, second; using (var multi = connection.QueryMultiple("select @A as [A], @B as [B]; select @C as [A], @D as [B]", new { A = 123, B = "abc", C = 456, D = "def" })) { first = multi.Read(type).Single(); second = multi.Read(type).Single(); } Assert.Equal(((object)first).GetType(), type); int a = first.A; string b = first.B; Assert.Equal(123, a); Assert.Equal("abc", b); Assert.Equal(((object)second).GetType(), type); a = second.A; b = second.B; Assert.Equal(456, a); Assert.Equal("def", b); } } } ================================================ FILE: tests/Dapper.Tests/SharedTypes/Address.cs ================================================ namespace Dapper.Tests { public class Address { public int AddressId { get; set; } public string? Name { get; set; } public int PersonId { get; set; } public Index? Index { get; set; } } } ================================================ FILE: tests/Dapper.Tests/SharedTypes/Bar1.cs ================================================ namespace Dapper.Tests { public class Bar1 { public int BarId; public string? Name { get; set; } } } ================================================ FILE: tests/Dapper.Tests/SharedTypes/Category.cs ================================================ namespace Dapper.Tests { public class Category { public int Id { get; set; } public string? Name { get; set; } public string? Description { get; set; } } } ================================================ FILE: tests/Dapper.Tests/SharedTypes/Comment.cs ================================================ namespace Dapper.Tests { public class Comment { public int Id { get; set; } public string? CommentData { get; set; } } } ================================================ FILE: tests/Dapper.Tests/SharedTypes/Dog.cs ================================================ using System; namespace Dapper.Tests { public class Dog { public int? Age { get; set; } public Guid Id { get; set; } public string? Name { get; set; } public float? Weight { get; set; } public int IgnoredProperty => 1; } } ================================================ FILE: tests/Dapper.Tests/SharedTypes/Enums.cs ================================================ namespace Dapper.Tests { internal enum AnEnum { A = 2, B = 1 } internal enum AnotherEnum : byte { A = 2, B = 1 } } ================================================ FILE: tests/Dapper.Tests/SharedTypes/Foo1.cs ================================================ namespace Dapper.Tests { public class Foo1 { public int Id; public int BarId { get; set; } } } ================================================ FILE: tests/Dapper.Tests/SharedTypes/HazNameId.cs ================================================ namespace Dapper.Tests { public class HazNameId { public string? Name { get; set; } public int Id { get; set; } } } ================================================ FILE: tests/Dapper.Tests/SharedTypes/Index.cs ================================================ namespace Dapper.Tests { public class Index { public string? Id { get; set; } } } ================================================ FILE: tests/Dapper.Tests/SharedTypes/Person.cs ================================================ namespace Dapper.Tests { public class Person { public int PersonId { get; set; } public string? Name { get; set; } public string? Occupation { get; private set; } public int NumberOfLegs = 2; public Address? Address { get; set; } } } ================================================ FILE: tests/Dapper.Tests/SharedTypes/Post.cs ================================================ namespace Dapper.Tests { public class Post { public int Id { get; set; } public User? Owner { get; set; } public string? Content { get; set; } public Comment? Comment { get; set; } } } ================================================ FILE: tests/Dapper.Tests/SharedTypes/Product.cs ================================================ namespace Dapper.Tests { public class Product { public int Id { get; set; } public string? Name { get; set; } public Category? Category { get; set; } } } ================================================ FILE: tests/Dapper.Tests/SharedTypes/ReviewBoard.cs ================================================ namespace Dapper.Tests { public class ReviewBoard { public int Id { get; set; } public string? Name { get; set; } public User? User1 { get; set; } public User? User2 { get; set; } public User? User3 { get; set; } public User? User4 { get; set; } public User? User5 { get; set; } public User? User6 { get; set; } public User? User7 { get; set; } public User? User8 { get; set; } public User? User9 { get; set; } } } ================================================ FILE: tests/Dapper.Tests/SharedTypes/ShortEnum.cs ================================================ namespace Dapper.Tests { public enum ShortEnum : short { Zero = 0, One = 1, Two = 2, Three = 3, Four = 4, Five = 5, Six = 6 } } ================================================ FILE: tests/Dapper.Tests/SharedTypes/SomeType.cs ================================================ namespace Dapper.Tests { public class SomeType { public int A { get; set; } public string? B { get; set; } } } ================================================ FILE: tests/Dapper.Tests/SharedTypes/User.cs ================================================ namespace Dapper.Tests { public class User { public int Id { get; set; } public string? Name { get; set; } } } ================================================ FILE: tests/Dapper.Tests/SingleRowTests.cs ================================================ using System; using System.Collections.Generic; using System.Data.Common; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using FastMember; using Xunit; using Xunit.Abstractions; using static Dapper.SqlMapper; namespace Dapper.Tests; [Collection("SingleRowTests")] public sealed class SystemSqlClientSingleRowTests(ITestOutputHelper log) : SingleRowTests(log) { protected override async Task InjectDataAsync(DbConnection conn, DbDataReader source) { #pragma warning disable CS0618 // Type or member is obsolete using var bcp = new System.Data.SqlClient.SqlBulkCopy((System.Data.SqlClient.SqlConnection)conn); #pragma warning restore CS0618 // Type or member is obsolete bcp.DestinationTableName = "#mydata"; bcp.EnableStreaming = true; await bcp.WriteToServerAsync(source); } } #if MSSQLCLIENT [Collection("SingleRowTests")] public sealed class MicrosoftSqlClientSingleRowTests(ITestOutputHelper log) : SingleRowTests(log) { protected override async Task InjectDataAsync(DbConnection conn, DbDataReader source) { using var bcp = new Microsoft.Data.SqlClient.SqlBulkCopy((Microsoft.Data.SqlClient.SqlConnection)conn); bcp.DestinationTableName = "#mydata"; bcp.EnableStreaming = true; await bcp.WriteToServerAsync(source); } } #endif public abstract class SingleRowTests(ITestOutputHelper log) : TestBase where TProvider : DatabaseProvider { protected abstract Task InjectDataAsync(DbConnection connection, DbDataReader source); [Fact] public async Task QueryFirst_PerformanceAndCorrectness() { using var conn = GetOpenConnection(); conn.Execute("create table #mydata(id int not null, name nvarchar(250) not null)"); var rand = new Random(); var data = from id in Enumerable.Range(1, 500_000) select new MyRow { Id = rand.Next(), Name = CreateName(rand) }; Stopwatch watch; using (var reader = ObjectReader.Create(data)) { await InjectDataAsync(conn, reader); watch = Stopwatch.StartNew(); var count = await conn.QuerySingleAsync("""select count(1) from #mydata"""); watch.Stop(); log.WriteLine($"bulk-insert complete; {count} rows in {watch.ElapsedMilliseconds}ms"); } // just errors var ex = Assert.ThrowsAny(() => conn.Execute("raiserror('bad things', 16, 1)")); log.WriteLine(ex.Message); ex = await Assert.ThrowsAnyAsync(async () => await conn.ExecuteAsync("raiserror('bad things', 16, 1)")); log.WriteLine(ex.Message); // just data watch = Stopwatch.StartNew(); var row = conn.QueryFirst("select top 1 * from #mydata"); watch.Stop(); log.WriteLine($"sync top 1 read first complete; row {row.Id} in {watch.ElapsedMilliseconds}ms"); watch = Stopwatch.StartNew(); row = await conn.QueryFirstAsync("select top 1 * from #mydata"); watch.Stop(); log.WriteLine($"async top 1 read first complete; row {row.Id} in {watch.ElapsedMilliseconds}ms"); watch = Stopwatch.StartNew(); row = conn.QueryFirst("select * from #mydata"); watch.Stop(); log.WriteLine($"sync read first complete; row {row.Id} in {watch.ElapsedMilliseconds}ms"); watch = Stopwatch.StartNew(); row = await conn.QueryFirstAsync("select * from #mydata"); watch.Stop(); log.WriteLine($"async read first complete; row {row.Id} in {watch.ElapsedMilliseconds}ms"); // data with trailing errors watch = Stopwatch.StartNew(); ex = Assert.ThrowsAny(() => conn.QueryFirst("select * from #mydata; raiserror('bad things', 16, 1)")); watch.Stop(); log.WriteLine($"sync read with error complete in {watch.ElapsedMilliseconds}ms; {ex.Message}"); watch = Stopwatch.StartNew(); ex = await Assert.ThrowsAnyAsync(async () => await conn.QueryFirstAsync("select * from #mydata; raiserror('bad things', 16, 1)")); watch.Stop(); log.WriteLine($"async read with error complete in {watch.ElapsedMilliseconds}ms; {ex.Message}"); // unbuffered read with trailing errors - do not expect to see this unless we consume all! watch = Stopwatch.StartNew(); row = conn.Query("select * from #mydata", buffered: false).First(); watch.Stop(); log.WriteLine($"sync unbuffered LINQ read first complete; row {row.Id} in {watch.ElapsedMilliseconds}ms"); watch = Stopwatch.StartNew(); row = await conn.QueryUnbufferedAsync("select * from #mydata").FirstAsync(); watch.Stop(); log.WriteLine($"async unbuffered LINQ read first complete; row {row.Id} in {watch.ElapsedMilliseconds}ms"); static unsafe string CreateName(Random rand) { const string Alphabet = "abcdefghijklmnopqrstuvwxyz 0123456789,;-"; var len = rand.Next(5, 251); char* ptr = stackalloc char[len]; for (int i = 0; i < len; i++) { ptr[i] = Alphabet[rand.Next(Alphabet.Length)]; } return new string(ptr, 0, len); } } public class MyRow { public int Id { get; set; } public string Name { get; set; } = ""; } } internal static class AsyncLinqHelper { public static async ValueTask FirstAsync(this IAsyncEnumerable source, CancellationToken cancellationToken = default) { await using var iter = source.GetAsyncEnumerator(cancellationToken); if (!await iter.MoveNextAsync()) Array.Empty().First(); // for consistent error return iter.Current; } } ================================================ FILE: tests/Dapper.Tests/SqlBuilderTests.cs ================================================ using System; using System.Linq; using Xunit; namespace Dapper.Tests { [Collection("SqlBuilderTests")] public sealed class SystemSqlClientSqlBuilderTests : SqlBuilderTests { } #if MSSQLCLIENT [Collection("SqlBuilderTests")] public sealed class MicrosoftSqlClientSqlBuilderTests : SqlBuilderTests { } #endif public abstract class SqlBuilderTests : TestBase where TProvider : DatabaseProvider { [Fact] public void TestSqlBuilderWithDapperQuery() { var sb = new SqlBuilder(); var template = sb.AddTemplate("SELECT /**select**/ FROM #Users /**where**/"); sb.Where("Age <= @Age", new { Age = 18 }) .Where("Country = @Country", new { Country = "USA" }) .Select("Name,Age,Country"); const string createSql = @" create table #Users (Name varchar(20),Age int,Country nvarchar(5)); insert #Users values('Sam',16,'USA'),('Tom',25,'UK'),('Henry',14,'UK')"; try { connection.Execute(createSql); var result = connection.Query(template.RawSql,template.Parameters).ToArray(); Assert.Equal("SELECT Name,Age,Country\n FROM #Users WHERE Age <= @Age AND Country = @Country\n", template.RawSql); Assert.Single(result); Assert.Equal(16, (int)result[0].Age); Assert.Equal("Sam", (string)result[0].Name); Assert.Equal("USA", (string)result[0].Country); } finally { connection.Execute("drop table #Users"); } } [Fact] public void TestSqlBuilderUpdateSet() { var id = 1; var vip = true; var updatetime = DateTime.Parse("2020/01/01"); var sb = new SqlBuilder() .Set("Vip = @vip", new { vip }) .Set("Updatetime = @updatetime", new { updatetime }) .Where("Id = @id", new { id }) ; var template = sb.AddTemplate("update #Users /**set**/ /**where**/"); const string createSql = @" create table #Users (Id int,Name varchar(20),Age int,Country nvarchar(5),Vip bit,Updatetime datetime); insert #Users (Id,Name,Age,Country) values(1,'Sam',16,'USA'),(2,'Tom',25,'UK'),(3,'Henry',14,'UK')"; try { connection.Execute(createSql); var effectCount = connection.Execute(template.RawSql, template.Parameters); var result = connection.QueryFirst("select * from #Users where Id = 1"); Assert.Equal("update #Users SET Vip = @vip , Updatetime = @updatetime\n WHERE Id = @id\n", template.RawSql); Assert.True((bool)result.Vip); Assert.Equal(updatetime, (DateTime)result.Updatetime); } finally { connection.Execute("drop table #Users"); } } } } ================================================ FILE: tests/Dapper.Tests/TestBase.cs ================================================ using System; using System.Data; using System.Data.Common; using System.Globalization; using System.Threading; using Xunit; namespace Dapper.Tests { public static class DatabaseProvider where TProvider : DatabaseProvider { public static TProvider Instance { get; } = Activator.CreateInstance(); } public abstract class DatabaseProvider { public abstract DbProviderFactory Factory { get; } public virtual void Dispose() { } public abstract string GetConnectionString(); protected static string GetConnectionString(string name, string defaultConnectionString) => Environment.GetEnvironmentVariable(name) ?? defaultConnectionString; public DbConnection GetOpenConnection() { var conn = Factory.CreateConnection()!; conn.ConnectionString = GetConnectionString(); conn.Open(); if (conn.State != ConnectionState.Open) throw new InvalidOperationException("should be open!"); return conn; } public DbConnection GetClosedConnection() { var conn = Factory.CreateConnection()!; conn.ConnectionString = GetConnectionString(); if (conn.State != ConnectionState.Closed) throw new InvalidOperationException("should be closed!"); return conn; } public DbParameter CreateRawParameter(string name, object value) { var p = Factory.CreateParameter()!; p.ParameterName = name; p.Value = value ?? DBNull.Value; return p; } } public abstract class SqlServerDatabaseProvider : DatabaseProvider { public override string GetConnectionString() => GetConnectionString(false); private string GetConnectionString(bool mars) { var builder = Factory.CreateConnectionStringBuilder()!; builder.ConnectionString = GetConnectionString("SqlServerConnectionString", "Data Source=.;Initial Catalog=tempdb;Integrated Security=True"); builder["TrustServerCertificate"] = true; if (mars) { ((dynamic)builder).MultipleActiveResultSets = true; } return builder.ConnectionString; } public DbConnection GetOpenConnection(bool mars) { var conn = Factory.CreateConnection()!; conn.ConnectionString = GetConnectionString(mars); conn.Open(); if (conn.State != ConnectionState.Open) throw new InvalidOperationException("should be open!"); return conn; } } public sealed class SystemSqlClientProvider : SqlServerDatabaseProvider { #pragma warning disable CS0618 // Type or member is obsolete public override DbProviderFactory Factory => System.Data.SqlClient.SqlClientFactory.Instance; #pragma warning restore CS0618 // Type or member is obsolete } #if MSSQLCLIENT public sealed class MicrosoftSqlClientProvider : SqlServerDatabaseProvider { public override DbProviderFactory Factory => Microsoft.Data.SqlClient.SqlClientFactory.Instance; } #endif public abstract class TestBase : IDisposable where TProvider : DatabaseProvider { protected void SkipIfMsDataClient() => Skip.If(connection); protected DbConnection GetOpenConnection() => Provider.GetOpenConnection(); protected DbConnection GetClosedConnection() => Provider.GetClosedConnection(); protected DbConnection? _connection; protected DbConnection connection => _connection ??= Provider.GetOpenConnection(); public TProvider Provider { get; } = DatabaseProvider.Instance; protected static CultureInfo ActiveCulture { get { return Thread.CurrentThread.CurrentCulture; } set { Thread.CurrentThread.CurrentCulture = value; } } static TestBase() { Console.WriteLine("Dapper: " + typeof(SqlMapper).AssemblyQualifiedName); var provider = DatabaseProvider.Instance; Console.WriteLine("Using Connectionstring: {0}", provider.GetConnectionString()); var factory = provider.Factory; Console.WriteLine("Using Provider: {0}", factory.GetType().FullName); Console.WriteLine(".NET: " + Environment.Version); Console.Write("Loading native assemblies for SQL types..."); try { SqlServerTypesLoader.LoadNativeAssemblies(AppDomain.CurrentDomain.BaseDirectory); Console.WriteLine("done."); } catch (Exception ex) { Console.WriteLine("failed."); Console.Error.WriteLine(ex.Message); } } public virtual void Dispose() { _connection?.Dispose(); _connection = null; Provider?.Dispose(); } } public static class NonParallelDefinition { public const string Name = "NonParallel"; } } ================================================ FILE: tests/Dapper.Tests/TransactionTests.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Xunit; namespace Dapper.Tests { [Collection("TransactionTests")] public sealed class SystemSqlClientTransactionTests : TransactionTests { } #if MSSQLCLIENT [Collection("TransactionTests")] public sealed class MicrosoftSqlClientTransactionTests : TransactionTests { } #endif public abstract class TransactionTests : TestBase where TProvider : DatabaseProvider { [Fact] public void TestTransactionCommit() { try { connection.Execute("create table #TransactionTest ([ID] int, [Value] varchar(32));"); using (var transaction = connection.BeginTransaction()) { connection.Execute("insert into #TransactionTest ([ID], [Value]) values (1, 'ABC');", transaction: transaction); transaction.Commit(); } Assert.Equal(1, connection.Query("select count(*) from #TransactionTest;").Single()); } finally { connection.Execute("drop table #TransactionTest;"); } } [Fact] public void TestTransactionRollback() { connection.Execute("create table #TransactionTest ([ID] int, [Value] varchar(32));"); try { using (var transaction = connection.BeginTransaction()) { connection.Execute("insert into #TransactionTest ([ID], [Value]) values (1, 'ABC');", transaction: transaction); transaction.Rollback(); } Assert.Equal(0, connection.Query("select count(*) from #TransactionTest;").Single()); } finally { connection.Execute("drop table #TransactionTest;"); } } [Fact] public void TestCommandWithInheritedTransaction() { connection.Execute("create table #TransactionTest ([ID] int, [Value] varchar(32));"); try { using (var transaction = connection.BeginTransaction()) { var transactedConnection = new TransactedConnection(connection, transaction); transactedConnection.Execute("insert into #TransactionTest ([ID], [Value]) values (1, 'ABC');"); transaction.Rollback(); } Assert.Equal(0, connection.Query("select count(*) from #TransactionTest;").Single()); } finally { connection.Execute("drop table #TransactionTest;"); } } } } ================================================ FILE: tests/Dapper.Tests/TupleTests.cs ================================================ using System; using Xunit; namespace Dapper.Tests { [Collection("TupleTests")] public sealed class SystemSqlClientTupleTests : TupleTests { } #if MSSQLCLIENT [Collection("TupleTests")] public sealed class MicrosoftSqlClientTupleTests : TupleTests { } #endif public abstract class TupleTests : TestBase where TProvider : DatabaseProvider { [Fact] public void TupleStructParameter_Fails_HelpfulMessage() { var ex = Assert.Throws(() => connection.QuerySingle("select @id", (id: 42, name: "Fred"))); Assert.Equal("ValueTuple should not be used for parameters - the language-level names are not available to use as parameter names, and it adds unnecessary boxing", ex.Message); } [Fact] public void TupleClassParameter_Works() { Assert.Equal(42, connection.QuerySingle("select @Item1", Tuple.Create(42, "Fred"))); } [Fact] public void TupleReturnValue_Works_ByPosition() { var val = connection.QuerySingle<(int id, string name)>("select 42, 'Fred'"); Assert.Equal(42, val.id); Assert.Equal("Fred", val.name); } [Fact] public void TupleReturnValue_TooManyColumns_Ignored() { var val = connection.QuerySingle<(int id, string name)>("select 42, 'Fred', 123"); Assert.Equal(42, val.id); Assert.Equal("Fred", val.name); } [Fact] public void TupleReturnValue_NullableTuple_Works() { var val = connection.QuerySingleOrDefault<(int id, string name)?>("select 42, 'Fred', 123"); Assert.NotNull(val); Assert.Equal(42, val!.Value.id); Assert.Equal("Fred", val.Value.name); } [Fact] public void TupleReturnValue_NullableTuple_Works_When_Null() { var val = connection.QuerySingleOrDefault<(int id, string name)?>("select 42, 'Fred', 123 where 1 = 2"); Assert.Null(val); } [Fact] public void TupleReturnValue_TooFewColumns_Unmapped() { // I'm very wary of making this throw, but I can also see some sense in pointing out the oddness var val = connection.QuerySingle<(int id, string name, int extra)>("select 42, 'Fred'"); Assert.Equal(42, val.id); Assert.Equal("Fred", val.name); Assert.Equal(0, val.extra); } [Fact] public void TupleReturnValue_Works_NamesIgnored() { var val = connection.QuerySingle<(int id, string name)>("select 42 as [Item2], 'Fred' as [Item1]"); Assert.Equal(42, val.id); Assert.Equal("Fred", val.name); } [Fact] public void TupleReturnValue_Works_With8Elements() { // C# encodes an 8-tuple as ValueTuple> var val = connection.QuerySingle<(int e1, int e2, int e3, int e4, int e5, int e6, int e7, int e8)>( "select 1, 2, 3, 4, 5, 6, 7, 8"); Assert.Equal(1, val.e1); Assert.Equal(2, val.e2); Assert.Equal(3, val.e3); Assert.Equal(4, val.e4); Assert.Equal(5, val.e5); Assert.Equal(6, val.e6); Assert.Equal(7, val.e7); Assert.Equal(8, val.e8); } [Fact] public void Nullable_TupleReturnValue_Works_With8Elements() { // C# encodes an 8-tuple as ValueTuple> var val = connection.QuerySingle<(int e1, int e2, int e3, int e4, int e5, int e6, int e7, int e8)?>( "select 1, 2, 3, 4, 5, 6, 7, 8"); Assert.NotNull(val); Assert.Equal(1, val!.Value.e1); Assert.Equal(2, val.Value.e2); Assert.Equal(3, val.Value.e3); Assert.Equal(4, val.Value.e4); Assert.Equal(5, val.Value.e5); Assert.Equal(6, val.Value.e6); Assert.Equal(7, val.Value.e7); Assert.Equal(8, val.Value.e8); } [Fact] public void Nullable_TupleReturnValue_Works_With8Elements_When_Null() { // C# encodes an 8-tuple as ValueTuple> var val = connection.QuerySingleOrDefault<(int e1, int e2, int e3, int e4, int e5, int e6, int e7, int e8)?>( "select 1, 2, 3, 4, 5, 6, 7, 8 where 1 = 2"); Assert.Null(val); } [Fact] public void TupleReturnValue_Works_With15Elements() { // C# encodes a 15-tuple as ValueTuple>> var val = connection.QuerySingle<(int e1, int e2, int e3, int e4, int e5, int e6, int e7, int e8, int e9, int e10, int e11, int e12, int e13, int e14, int e15)>( "select 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15"); Assert.Equal(1, val.e1); Assert.Equal(2, val.e2); Assert.Equal(3, val.e3); Assert.Equal(4, val.e4); Assert.Equal(5, val.e5); Assert.Equal(6, val.e6); Assert.Equal(7, val.e7); Assert.Equal(8, val.e8); Assert.Equal(9, val.e9); Assert.Equal(10, val.e10); Assert.Equal(11, val.e11); Assert.Equal(12, val.e12); Assert.Equal(13, val.e13); Assert.Equal(14, val.e14); Assert.Equal(15, val.e15); } [Fact] public void Nullable_TupleReturnValue_Works_With15Elements() { // C# encodes a 15-tuple as ValueTuple>> var val = connection.QuerySingle<(int e1, int e2, int e3, int e4, int e5, int e6, int e7, int e8, int e9, int e10, int e11, int e12, int e13, int e14, int e15)?>( "select 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15"); Assert.NotNull(val); Assert.Equal(1, val!.Value.e1); Assert.Equal(2, val.Value.e2); Assert.Equal(3, val.Value.e3); Assert.Equal(4, val.Value.e4); Assert.Equal(5, val.Value.e5); Assert.Equal(6, val.Value.e6); Assert.Equal(7, val.Value.e7); Assert.Equal(8, val.Value.e8); Assert.Equal(9, val.Value.e9); Assert.Equal(10, val.Value.e10); Assert.Equal(11, val.Value.e11); Assert.Equal(12, val.Value.e12); Assert.Equal(13, val.Value.e13); Assert.Equal(14, val.Value.e14); Assert.Equal(15, val.Value.e15); } [Fact] public void Nullable_TupleReturnValue_Works_With15Elements_When_Null() { // C# encodes a 15-tuple as ValueTuple>> var val = connection.QuerySingleOrDefault<(int e1, int e2, int e3, int e4, int e5, int e6, int e7, int e8, int e9, int e10, int e11, int e12, int e13, int e14, int e15)?>( "select 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 where 1 = 2"); Assert.Null(val); } [Fact] public void TupleReturnValue_Works_WithStringField() { var val = connection.QuerySingle>("select '42'"); Assert.Equal("42", val.Item1); } [Fact] public void TupleReturnValue_Works_WithByteField() { var val = connection.QuerySingle>("select 0xDEADBEEF"); Assert.Equal(new byte[] { 0xDE, 0xAD, 0xBE, 0xEF }, val.Item1); } } } ================================================ FILE: tests/Dapper.Tests/TypeHandlerTests.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Linq; using System.Reflection; using Xunit; namespace Dapper.Tests { [Collection(NonParallelDefinition.Name)] public sealed class SystemSqlClientTypeHandlerTests : TypeHandlerTests { } #if MSSQLCLIENT [Collection(NonParallelDefinition.Name)] public sealed class MicrosoftSqlClientTypeHandlerTests : TypeHandlerTests { } #endif public abstract class TypeHandlerTests : TestBase where TProvider : DatabaseProvider { [Fact] public void TestChangingDefaultStringTypeMappingToAnsiString() { const string sql = "SELECT SQL_VARIANT_PROPERTY(CONVERT(sql_variant, @testParam),'BaseType') AS BaseType"; var param = new { testParam = "TestString" }; var result01 = connection.Query(sql, param).FirstOrDefault(); Assert.Equal("nvarchar", result01); SqlMapper.PurgeQueryCache(); SqlMapper.AddTypeMap(typeof(string), DbType.AnsiString, false); // Change Default String Handling to AnsiString try { var result02 = connection.Query(sql, param).FirstOrDefault(); Assert.Equal("varchar", result02); SqlMapper.PurgeQueryCache(); } finally { SqlMapper.AddTypeMap(typeof(string), DbType.String, false); // Restore Default to Unicode String } } [Fact] public void TestChangingDefaultStringTypeMappingToAnsiStringFirstOrDefault() { const string sql = "SELECT SQL_VARIANT_PROPERTY(CONVERT(sql_variant, @testParam),'BaseType') AS BaseType"; var param = new { testParam = "TestString" }; var result01 = connection.QueryFirstOrDefault(sql, param); Assert.Equal("nvarchar", result01); SqlMapper.PurgeQueryCache(); SqlMapper.AddTypeMap(typeof(string), DbType.AnsiString, false); // Change Default String Handling to AnsiString try { var result02 = connection.QueryFirstOrDefault(sql, param); Assert.Equal("varchar", result02); SqlMapper.PurgeQueryCache(); } finally { SqlMapper.AddTypeMap(typeof(string), DbType.String, false); // Restore Default to Unicode String } } [Fact] public void TestCustomTypeMap() { // default mapping var item = connection.Query("Select 'AVal' as A, 'BVal' as B").Single(); Assert.Equal("AVal", item.A); Assert.Equal("BVal", item.B); // custom mapping var map = new CustomPropertyTypeMap(typeof(TypeWithMapping), (type, columnName) => type.GetProperties().FirstOrDefault(prop => GetDescriptionFromAttribute(prop) == columnName)!); SqlMapper.SetTypeMap(typeof(TypeWithMapping), map); item = connection.Query("Select 'AVal' as A, 'BVal' as B").Single(); Assert.Equal("BVal", item.A); Assert.Equal("AVal", item.B); // reset to default SqlMapper.SetTypeMap(typeof(TypeWithMapping), null); item = connection.Query("Select 'AVal' as A, 'BVal' as B").Single(); Assert.Equal("AVal", item.A); Assert.Equal("BVal", item.B); } private static string? GetDescriptionFromAttribute(MemberInfo member) { if (member == null) return null; var attrib = (DescriptionAttribute?)Attribute.GetCustomAttribute(member, typeof(DescriptionAttribute), false); return attrib?.Description; } public class TypeWithMapping { [Description("B")] public string? A { get; set; } [Description("A")] public string? B { get; set; } } [Fact] public void Issue136_ValueTypeHandlers() { SqlMapper.ResetTypeHandlers(); SqlMapper.AddTypeHandler(typeof(LocalDate), LocalDateHandler.Default); var param = new LocalDateResult { NotNullable = new LocalDate { Year = 2014, Month = 7, Day = 25 }, NullableNotNull = new LocalDate { Year = 2014, Month = 7, Day = 26 }, NullableIsNull = null, }; var result = connection.Query("SELECT @NotNullable AS NotNullable, @NullableNotNull AS NullableNotNull, @NullableIsNull AS NullableIsNull", param).Single(); SqlMapper.ResetTypeHandlers(); SqlMapper.AddTypeHandler(typeof(LocalDate?), LocalDateHandler.Default); result = connection.Query("SELECT @NotNullable AS NotNullable, @NullableNotNull AS NullableNotNull, @NullableIsNull AS NullableIsNull", param).Single(); } public class LocalDateHandler : SqlMapper.TypeHandler { private LocalDateHandler() { /* private constructor */ } // Make the field type ITypeHandler to ensure it cannot be used with SqlMapper.AddTypeHandler(TypeHandler) // by mistake. public static readonly SqlMapper.ITypeHandler Default = new LocalDateHandler(); public override LocalDate Parse(object? value) { var date = (DateTime)value!; return new LocalDate { Year = date.Year, Month = date.Month, Day = date.Day }; } public override void SetValue(IDbDataParameter parameter, LocalDate value) { parameter.DbType = DbType.DateTime; parameter.Value = new DateTime(value.Year, value.Month, value.Day); } } public struct LocalDate { public int Year { get; set; } public int Month { get; set; } public int Day { get; set; } } public class LocalDateResult { public LocalDate NotNullable { get; set; } public LocalDate? NullableNotNull { get; set; } public LocalDate? NullableIsNull { get; set; } } public class LotsOfNumerics { public enum E_Byte : byte { A = 0, B = 1 } public enum E_SByte : sbyte { A = 0, B = 1 } public enum E_Short : short { A = 0, B = 1 } public enum E_UShort : ushort { A = 0, B = 1 } public enum E_Int : int { A = 0, B = 1 } public enum E_UInt : uint { A = 0, B = 1 } public enum E_Long : long { A = 0, B = 1 } public enum E_ULong : ulong { A = 0, B = 1 } public E_Byte P_Byte { get; set; } public E_SByte P_SByte { get; set; } public E_Short P_Short { get; set; } public E_UShort P_UShort { get; set; } public E_Int P_Int { get; set; } public E_UInt P_UInt { get; set; } public E_Long P_Long { get; set; } public E_ULong P_ULong { get; set; } public bool N_Bool { get; set; } public byte N_Byte { get; set; } public sbyte N_SByte { get; set; } public short N_Short { get; set; } public ushort N_UShort { get; set; } public int N_Int { get; set; } public uint N_UInt { get; set; } public long N_Long { get; set; } public ulong N_ULong { get; set; } public float N_Float { get; set; } public double N_Double { get; set; } public decimal N_Decimal { get; set; } public E_Byte? N_P_Byte { get; set; } public E_SByte? N_P_SByte { get; set; } public E_Short? N_P_Short { get; set; } public E_UShort? N_P_UShort { get; set; } public E_Int? N_P_Int { get; set; } public E_UInt? N_P_UInt { get; set; } public E_Long? N_P_Long { get; set; } public E_ULong? N_P_ULong { get; set; } public bool? N_N_Bool { get; set; } public byte? N_N_Byte { get; set; } public sbyte? N_N_SByte { get; set; } public short? N_N_Short { get; set; } public ushort? N_N_UShort { get; set; } public int? N_N_Int { get; set; } public uint? N_N_UInt { get; set; } public long? N_N_Long { get; set; } public ulong? N_N_ULong { get; set; } public float? N_N_Float { get; set; } public double? N_N_Double { get; set; } public decimal? N_N_Decimal { get; set; } } [Fact] public void TestBigIntForEverythingWorks() { TestBigIntForEverythingWorks_ByDataType("bigint"); TestBigIntForEverythingWorks_ByDataType("int"); TestBigIntForEverythingWorks_ByDataType("tinyint"); TestBigIntForEverythingWorks_ByDataType("smallint"); TestBigIntForEverythingWorks_ByDataType("bit"); TestBigIntForEverythingWorks_ByDataType("float(24)"); TestBigIntForEverythingWorks_ByDataType("float(53)"); } private void TestBigIntForEverythingWorks_ByDataType(string dbType) { using (var reader = connection.ExecuteReader("select cast(1 as " + dbType + ")")) { Assert.True(reader.Read()); reader.GetFieldType(0).Equals(typeof(T)); Assert.False(reader.Read()); Assert.False(reader.NextResult()); } string sql = "select " + string.Join(",", typeof(LotsOfNumerics).GetProperties().Select( x => "cast (1 as " + dbType + ") as [" + x.Name + "]")); var row = connection.Query(sql).Single(); Assert.True(row.N_Bool); Assert.Equal((sbyte)1, row.N_SByte); Assert.Equal((byte)1, row.N_Byte); Assert.Equal((int)1, row.N_Int); Assert.Equal((uint)1, row.N_UInt); Assert.Equal((short)1, row.N_Short); Assert.Equal((ushort)1, row.N_UShort); Assert.Equal((long)1, row.N_Long); Assert.Equal((ulong)1, row.N_ULong); Assert.Equal((float)1, row.N_Float); Assert.Equal((double)1, row.N_Double); Assert.Equal((decimal)1, row.N_Decimal); Assert.Equal(LotsOfNumerics.E_Byte.B, row.P_Byte); Assert.Equal(LotsOfNumerics.E_SByte.B, row.P_SByte); Assert.Equal(LotsOfNumerics.E_Short.B, row.P_Short); Assert.Equal(LotsOfNumerics.E_UShort.B, row.P_UShort); Assert.Equal(LotsOfNumerics.E_Int.B, row.P_Int); Assert.Equal(LotsOfNumerics.E_UInt.B, row.P_UInt); Assert.Equal(LotsOfNumerics.E_Long.B, row.P_Long); Assert.Equal(LotsOfNumerics.E_ULong.B, row.P_ULong); Assert.True(row.N_N_Bool!.Value); Assert.Equal((sbyte)1, row.N_N_SByte!.Value); Assert.Equal((byte)1, row.N_N_Byte!.Value); Assert.Equal((int)1, row.N_N_Int!.Value); Assert.Equal((uint)1, row.N_N_UInt!.Value); Assert.Equal((short)1, row.N_N_Short!.Value); Assert.Equal((ushort)1, row.N_N_UShort!.Value); Assert.Equal((long)1, row.N_N_Long!.Value); Assert.Equal((ulong)1, row.N_N_ULong!.Value); Assert.Equal((float)1, row.N_N_Float!.Value); Assert.Equal((double)1, row.N_N_Double!.Value); Assert.Equal((decimal)1, row.N_N_Decimal); Assert.Equal(LotsOfNumerics.E_Byte.B, row.N_P_Byte!.Value); Assert.Equal(LotsOfNumerics.E_SByte.B, row.N_P_SByte!.Value); Assert.Equal(LotsOfNumerics.E_Short.B, row.N_P_Short!.Value); Assert.Equal(LotsOfNumerics.E_UShort.B, row.N_P_UShort!.Value); Assert.Equal(LotsOfNumerics.E_Int.B, row.N_P_Int!.Value); Assert.Equal(LotsOfNumerics.E_UInt.B, row.N_P_UInt!.Value); Assert.Equal(LotsOfNumerics.E_Long.B, row.N_P_Long!.Value); Assert.Equal(LotsOfNumerics.E_ULong.B, row.N_P_ULong!.Value); TestBigIntForEverythingWorksGeneric(true, dbType); TestBigIntForEverythingWorksGeneric((sbyte)1, dbType); TestBigIntForEverythingWorksGeneric((byte)1, dbType); TestBigIntForEverythingWorksGeneric((int)1, dbType); TestBigIntForEverythingWorksGeneric((uint)1, dbType); TestBigIntForEverythingWorksGeneric((short)1, dbType); TestBigIntForEverythingWorksGeneric((ushort)1, dbType); TestBigIntForEverythingWorksGeneric((long)1, dbType); TestBigIntForEverythingWorksGeneric((ulong)1, dbType); TestBigIntForEverythingWorksGeneric((float)1, dbType); TestBigIntForEverythingWorksGeneric((double)1, dbType); TestBigIntForEverythingWorksGeneric((decimal)1, dbType); TestBigIntForEverythingWorksGeneric(LotsOfNumerics.E_Byte.B, dbType); TestBigIntForEverythingWorksGeneric(LotsOfNumerics.E_SByte.B, dbType); TestBigIntForEverythingWorksGeneric(LotsOfNumerics.E_Int.B, dbType); TestBigIntForEverythingWorksGeneric(LotsOfNumerics.E_UInt.B, dbType); TestBigIntForEverythingWorksGeneric(LotsOfNumerics.E_Short.B, dbType); TestBigIntForEverythingWorksGeneric(LotsOfNumerics.E_UShort.B, dbType); TestBigIntForEverythingWorksGeneric(LotsOfNumerics.E_Long.B, dbType); TestBigIntForEverythingWorksGeneric(LotsOfNumerics.E_ULong.B, dbType); TestBigIntForEverythingWorksGeneric(true, dbType); TestBigIntForEverythingWorksGeneric((sbyte)1, dbType); TestBigIntForEverythingWorksGeneric((byte)1, dbType); TestBigIntForEverythingWorksGeneric((int)1, dbType); TestBigIntForEverythingWorksGeneric((uint)1, dbType); TestBigIntForEverythingWorksGeneric((short)1, dbType); TestBigIntForEverythingWorksGeneric((ushort)1, dbType); TestBigIntForEverythingWorksGeneric((long)1, dbType); TestBigIntForEverythingWorksGeneric((ulong)1, dbType); TestBigIntForEverythingWorksGeneric((float)1, dbType); TestBigIntForEverythingWorksGeneric((double)1, dbType); TestBigIntForEverythingWorksGeneric((decimal)1, dbType); TestBigIntForEverythingWorksGeneric(LotsOfNumerics.E_Byte.B, dbType); TestBigIntForEverythingWorksGeneric(LotsOfNumerics.E_SByte.B, dbType); TestBigIntForEverythingWorksGeneric(LotsOfNumerics.E_Int.B, dbType); TestBigIntForEverythingWorksGeneric(LotsOfNumerics.E_UInt.B, dbType); TestBigIntForEverythingWorksGeneric(LotsOfNumerics.E_Short.B, dbType); TestBigIntForEverythingWorksGeneric(LotsOfNumerics.E_UShort.B, dbType); TestBigIntForEverythingWorksGeneric(LotsOfNumerics.E_Long.B, dbType); TestBigIntForEverythingWorksGeneric(LotsOfNumerics.E_ULong.B, dbType); } private void TestBigIntForEverythingWorksGeneric(T expected, string dbType) { var query = connection.Query("select cast(1 as " + dbType + ")").Single(); Assert.Equal(query, expected); var scalar = connection.ExecuteScalar("select cast(1 as " + dbType + ")"); Assert.Equal(scalar, expected); } [Fact] public void TestSubsequentQueriesSuccess() { var data0 = connection.Query("select 1 as [Id] where 1 = 0").ToList(); Assert.Empty(data0); var data1 = connection.Query(new CommandDefinition("select 1 as [Id] where 1 = 0", flags: CommandFlags.Buffered)).ToList(); Assert.Empty(data1); var data2 = connection.Query(new CommandDefinition("select 1 as [Id] where 1 = 0", flags: CommandFlags.None)).ToList(); Assert.Empty(data2); data0 = connection.Query("select 1 as [Id] where 1 = 0").ToList(); Assert.Empty(data0); data1 = connection.Query(new CommandDefinition("select 1 as [Id] where 1 = 0", flags: CommandFlags.Buffered)).ToList(); Assert.Empty(data1); data2 = connection.Query(new CommandDefinition("select 1 as [Id] where 1 = 0", flags: CommandFlags.None)).ToList(); Assert.Empty(data2); } private class Fooz0 { public int Id { get; set; } } private class Fooz1 { public int Id { get; set; } } private class Fooz2 { public int Id { get; set; } } public class RatingValueHandler : SqlMapper.TypeHandler { private RatingValueHandler() { } public static readonly RatingValueHandler Default = new(); public override RatingValue Parse(object? value) { if (value is int i) { return new RatingValue() { Value = i }; } throw new FormatException("Invalid conversion to RatingValue"); } public override void SetValue(IDbDataParameter parameter, RatingValue? value) { // ... null, range checks etc ... parameter.DbType = System.Data.DbType.Int32; parameter.Value = value?.Value; } } public class RatingValue { public int Value { get; set; } // ... some other properties etc ... } public class MyResult { public string? CategoryName { get; set; } public RatingValue? CategoryRating { get; set; } } [Fact] public void SO24740733_TestCustomValueHandler() { SqlMapper.AddTypeHandler(RatingValueHandler.Default); var foo = connection.Query("SELECT 'Foo' AS CategoryName, 200 AS CategoryRating").Single(); Assert.Equal("Foo", foo.CategoryName); Assert.Equal(200, foo.CategoryRating?.Value); } [Fact] public void SO24740733_TestCustomValueSingleColumn() { SqlMapper.AddTypeHandler(RatingValueHandler.Default); var foo = connection.Query("SELECT 200 AS CategoryRating").Single(); Assert.Equal(200, foo.Value); } public class StringListTypeHandler : SqlMapper.TypeHandler> { private StringListTypeHandler() { } public static readonly StringListTypeHandler Default = new(); //Just a simple List type handler implementation public override void SetValue(IDbDataParameter parameter, List? value) { parameter.Value = string.Join(",", value ?? new()); } public override List Parse(object? value) { return ((value as string) ?? "").Split(',').ToList(); } } public class MyObjectWithStringList { public List? Names { get; set; } } [Fact] public void Issue253_TestIEnumerableTypeHandlerParsing() { SqlMapper.ResetTypeHandlers(); SqlMapper.AddTypeHandler(StringListTypeHandler.Default); var foo = connection.Query("SELECT 'Sam,Kyro' AS Names").Single(); Assert.Equal(new[] { "Sam", "Kyro" }, foo.Names); } [Fact] public void Issue253_TestIEnumerableTypeHandlerSetParameterValue() { SqlMapper.ResetTypeHandlers(); SqlMapper.AddTypeHandler(StringListTypeHandler.Default); connection.Execute("CREATE TABLE #Issue253 (Names VARCHAR(50) NOT NULL);"); try { const string names = "Sam,Kyro"; List names_list = names.Split(',').ToList(); var foo = connection.Query("INSERT INTO #Issue253 (Names) VALUES (@Names); SELECT Names FROM #Issue253;", new { Names = names_list }).Single(); Assert.Equal(names, foo); } finally { connection.Execute("DROP TABLE #Issue253;"); } } public class RecordingTypeHandler : SqlMapper.TypeHandler { public override void SetValue(IDbDataParameter parameter, T? value) { SetValueWasCalled = true; parameter.Value = value; } public override T Parse(object? value) { ParseWasCalled = true; return (T)value!; } public bool SetValueWasCalled { get; set; } public bool ParseWasCalled { get; set; } } [Fact] public void Test_RemoveTypeMap() { SqlMapper.ResetTypeHandlers(); SqlMapper.RemoveTypeMap(typeof(DateTime)); var dateTimeHandler = new RecordingTypeHandler(); SqlMapper.AddTypeHandler(dateTimeHandler); connection.Execute("CREATE TABLE #Test_RemoveTypeMap (x datetime NOT NULL);"); try { connection.Execute("INSERT INTO #Test_RemoveTypeMap VALUES (@Now)", new { DateTime.Now }); connection.Query("SELECT * FROM #Test_RemoveTypeMap"); Assert.True(dateTimeHandler.ParseWasCalled); Assert.True(dateTimeHandler.SetValueWasCalled); } finally { connection.Execute("DROP TABLE #Test_RemoveTypeMap"); SqlMapper.AddTypeMap(typeof(DateTime), DbType.DateTime); // or an option to reset type map? } } [Fact] public void TestReaderWhenResultsChange() { try { connection.Execute("create table #ResultsChange (X int);create table #ResultsChange2 (Y int);insert #ResultsChange (X) values(1);insert #ResultsChange2 (Y) values(1);"); var obj1 = connection.Query("select * from #ResultsChange").Single(); Assert.Equal(1, obj1.X); Assert.Equal(0, obj1.Y); Assert.Equal(0, obj1.Z); var obj2 = connection.Query("select * from #ResultsChange rc inner join #ResultsChange2 rc2 on rc2.Y=rc.X").Single(); Assert.Equal(1, obj2.X); Assert.Equal(1, obj2.Y); Assert.Equal(0, obj2.Z); connection.Execute("alter table #ResultsChange add Z int null"); connection.Execute("update #ResultsChange set Z = 2"); var obj3 = connection.Query("select * from #ResultsChange").Single(); Assert.Equal(1, obj3.X); Assert.Equal(0, obj3.Y); Assert.Equal(2, obj3.Z); var obj4 = connection.Query("select * from #ResultsChange rc inner join #ResultsChange2 rc2 on rc2.Y=rc.X").Single(); Assert.Equal(1, obj4.X); Assert.Equal(1, obj4.Y); Assert.Equal(2, obj4.Z); } finally { connection.Execute("drop table #ResultsChange;drop table #ResultsChange2;"); } } private class ResultsChangeType { public int X { get; set; } public int Y { get; set; } public int Z { get; set; } } public class WrongTypes { public int A { get; set; } public double B { get; set; } public long C { get; set; } public bool D { get; set; } } [Fact] public void TestWrongTypes_WithRightTypes() { var item = connection.Query("select 1 as A, cast(2.0 as float) as B, cast(3 as bigint) as C, cast(1 as bit) as D").Single(); Assert.Equal(1, item.A); Assert.Equal(2.0, item.B); Assert.Equal(3L, item.C); Assert.True(item.D); } [Fact] public void TestWrongTypes_WithWrongTypes() { var item = connection.Query("select cast(1.0 as float) as A, 2 as B, 3 as C, cast(1 as bigint) as D").Single(); Assert.Equal(1, item.A); Assert.Equal(2.0, item.B); Assert.Equal(3L, item.C); Assert.True(item.D); } [Fact] public void TestTreatIntAsABool() { Assert.True(connection.Query("select CAST(1 AS BIT)").Single()); Assert.True(connection.Query("select 1").Single()); } [Fact] public void SO24607639_NullableBools() { var obj = connection.Query( @"declare @vals table (A bit null, B bit null, C bit null); insert @vals (A,B,C) values (1,0,null); select * from @vals").Single(); Assert.NotNull(obj); Assert.True(obj.A.HasValue); Assert.True(obj.A.Value); Assert.True(obj.B.HasValue); Assert.False(obj.B.Value); Assert.Null(obj.C); } private class HazBools { public bool? A { get; set; } public bool? B { get; set; } public bool? C { get; set; } } [Fact] public void Issue130_IConvertible() { dynamic row = connection.Query("select 1 as [a], '2' as [b]").Single(); int a = row.a; string b = row.b; Assert.Equal(1, a); Assert.Equal("2", b); row = connection.Query("select 3 as [a], '4' as [b]").Single(); a = row.a; b = row.b; Assert.Equal(3, a); Assert.Equal("4", b); } [Fact] public void Issue149_TypeMismatch_SequentialAccess() { Guid guid = Guid.Parse("cf0ef7ac-b6fe-4e24-aeda-a2b45bb5654e"); var ex = Assert.ThrowsAny(() => connection.Query("select @guid as Id", new { guid }).First()); Assert.Equal("Error parsing column 0 (Id=cf0ef7ac-b6fe-4e24-aeda-a2b45bb5654e - Guid)", ex.Message); } public class Issue149_Person { public string? Id { get; set; } } [Fact] public void Issue295_NullableDateTime_SqlServer() => Common.TestDateTime(connection); [Fact] public void SO29343103_UtcDates() { const string sql = "select @date"; var date = DateTime.UtcNow; var returned = connection.Query(sql, new { date }).Single(); var delta = returned - date; Assert.True(delta.TotalMilliseconds >= -10 && delta.TotalMilliseconds <= 10); } [Fact] public void Issue461_TypeHandlerWorksInConstructor() { SqlMapper.AddTypeHandler(new Issue461_BlargHandler()); connection.Execute(@"CREATE TABLE #Issue461 ( Id int not null IDENTITY(1,1), SomeValue nvarchar(50), SomeBlargValue nvarchar(200), )"); const string Expected = "abc123def"; var blarg = new Blarg(Expected); connection.Execute( "INSERT INTO #Issue461 (SomeValue, SomeBlargValue) VALUES (@value, @blarg)", new { value = "what up?", blarg }); // test: without constructor var parameterlessWorks = connection.QuerySingle("SELECT * FROM #Issue461"); Assert.Equal(1, parameterlessWorks.Id); Assert.Equal("what up?", parameterlessWorks.SomeValue); Assert.Equal(Expected, parameterlessWorks.SomeBlargValue?.Value); // test: via constructor var parameterDoesNot = connection.QuerySingle("SELECT * FROM #Issue461"); Assert.Equal(1, parameterDoesNot.Id); Assert.Equal("what up?", parameterDoesNot.SomeValue); Assert.Equal(Expected, parameterDoesNot.SomeBlargValue?.Value); } // I would usually expect this to be a struct; using a class // so that we can't pass unexpectedly due to forcing an unsafe cast - want // to see an InvalidCastException if it is wrong private class Blarg { public Blarg(string? value) { Value = value; } public string? Value { get; } public override string ToString() { return Value!; } } private class Issue461_BlargHandler : SqlMapper.TypeHandler { public override void SetValue(IDbDataParameter parameter, Blarg? value) { parameter.Value = ((object?)value?.Value) ?? DBNull.Value; } public override Blarg? Parse(object? value) { string? s = (value == null || value is DBNull) ? null : Convert.ToString(value); return new Blarg(s); } } private class Issue461_ParameterlessTypeConstructor { public int Id { get; set; } public string? SomeValue { get; set; } public Blarg? SomeBlargValue { get; set; } } private class Issue461_ParameterisedTypeConstructor { public Issue461_ParameterisedTypeConstructor(int id, string someValue, Blarg someBlargValue) { Id = id; SomeValue = someValue; SomeBlargValue = someBlargValue; } public int Id { get; } public string SomeValue { get; } public Blarg SomeBlargValue { get; } } [Theory] [InlineData(true)] [InlineData(false)] public void Issue1959_TypeHandlerNullability_Subclass(bool isNull) { Issue1959_Subclass_Handler.Register(); Issue1959_Subclass? when = isNull ? null : new(DateTime.Today); var whenNotNull = when ?? new(new DateTime(1753, 1, 1)); var args = new HazIssue1959_Subclass { Id = 42, Nullable = when, NonNullable = whenNotNull }; var row = connection.QuerySingle( "select @Id as [Id], @NonNullable as [NonNullable], @Nullable as [Nullable]", args); Assert.NotNull(row); Assert.Equal(42, row.Id); Assert.Equal(when, row.Nullable); Assert.Equal(whenNotNull, row.NonNullable); } [Theory] [InlineData(true)] [InlineData(false)] public void Issue1959_TypeHandlerNullability_Raw(bool isNull) { Issue1959_Raw_Handler.Register(); Issue1959_Raw? when = isNull ? null : new(DateTime.Today); var whenNotNull = when ?? new(new DateTime(1753, 1, 1)); var args = new HazIssue1959_Raw { Id = 42, Nullable = when, NonNullable = whenNotNull }; var row = connection.QuerySingle( "select @Id as [Id], @NonNullable as [NonNullable], @Nullable as [Nullable]", args); Assert.NotNull(row); Assert.Equal(42, row.Id); Assert.Equal(when, row.Nullable); Assert.Equal(whenNotNull, row.NonNullable); } public class HazIssue1959_Subclass { public int Id { get; set; } public Issue1959_Subclass NonNullable { get; set; } public Issue1959_Subclass? Nullable { get; set; } } public class HazIssue1959_Raw { public int Id { get; set; } public Issue1959_Raw NonNullable { get; set; } public Issue1959_Raw? Nullable { get; set; } } public class Issue1959_Subclass_Handler : SqlMapper.TypeHandler { public static void Register() => SqlMapper.AddTypeHandler(Instance); private Issue1959_Subclass_Handler() { } private static readonly Issue1959_Subclass_Handler Instance = new(); public override Issue1959_Subclass Parse(object value) { Assert.NotNull(value); Assert.IsType(value); // checking not DbNull etc return new Issue1959_Subclass((DateTime)value); } public override void SetValue(IDbDataParameter parameter, TypeHandlerTests.Issue1959_Subclass value) => parameter.Value = value.Value; } public class Issue1959_Raw_Handler : SqlMapper.ITypeHandler { public static void Register() => SqlMapper.AddTypeHandler(typeof(Issue1959_Raw), Instance); private Issue1959_Raw_Handler() { } private static readonly Issue1959_Raw_Handler Instance = new(); void SqlMapper.ITypeHandler.SetValue(IDbDataParameter parameter, object value) { Assert.NotNull(value); if (value is DBNull) { parameter.Value = value; } else { Assert.IsType(value); // checking not DbNull etc parameter.Value = ((Issue1959_Raw)value).Value; } } object? SqlMapper.ITypeHandler.Parse(Type destinationType, object value) { Assert.NotNull(value); Assert.IsType(value); // checking not DbNull etc return new Issue1959_Raw((DateTime)value); } } #pragma warning disable CA2231 // Overload operator equals on overriding value type Equals public readonly struct Issue1959_Subclass : IEquatable #pragma warning restore CA2231 // Overload operator equals on overriding value type Equals { public Issue1959_Subclass(DateTime value) => Value = value; public readonly DateTime Value; public override int GetHashCode() => Value.GetHashCode(); public override bool Equals(object? obj) => obj is Issue1959_Subclass other && Equals(other); public bool Equals(Issue1959_Subclass other) => other.Value == Value; public override string ToString() => Value.ToString(); } #pragma warning disable CA2231 // Overload operator equals on overriding value type Equals public readonly struct Issue1959_Raw : IEquatable #pragma warning restore CA2231 // Overload operator equals on overriding value type Equals { public Issue1959_Raw(DateTime value) => Value = value; public readonly DateTime Value; public override int GetHashCode() => Value.GetHashCode(); public override bool Equals(object? obj) => obj is Issue1959_Raw other && Equals(other); public bool Equals(Issue1959_Raw other) => other.Value == Value; public override string ToString() => Value.ToString(); } } } ================================================ FILE: tests/Dapper.Tests/WrappedReaderTests.cs ================================================ using System; using System.Collections; using System.Data; using System.Data.Common; using Xunit.Abstractions; namespace Dapper.Tests; public class WrappedReaderTests(ITestOutputHelper testOutputHelper) { [Fact] public void DbWrappedReader_Dispose_DoesNotThrow() { var reader = new DbWrappedReader(new DummyDbCommand(), new ThrowOnCloseDbDataReader(testOutputHelper)); reader.Dispose(); } #if !NETFRAMEWORK [Fact] public async System.Threading.Tasks.Task DbWrappedReader_DisposeAsync_DoesNotThrow() { var reader = new DbWrappedReader(new DummyDbCommand(), new ThrowOnCloseDbDataReader(testOutputHelper)); await reader.DisposeAsync(); } #endif [Fact] public void WrappedBasicReader_Dispose_DoesNotThrow() { var reader = new WrappedBasicReader(new ThrowOnCloseIDataReader()); reader.Dispose(); } #if !NETFRAMEWORK [Fact] public async System.Threading.Tasks.Task WrappedBasicReader_DisposeAsync_DoesNotThrow() { var reader = new WrappedBasicReader(new ThrowOnCloseIDataReader()); await reader.DisposeAsync(); } #endif private class DummyDbCommand : DbCommand { public override void Cancel() => throw new NotSupportedException(); public override int ExecuteNonQuery() => throw new NotSupportedException(); public override object ExecuteScalar() => throw new NotSupportedException(); public override void Prepare() => throw new NotSupportedException(); #pragma warning disable CS8765 // nullability of value public override string CommandText { get; set; } = ""; #pragma warning restore CS8765 // nullability of value public override int CommandTimeout { get; set; } public override CommandType CommandType { get; set; } public override UpdateRowSource UpdatedRowSource { get; set; } protected override DbConnection? DbConnection { get; set; } protected override DbParameterCollection DbParameterCollection => throw new NotSupportedException(); protected override DbTransaction? DbTransaction { get; set; } public override bool DesignTimeVisible { get; set; } protected override DbParameter CreateDbParameter() => throw new NotSupportedException(); protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior) => throw new NotSupportedException(); } private class DummyDbException(string message) : DbException(message); private class ThrowOnCloseDbDataReader(ITestOutputHelper testOutputHelper) : DbDataReader { // This is basically what SqlClient does, see https://github.com/dotnet/SqlClient/blob/v5.2.1/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs#L835-L849 protected override void Dispose(bool disposing) { try { if (disposing) { Close(); } base.Dispose(disposing); } catch (DbException e) { testOutputHelper.WriteLine($"Ignored exception when disposing {e}"); } } public override void Close() => throw new DummyDbException("Exception during Close()"); public override bool GetBoolean(int ordinal) => throw new NotSupportedException(); public override byte GetByte(int ordinal) => throw new NotSupportedException(); public override long GetBytes(int ordinal, long dataOffset, byte[]? buffer, int bufferOffset, int length) => throw new NotSupportedException(); public override char GetChar(int ordinal) => throw new NotSupportedException(); public override long GetChars(int ordinal, long dataOffset, char[]? buffer, int bufferOffset, int length) => throw new NotSupportedException(); public override string GetDataTypeName(int ordinal) => throw new NotSupportedException(); public override DateTime GetDateTime(int ordinal) => throw new NotSupportedException(); public override decimal GetDecimal(int ordinal) => throw new NotSupportedException(); public override double GetDouble(int ordinal) => throw new NotSupportedException(); public override Type GetFieldType(int ordinal) => throw new NotSupportedException(); public override float GetFloat(int ordinal) => throw new NotSupportedException(); public override Guid GetGuid(int ordinal) => throw new NotSupportedException(); public override short GetInt16(int ordinal) => throw new NotSupportedException(); public override int GetInt32(int ordinal) => throw new NotSupportedException(); public override long GetInt64(int ordinal) => throw new NotSupportedException(); public override string GetName(int ordinal) => throw new NotSupportedException(); public override int GetOrdinal(string name) => throw new NotSupportedException(); public override string GetString(int ordinal) => throw new NotSupportedException(); public override object GetValue(int ordinal) => throw new NotSupportedException(); public override int GetValues(object[] values) => throw new NotSupportedException(); public override bool IsDBNull(int ordinal) => throw new NotSupportedException(); public override int FieldCount => throw new NotSupportedException(); public override object this[int ordinal] => throw new NotSupportedException(); public override object this[string name] => throw new NotSupportedException(); public override int RecordsAffected => throw new NotSupportedException(); public override bool HasRows => throw new NotSupportedException(); public override bool IsClosed => throw new NotSupportedException(); public override bool NextResult() => throw new NotSupportedException(); public override bool Read() => throw new NotSupportedException(); public override int Depth => throw new NotSupportedException(); public override IEnumerator GetEnumerator() => throw new NotSupportedException(); } private class ThrowOnCloseIDataReader : IDataReader { public void Dispose() { // Assume that IDataReader Dispose implementation does not throw } public void Close() => throw new DummyDbException("Exception during Close()"); public bool GetBoolean(int i) => throw new NotSupportedException(); public byte GetByte(int i) => throw new NotSupportedException(); public long GetBytes(int i, long fieldOffset, byte[]? buffer, int bufferoffset, int length) => throw new NotSupportedException(); public char GetChar(int i) => throw new NotSupportedException(); public long GetChars(int i, long fieldoffset, char[]? buffer, int bufferoffset, int length) => throw new NotSupportedException(); public IDataReader GetData(int i) => throw new NotSupportedException(); public string GetDataTypeName(int i) => throw new NotSupportedException(); public DateTime GetDateTime(int i) => throw new NotSupportedException(); public decimal GetDecimal(int i) => throw new NotSupportedException(); public double GetDouble(int i) => throw new NotSupportedException(); public Type GetFieldType(int i) => throw new NotSupportedException(); public float GetFloat(int i) => throw new NotSupportedException(); public Guid GetGuid(int i) => throw new NotSupportedException(); public short GetInt16(int i) => throw new NotSupportedException(); public int GetInt32(int i) => throw new NotSupportedException(); public long GetInt64(int i) => throw new NotSupportedException(); public string GetName(int i) => throw new NotSupportedException(); public int GetOrdinal(string name) => throw new NotSupportedException(); public string GetString(int i) => throw new NotSupportedException(); public object GetValue(int i) => throw new NotSupportedException(); public int GetValues(object[] values) => throw new NotSupportedException(); public bool IsDBNull(int i) => throw new NotSupportedException(); public int FieldCount => throw new NotSupportedException(); public object this[int i] => throw new NotSupportedException(); public object this[string name] => throw new NotSupportedException(); public DataTable? GetSchemaTable() => throw new NotSupportedException(); public bool NextResult() => throw new NotSupportedException(); public bool Read() => throw new NotSupportedException(); public int Depth => throw new NotSupportedException(); public bool IsClosed => throw new NotSupportedException(); public int RecordsAffected => throw new NotSupportedException(); } } ================================================ FILE: tests/Dapper.Tests/XmlTests.cs ================================================ using System.Xml; using System.Xml.Linq; using Xunit; namespace Dapper.Tests { [Collection("XmlTests")] public sealed class SystemSqlClientXmlTests : XmlTests { } #if MSSQLCLIENT [Collection("XmlTests")] public sealed class MicrosoftSqlClientXmlTests : XmlTests { } #endif public abstract class XmlTests : TestBase where TProvider : DatabaseProvider { [Fact] public void CommonXmlTypesSupported() { var xml = new XmlDocument(); xml.LoadXml(""); var foo = new Foo { A = xml, B = XDocument.Parse(""), C = XElement.Parse("") }; var bar = connection.QuerySingle("select @a as [A], @b as [B], @c as [C]", new { a = foo.A, b = foo.B, c = foo.C }); Assert.Equal("abc", bar.A?.DocumentElement?.Name); Assert.Equal("def", bar.B?.Root?.Name.LocalName); Assert.Equal("ghi", bar.C?.Name.LocalName); } public class Foo { public XmlDocument? A { get; set; } public XDocument? B { get; set; } public XElement? C { get; set; } } } } ================================================ FILE: tests/Dapper.Tests/xunit.runner.json ================================================ { "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", "shadowCopy": false } ================================================ FILE: tests/Directory.Build.props ================================================ Library false false false false true Full $(DefineConstants);WINDOWS ================================================ FILE: tests/Directory.Build.targets ================================================ false ================================================ FILE: tests/docker-compose.yml ================================================ version: "3" services: mysql: image: mysql:8 container_name: mysql ports: - 3306:3306 environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: test postgres: image: postgres:alpine container_name: postgres ports: - 5432:5432 environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: test sqlserver: image: mcr.microsoft.com/mssql/server:2019-latest container_name: sql-server-db ports: - 1433:1433 environment: ACCEPT_EULA: Y SA_PASSWORD: "Password." ================================================ FILE: version.json ================================================ { "version": "2.1", "assemblyVersion": "2.0.0.0", "publicReleaseRefSpec": [ "^refs/heads/main$", "^refs/tags/v\\d+\\.\\d+" ], "nugetPackageVersion": { "semVer": 2 }, "cloudBuild": { "buildNumber": { "enabled": true, "setVersionVariables": true } } }