diff --git a/.editorconfig b/.editorconfig index d06775a..f14569a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,180 +1,115 @@ root = true -# C# files -[*.cs] - -#### Core EditorConfig Options #### +[*.{cs,vb}] -# Indentation and spacing -indent_size = 4 -indent_style = tab +# IDE0305: Simplify collection initialization +dotnet_style_prefer_collection_expression = when_types_exactly_match +dotnet_style_operator_placement_when_wrapping = beginning_of_line tab_width = 4 - -# New line preferences +indent_size = 4 end_of_line = crlf -insert_final_newline = false - -#### .NET Coding Conventions #### +dotnet_style_coalesce_expression = true:warning +dotnet_style_null_propagation = true:warning +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:warning +dotnet_style_prefer_conditional_expression_over_assignment = true:warning +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_explicit_tuple_names = true:warning -# Organize usings -dotnet_separate_import_directive_groups = false -dotnet_sort_system_directives_first = false -file_header_template = unset +[*.cs] +#### Naming styles #### -# this. and Me. preferences -dotnet_style_qualification_for_event = false:suggestion -dotnet_style_qualification_for_field = false -dotnet_style_qualification_for_method = false:suggestion -dotnet_style_qualification_for_property = false:suggestion +# Naming rules +dotnet_naming_rule.private_const_should_be_title_case.severity = warning +dotnet_naming_rule.private_const_should_be_title_case.symbols = private_const +dotnet_naming_rule.private_const_should_be_title_case.style = title_case -# Language keywords vs BCL types preferences -dotnet_style_predefined_type_for_locals_parameters_members = true:warning -dotnet_style_predefined_type_for_member_access = true:warning +dotnet_naming_symbols.private_const.applicable_kinds = field +dotnet_naming_symbols.private_const.applicable_accessibilities = private +dotnet_naming_symbols.private_const.required_modifiers = const -# Parentheses preferences -dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:suggestion -dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:suggestion -dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion -dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion +dotnet_naming_style.title_case.required_prefix = +dotnet_naming_style.title_case.required_suffix = +dotnet_naming_style.title_case.word_separator = +dotnet_naming_style.title_case.capitalization = pascal_case -# Modifier preferences -dotnet_style_require_accessibility_modifiers = for_non_interface_members +dotnet_naming_rule.interface_should_be_begins_with_i.severity = warning +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i -# Expression-level preferences -dotnet_style_coalesce_expression = true:warning -dotnet_style_collection_initializer = true -dotnet_style_explicit_tuple_names = true -dotnet_style_namespace_match_folder = true -dotnet_style_null_propagation = true:warning -dotnet_style_object_initializer = true -dotnet_style_operator_placement_when_wrapping = beginning_of_line -dotnet_style_prefer_auto_properties = true:suggestion -dotnet_style_prefer_compound_assignment = true -dotnet_style_prefer_conditional_expression_over_assignment = true:warning -dotnet_style_prefer_conditional_expression_over_return = true:warning -dotnet_style_prefer_inferred_anonymous_type_member_names = true -dotnet_style_prefer_inferred_tuple_names = true -dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning -dotnet_style_prefer_simplified_boolean_expressions = true:warning -dotnet_style_prefer_simplified_interpolation = true +dotnet_naming_rule.types_should_be_pascal_case.severity = warning +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case -# Field preferences -dotnet_style_readonly_field = true +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = warning +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case -# Parameter preferences -dotnet_code_quality_unused_parameters = all +dotnet_naming_rule.private_or_internal_field_should_be_private_field.severity = warning +dotnet_naming_rule.private_or_internal_field_should_be_private_field.symbols = private_or_internal_field +dotnet_naming_rule.private_or_internal_field_should_be_private_field.style = private_field -# Suppression preferences -dotnet_remove_unnecessary_suppression_exclusions = none +# Symbol specifications -# New line preferences -dotnet_style_allow_multiple_blank_lines_experimental = true -dotnet_style_allow_statement_immediately_after_block_experimental = false +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = -#### C# Coding Conventions #### +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = -# var preferences -csharp_style_var_elsewhere = false -csharp_style_var_for_built_in_types = false -csharp_style_var_when_type_is_apparent = true:suggestion +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = -# Expression-bodied members -csharp_style_expression_bodied_accessors = true:suggestion -csharp_style_expression_bodied_constructors = when_on_single_line -csharp_style_expression_bodied_indexers = true:suggestion -csharp_style_expression_bodied_lambdas = true:suggestion -csharp_style_expression_bodied_local_functions = true:suggestion -csharp_style_expression_bodied_methods = true:suggestion -csharp_style_expression_bodied_operators = true:suggestion -csharp_style_expression_bodied_properties = true:suggestion +dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field +dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private, private_protected +dotnet_naming_symbols.private_or_internal_field.required_modifiers = -# Pattern matching preferences -csharp_style_pattern_matching_over_as_with_null_check = true:warning -csharp_style_pattern_matching_over_is_with_cast_check = true:warning -csharp_style_prefer_not_pattern = true:warning -csharp_style_prefer_pattern_matching = true:suggestion -csharp_style_prefer_switch_expression = true +# Naming styles -# Null-checking preferences -csharp_style_conditional_delegate_call = true:warning +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case -# Modifier preferences -csharp_prefer_static_local_function = true -csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case -# Code-block preferences -csharp_prefer_braces = when_multiline -csharp_prefer_simple_using_statement = true -csharp_style_namespace_declarations = file_scoped:suggestion +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case -# Expression-level preferences -csharp_prefer_simple_default_expression = true -csharp_style_deconstructed_variable_declaration = true -csharp_style_implicit_object_creation_when_type_is_apparent = true -csharp_style_inlined_variable_declaration = true -csharp_style_pattern_local_over_anonymous_function = true -csharp_style_prefer_index_operator = true:silent -csharp_style_prefer_null_check_over_type_check = true:warning -csharp_style_prefer_range_operator = true:silent -csharp_style_throw_expression = true:warning -csharp_style_unused_value_assignment_preference = discard_variable -csharp_style_unused_value_expression_statement_preference = discard_variable - -# 'using' directive preferences -csharp_using_directive_placement = outside_namespace:warning - -# New line preferences -csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false:warning -csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true -csharp_style_allow_embedded_statements_on_same_line_experimental = true - -#### C# Formatting Rules #### - -# New line preferences -csharp_new_line_before_catch = true -csharp_new_line_before_else = true -csharp_new_line_before_finally = true -csharp_new_line_before_members_in_anonymous_types = true -csharp_new_line_before_members_in_object_initializers = true -csharp_new_line_before_open_brace = all -csharp_new_line_between_query_expression_clauses = true - -# Indentation preferences -csharp_indent_block_contents = true -csharp_indent_braces = false -csharp_indent_case_contents = true -csharp_indent_case_contents_when_block = true +dotnet_naming_style.private_field.required_prefix = _ +dotnet_naming_style.private_field.required_suffix = +dotnet_naming_style.private_field.word_separator = +dotnet_naming_style.private_field.capitalization = camel_case csharp_indent_labels = one_less_than_current -csharp_indent_switch_labels = true - -# Space preferences -csharp_space_after_cast = false -csharp_space_after_colon_in_inheritance_clause = true -csharp_space_after_comma = true -csharp_space_after_dot = false -csharp_space_after_keywords_in_control_flow_statements = true -csharp_space_after_semicolon_in_for_statement = true -csharp_space_around_binary_operators = before_and_after -csharp_space_around_declaration_statements = false -csharp_space_before_colon_in_inheritance_clause = true -csharp_space_before_comma = false -csharp_space_before_dot = false -csharp_space_before_open_square_brackets = false -csharp_space_before_semicolon_in_for_statement = false -csharp_space_between_empty_square_brackets = false -csharp_space_between_method_call_empty_parameter_list_parentheses = false -csharp_space_between_method_call_name_and_opening_parenthesis = false -csharp_space_between_method_call_parameter_list_parentheses = false -csharp_space_between_method_declaration_empty_parameter_list_parentheses = false -csharp_space_between_method_declaration_name_and_open_parenthesis = false -csharp_space_between_method_declaration_parameter_list_parentheses = false -csharp_space_between_parentheses = false -csharp_space_between_square_brackets = false - -# Wrapping preferences -csharp_preserve_single_line_blocks = true -csharp_preserve_single_line_statements = true +csharp_using_directive_placement = outside_namespace:error +csharp_prefer_simple_using_statement = true:suggestion +csharp_prefer_braces = when_multiline:suggestion +csharp_style_namespace_declarations = file_scoped:suggestion +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:suggestion +csharp_style_prefer_primary_constructors = true:suggestion +csharp_prefer_system_threading_lock = true:suggestion +csharp_style_expression_bodied_methods = true:suggestion +csharp_style_expression_bodied_constructors = true:suggestion +csharp_style_expression_bodied_operators = true:suggestion +csharp_style_expression_bodied_properties = true:warning +csharp_style_expression_bodied_indexers = true:warning +csharp_style_expression_bodied_accessors = true:warning +csharp_style_expression_bodied_lambdas = true:suggestion +csharp_style_expression_bodied_local_functions = true:suggestion +[*.vb] #### Naming styles #### # Naming rules @@ -191,31 +126,66 @@ dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.static_field_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.static_field_should_be_pascal_case.symbols = static_field +dotnet_naming_rule.static_field_should_be_pascal_case.style = pascal_case + # Symbol specifications dotnet_naming_symbols.interface.applicable_kinds = interface -dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected dotnet_naming_symbols.interface.required_modifiers = dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum -dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected dotnet_naming_symbols.types.required_modifiers = dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method -dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected dotnet_naming_symbols.non_field_members.required_modifiers = +dotnet_naming_symbols.static_field.applicable_kinds = field +dotnet_naming_symbols.static_field.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected +dotnet_naming_symbols.static_field.required_modifiers = shared + # Naming styles +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + dotnet_naming_style.pascal_case.required_prefix = dotnet_naming_style.pascal_case.required_suffix = dotnet_naming_style.pascal_case.word_separator = dotnet_naming_style.pascal_case.capitalization = pascal_case -dotnet_naming_style.begins_with_i.required_prefix = I -dotnet_naming_style.begins_with_i.required_suffix = -dotnet_naming_style.begins_with_i.word_separator = -dotnet_naming_style.begins_with_i.capitalization = pascal_case +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +[*.cs] + +# CA1510: Use ArgumentNullException throw helper +dotnet_diagnostic.CA1510.severity = silent + +# IDE0130: Namespace does not match folder structure +dotnet_diagnostic.IDE0130.severity = silent + +# IDE0301: Simplify collection initialization +dotnet_diagnostic.IDE0301.severity = silent + +# IDE0305: Simplify collection initialization +dotnet_diagnostic.IDE0305.severity = silent + +# IDE0306: Simplify collection initialization +dotnet_diagnostic.IDE0306.severity = silent -# RCS1123: Add parentheses when necessary. -dotnet_diagnostic.RCS1123.severity = silent +# RCS1077: Optimize LINQ usage. +dotnet_diagnostic.RCS1077.severity = silent \ No newline at end of file diff --git a/Open.Database.Extensions.sln b/Open.Database.Extensions.sln index 1858a6f..5fd74f5 100644 --- a/Open.Database.Extensions.sln +++ b/Open.Database.Extensions.sln @@ -11,12 +11,21 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Open.Database.Extensions.Sq EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Open.Database.Extensions.Channel", "Source\Channel\Open.Database.Extensions.Channel.csproj", "{EE37225F-0B2B-4225-B07D-2BCE007B780C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Open.Database.Extensions", "Source\Extensions\Open.Database.Extensions.csproj", "{4FB33C8D-D70E-4952-9947-A1347BCED665}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Open.Database.Extensions.Core.Tests", "Tests\Open.Database.Extensions.Core.Tests\Open.Database.Extensions.Core.Tests.csproj", "{B2C2E89F-3B08-4CF8-B363-468C0918F867}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{B4ADB121-1900-4CB4-905C-A7B1F640BA8A}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3194AC1F-55DE-4EAE-BDC6-4E427B034CFF}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + Source\Directory.Build.props = Source\Directory.Build.props + README.md = README.md + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Open.Database.Extensions.MSSqlClient", "Source\MSSqlClient\Open.Database.Extensions.MSSqlClient.csproj", "{B9245504-9E2F-417F-87AC-8950BFB4FD4E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Open.Database.Extensions", "Source\Extensions\Open.Database.Extensions.csproj", "{446C228B-E12F-424D-AB46-B856422EBDAE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -39,14 +48,18 @@ Global {EE37225F-0B2B-4225-B07D-2BCE007B780C}.Debug|Any CPU.Build.0 = Debug|Any CPU {EE37225F-0B2B-4225-B07D-2BCE007B780C}.Release|Any CPU.ActiveCfg = Release|Any CPU {EE37225F-0B2B-4225-B07D-2BCE007B780C}.Release|Any CPU.Build.0 = Release|Any CPU - {4FB33C8D-D70E-4952-9947-A1347BCED665}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4FB33C8D-D70E-4952-9947-A1347BCED665}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4FB33C8D-D70E-4952-9947-A1347BCED665}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4FB33C8D-D70E-4952-9947-A1347BCED665}.Release|Any CPU.Build.0 = Release|Any CPU {B2C2E89F-3B08-4CF8-B363-468C0918F867}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B2C2E89F-3B08-4CF8-B363-468C0918F867}.Debug|Any CPU.Build.0 = Debug|Any CPU {B2C2E89F-3B08-4CF8-B363-468C0918F867}.Release|Any CPU.ActiveCfg = Release|Any CPU {B2C2E89F-3B08-4CF8-B363-468C0918F867}.Release|Any CPU.Build.0 = Release|Any CPU + {B9245504-9E2F-417F-87AC-8950BFB4FD4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B9245504-9E2F-417F-87AC-8950BFB4FD4E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B9245504-9E2F-417F-87AC-8950BFB4FD4E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B9245504-9E2F-417F-87AC-8950BFB4FD4E}.Release|Any CPU.Build.0 = Release|Any CPU + {446C228B-E12F-424D-AB46-B856422EBDAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {446C228B-E12F-424D-AB46-B856422EBDAE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {446C228B-E12F-424D-AB46-B856422EBDAE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {446C228B-E12F-424D-AB46-B856422EBDAE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/README.md b/README.md index 21123e9..734d3c9 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,25 @@ Useful set of utilities and abstractions for simplifying modern database operations and ensuring dependency injection compatibility. -## Connection Factories +## Principles + +- Minimize connection open time. +- Deferred/lazy transformation. +- Optimize for specific use cases. +- Minimize boilerplate code. + +## Features +- Provides a fluent interface for database operations. +- Supports dependency injection for connection factories. +- Support both synchronous and asynchronous operations. +- Provides expressive commands for executing SQL queries and stored procedures. + +### Connection Factories Connection factories facilitate creation and disposal of connections without the concern of a connection reference or need for awareness of a connection string. A `SqlConnectionFactory` is provided and can be overridden to provide more specific dependency injection configurations. -## Expressive Commands +### Expressive Commands The provided expressive command classes allow for an expressive means to append parameters and execute the results without lengthy complicated setup. @@ -120,3 +133,17 @@ public static bool TryTransaction() .ExecuteScalar(); })); ``` + +## 8.0 Release Notes + +- All `.ConfigureAwait(true)` are now `.ConfigureAwait(false)` as they should be. The caller will need to `.ConfigureAwait(true)` if they need to resume on the calling context. +- Added `Open.Database.Extensions.MSSqlClient` for `Microsoft.Data.SqlClient` support. +- .NET 8.0 added to targets to ensure potential compliation and performance improvements are available. +- Improved nullable integrity. + +## 9.0 Release Notes + +- Open.Database.Extensions meta-package is discontinued. +- .NET 9.0 added to targets to ensure potential compilation and performance improvements are available. +- Impelmented some .NET 8 and 9 specific features. +- Significant cleanup and simplifcation where possible. diff --git a/Source/Channel/AsChannel.cs b/Source/Channel/AsChannel.cs deleted file mode 100644 index 7ee75b9..0000000 --- a/Source/Channel/AsChannel.cs +++ /dev/null @@ -1,664 +0,0 @@ -using System; -using System.Buffers; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; -using System.Diagnostics.Contracts; -using System.Threading; -using System.Threading.Channels; - -namespace Open.Database.Extensions; - -[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2012:Use ValueTasks correctly", Justification = "Intentionally running in the background.")] -[System.Diagnostics.CodeAnalysis.SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "")] -public static partial class ChannelDbExtensions -{ - /// - /// Iterates an and writes each record as an array to an unbound channel. - /// Be sure to await the completion. - /// - /// The IDataReader to iterate. - /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. - /// An optional cancellation token. - /// The channel reader containing the results. - public static ChannelReader AsChannel(this IDataReader reader, - bool singleReader, - CancellationToken cancellationToken = default) - { - if (reader is null) throw new ArgumentNullException(nameof(reader)); - Contract.EndContractBlock(); - - var channel = CreateChannel(-1, singleReader); - _ = ToChannel(reader, channel.Writer, true, cancellationToken); - return channel.Reader; - } - - /// - /// Iterates an and writes each record as an array to an unbound channel. - /// Be sure to await the completion. - /// - /// The IDataReader to iterate. - /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. - /// The array pool to acquire buffers from. - /// An optional cancellation token. - /// The channel reader containing the results. - public static ChannelReader AsChannel(this IDataReader reader, - bool singleReader, - ArrayPool arrayPool, - CancellationToken cancellationToken = default) - { - if (reader is null) throw new ArgumentNullException(nameof(reader)); - if (arrayPool is null) throw new ArgumentNullException(nameof(arrayPool)); - Contract.EndContractBlock(); - - var channel = CreateChannel(-1, singleReader); - _ = ToChannel(reader, channel.Writer, true, arrayPool, cancellationToken); - return channel.Reader; - } - - /// - /// Iterates an through the transform function and writes each record to an unbound channel. - /// Be sure to await the completion. - /// - /// The return type of the transform function. - /// The IDataReader to iterate. - /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. - /// The transform function for each IDataRecord. - /// An optional cancellation token. - /// The channel reader containing the results. - public static ChannelReader AsChannel(this IDataReader reader, - bool singleReader, - Func transform, - CancellationToken cancellationToken = default) - { - if (reader is null) throw new ArgumentNullException(nameof(reader)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - var channel = CreateChannel(-1, singleReader); - _ = ToChannel(reader, channel.Writer, true, transform, cancellationToken); - return channel.Reader; - } - - /// - /// Iterates an mapping the results to classes of type and writes each record an unbound channel. - /// - /// The return type of the transform function. - /// The IDataReader to iterate. - /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. - /// An optional cancellation token. - /// The channel reader containing the results. - public static ChannelReader AsChannel(this IDataReader reader, - bool singleReader, - CancellationToken cancellationToken = default) - where T : new() - { - if (reader is null) throw new ArgumentNullException(nameof(reader)); - Contract.EndContractBlock(); - - var channel = CreateChannel(-1, singleReader); - _ = ToChannel(reader, channel.Writer, true, cancellationToken); - return channel.Reader; - } - - /// - /// Iterates an mapping the results to classes of type and writes each record an unbound channel. - /// - /// The return type of the transform function. - /// The IDataReader to iterate. - /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. - /// An optional override map of field names to column names. - /// An optional cancellation token. - /// The channel reader containing the results. - public static ChannelReader AsChannel(this IDataReader reader, - bool singleReader, - IEnumerable<(string Field, string? Column)> fieldMappingOverrides, - CancellationToken cancellationToken = default) - where T : new() - { - if (reader is null) throw new ArgumentNullException(nameof(reader)); - Contract.EndContractBlock(); - - var channel = CreateChannel(-1, singleReader); - _ = ToChannel(reader, channel.Writer, true, fieldMappingOverrides, cancellationToken); - return channel.Reader; - } - - /// - /// Iterates an and writes each record as an array to an unbound channel. - /// Be sure to await the completion. - /// - /// The command to acquire a reader from to iterate. - /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. - /// An optional cancellation token. - /// The channel reader containing the results. - public static ChannelReader AsChannel(this IDbCommand command, - bool singleReader, - CancellationToken cancellationToken = default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - Contract.EndContractBlock(); - - var channel = CreateChannel(-1, singleReader); - _ = ToChannel(command, channel.Writer, true, cancellationToken); - return channel.Reader; - } - - /// - /// Iterates an and writes each record as an array to an unbound channel. - /// Be sure to await the completion. - /// - /// The command to acquire a reader from to iterate. - /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. - /// The array pool to acquire buffers from. - /// An optional cancellation token. - /// The channel reader containing the results. - public static ChannelReader AsChannel(this IDbCommand command, - bool singleReader, - ArrayPool arrayPool, - CancellationToken cancellationToken = default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (arrayPool is null) throw new ArgumentNullException(nameof(arrayPool)); - Contract.EndContractBlock(); - - var channel = CreateChannel(-1, singleReader); - _ = ToChannel(command, channel.Writer, true, arrayPool, cancellationToken); - return channel.Reader; - } - - /// - /// Iterates an through the transform function and writes each record to an unbound channel. - /// Be sure to await the completion. - /// - /// The return type of the transform function. - /// The command to acquire a reader from to iterate. - /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. - /// The transform function for each IDataRecord. - /// An optional cancellation token. - /// The channel reader containing the results. - public static ChannelReader AsChannel(this IDbCommand command, - bool singleReader, - Func transform, - CancellationToken cancellationToken = default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - var channel = CreateChannel(-1, singleReader); - _ = ToChannel(command, channel.Writer, true, transform, cancellationToken); - return channel.Reader; - } - - /// - /// Iterates an mapping the results to classes of type and writes each record an unbound channel. - /// - /// The return type of the transform function. - /// The command to acquire a reader from to iterate. - /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. - /// An optional cancellation token. - /// The channel reader containing the results. - public static ChannelReader AsChannel(this IDbCommand command, - bool singleReader, - CancellationToken cancellationToken = default) - where T : new() - { - if (command is null) throw new ArgumentNullException(nameof(command)); - Contract.EndContractBlock(); - - var channel = CreateChannel(-1, singleReader); - _ = ToChannel(command, channel.Writer, true, cancellationToken); - return channel.Reader; - } - - /// - /// Iterates an mapping the results to classes of type and writes each record an unbound channel. - /// - /// The return type of the transform function. - /// The command to acquire a reader from to iterate. - /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. - /// An optional override map of field names to column names. - /// An optional cancellation token. - /// The channel reader containing the results. - public static ChannelReader AsChannel(this IDbCommand command, - bool singleReader, - IEnumerable<(string Field, string? Column)> fieldMappingOverrides, - CancellationToken cancellationToken = default) - where T : new() - { - if (command is null) throw new ArgumentNullException(nameof(command)); - Contract.EndContractBlock(); - - var channel = CreateChannel(-1, singleReader); - _ = ToChannel(command, channel.Writer, true, fieldMappingOverrides, cancellationToken); - return channel.Reader; - } - - /// - /// Iterates an and writes each record as an array to an unbound channel. - /// Be sure to await the completion. - /// - /// The IDataReader to iterate. - /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. - /// The channel reader containing the results. - public static ChannelReader AsChannel(this IExecuteReader command, - bool singleReader) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - Contract.EndContractBlock(); - - var channel = CreateChannel(-1, singleReader); - _ = ToChannel(command, channel.Writer, true); - return channel.Reader; - } - - /// - /// Iterates an and writes each record as an array to an unbound channel. - /// Be sure to await the completion. - /// - /// The IDataReader to iterate. - /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. - /// The array pool to acquire buffers from. - /// The channel reader containing the results. - public static ChannelReader AsChannel(this IExecuteReader command, - bool singleReader, - ArrayPool arrayPool) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (arrayPool is null) throw new ArgumentNullException(nameof(arrayPool)); - Contract.EndContractBlock(); - - var channel = CreateChannel(-1, singleReader); - _ = ToChannel(command, channel.Writer, true, arrayPool); - return channel.Reader; - } - - /// - /// Iterates an through the transform function and writes each record to an unbound channel. - /// Be sure to await the completion. - /// - /// The return type of the transform function. - /// The IDataReader to iterate. - /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. - /// The transform function for each IDataRecord. - /// The channel reader containing the results. - public static ChannelReader AsChannel(this IExecuteReader command, - bool singleReader, - Func transform) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - var channel = CreateChannel(-1, singleReader); - _ = ToChannel(command, channel.Writer, true, transform); - return channel.Reader; - } - - /// - /// Iterates an mapping the results to classes of type and writes each record an unbound channel. - /// - /// The return type of the transform function. - /// The IDataReader to iterate. - /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. - /// The channel reader containing the results. - public static ChannelReader AsChannel(this IExecuteReader command, - bool singleReader) - where T : new() - { - if (command is null) throw new ArgumentNullException(nameof(command)); - Contract.EndContractBlock(); - - var channel = CreateChannel(-1, singleReader); - _ = ToChannel(command, channel.Writer, true); - return channel.Reader; - } - - /// - /// Iterates an mapping the results to classes of type and writes each record an unbound channel. - /// - /// The return type of the transform function. - /// The IDataReader to iterate. - /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. - /// An optional override map of field names to column names. - /// The channel reader containing the results. - public static ChannelReader AsChannel(this IExecuteReader command, - bool singleReader, - IEnumerable<(string Field, string? Column)> fieldMappingOverrides) - where T : new() - { - if (command is null) throw new ArgumentNullException(nameof(command)); - Contract.EndContractBlock(); - - var channel = CreateChannel(-1, singleReader); - _ = ToChannel(command, channel.Writer, true, fieldMappingOverrides); - return channel.Reader; - } - -#if NETSTANDARD2_1 - /// - /// Asynchronously iterates an DbDataReader and writes each record as an array to an unbound channel. - /// Iterates an DbDataReader through the transform function and writes each record to an unbound channel. - /// Be sure to await the completion. - /// - /// The IDataReader to iterate. - /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. - /// An optional cancellation token. - /// The channel reader containing the results. - public static ChannelReader AsChannelAsync(this DbDataReader reader, - bool singleReader, - CancellationToken cancellationToken = default) - { - if (reader is null) throw new ArgumentNullException(nameof(reader)); - Contract.EndContractBlock(); - - var channel = CreateChannel(-1, singleReader); - _ = ToChannelAsync(reader, channel.Writer, true, cancellationToken); - return channel.Reader; - } - - /// - /// Asynchronously iterates an DbDataReader and writes each record as an array to an unbound channel. - /// Be sure to await the completion. - /// - /// The IDataReader to iterate. - /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. - /// The array pool to acquire buffers from. - /// An optional cancellation token. - /// The channel reader containing the results. - public static ChannelReader AsChannelAsync(this DbDataReader reader, - bool singleReader, - ArrayPool arrayPool, - CancellationToken cancellationToken = default) - { - if (reader is null) throw new ArgumentNullException(nameof(reader)); - if (arrayPool is null) throw new ArgumentNullException(nameof(arrayPool)); - Contract.EndContractBlock(); - - var channel = CreateChannel(-1, singleReader); - _ = ToChannelAsync(reader, channel.Writer, true, arrayPool, cancellationToken); - return channel.Reader; - } - - /// - /// Asynchronously iterates an DbDataReader through the transform function and writes each record to an unbound channel. - /// Be sure to await the completion. - /// - /// The return type of the transform function. - /// The IDataReader to iterate. - /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. - /// The transform function for each IDataRecord. - /// An optional cancellation token. - /// The channel reader containing the results. - public static ChannelReader AsChannelAsync(this DbDataReader reader, - bool singleReader, - Func transform, - CancellationToken cancellationToken = default) - { - if (reader is null) throw new ArgumentNullException(nameof(reader)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - var channel = CreateChannel(-1, singleReader); - _ = ToChannelAsync(reader, channel.Writer, true, transform, cancellationToken); - return channel.Reader; - } - - /// - /// Asynchronously iterates an DbDataReader mapping the results to classes of type and writes each record an unbound channel. - /// - /// The return type of the transform function. - /// The IDataReader to iterate. - /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. - /// An optional cancellation token. - /// The channel reader containing the results. - public static ChannelReader AsChannelAsync(this DbDataReader reader, - bool singleReader, - CancellationToken cancellationToken = default) - where T : new() - { - if (reader is null) throw new ArgumentNullException(nameof(reader)); - Contract.EndContractBlock(); - - var channel = CreateChannel(-1, singleReader); - _ = ToChannelAsync(reader, channel.Writer, true, cancellationToken); - return channel.Reader; - } - - /// - /// Asynchronously iterates an DbDataReader mapping the results to classes of type and writes each record an unbound channel. - /// - /// The return type of the transform function. - /// The IDataReader to iterate. - /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. - /// An optional override map of field names to column names. - /// An optional cancellation token. - /// The channel reader containing the results. - public static ChannelReader AsChannelAsync(this DbDataReader reader, - bool singleReader, - IEnumerable<(string Field, string? Column)> fieldMappingOverrides, - CancellationToken cancellationToken = default) - where T : new() - { - if (reader is null) throw new ArgumentNullException(nameof(reader)); - Contract.EndContractBlock(); - - var channel = CreateChannel(-1, singleReader); - _ = ToChannelAsync(reader, channel.Writer, true, fieldMappingOverrides, cancellationToken); - return channel.Reader; - } - - /// - /// Asynchronously iterates an DbDataReader and writes each record as an array to an unbound channel. - /// Be sure to await the completion. - /// - /// The command to acquire a reader from to iterate. - /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. - /// An optional cancellation token. - /// The channel reader containing the results. - public static ChannelReader AsChannelAsync(this DbCommand command, - bool singleReader, - CancellationToken cancellationToken = default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - Contract.EndContractBlock(); - - var channel = CreateChannel(-1, singleReader); - _ = ToChannelAsync(command, channel.Writer, true, cancellationToken); - return channel.Reader; - } - - /// - /// Asynchronously iterates an DbDataReader and writes each record as an array to an unbound channel. - /// Be sure to await the completion. - /// - /// The command to acquire a reader from to iterate. - /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. - /// The array pool to acquire buffers from. - /// An optional cancellation token. - /// The channel reader containing the results. - public static ChannelReader AsChannelAsync(this DbCommand command, - bool singleReader, - ArrayPool arrayPool, - CancellationToken cancellationToken = default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (arrayPool is null) throw new ArgumentNullException(nameof(arrayPool)); - Contract.EndContractBlock(); - - var channel = CreateChannel(-1, singleReader); - _ = ToChannelAsync(command, channel.Writer, true, arrayPool, cancellationToken); - return channel.Reader; - } - - /// - /// Asynchronously iterates an DbDataReader through the transform function and writes each record to an unbound channel. - /// Be sure to await the completion. - /// - /// The return type of the transform function. - /// The command to acquire a reader from to iterate. - /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. - /// The transform function for each IDataRecord. - /// An optional cancellation token. - /// The channel reader containing the results. - public static ChannelReader AsChannelAsync(this DbCommand command, - bool singleReader, - Func transform, - CancellationToken cancellationToken = default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - var channel = CreateChannel(-1, singleReader); - _ = ToChannelAsync(command, channel.Writer, true, transform, cancellationToken); - return channel.Reader; - } - - /// - /// Asynchronously iterates an DbDataReader mapping the results to classes of type and writes each record an unbound channel. - /// - /// The return type of the transform function. - /// The command to acquire a reader from to iterate. - /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. - /// An optional cancellation token. - /// The channel reader containing the results. - public static ChannelReader AsChannelAsync(this DbCommand command, - bool singleReader, - CancellationToken cancellationToken = default) - where T : new() - { - if (command is null) throw new ArgumentNullException(nameof(command)); - Contract.EndContractBlock(); - - var channel = CreateChannel(-1, singleReader); - _ = ToChannelAsync(command, channel.Writer, true, cancellationToken); - return channel.Reader; - } - - /// - /// Asynchronously iterates an DbDataReader mapping the results to classes of type and writes each record an unbound channel. - /// - /// The return type of the transform function. - /// The command to acquire a reader from to iterate. - /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. - /// An optional override map of field names to column names. - /// An optional cancellation token. - /// The channel reader containing the results. - public static ChannelReader AsChannelAsync(this DbCommand command, - bool singleReader, - IEnumerable<(string Field, string? Column)> fieldMappingOverrides, - CancellationToken cancellationToken = default) - where T : new() - { - if (command is null) throw new ArgumentNullException(nameof(command)); - Contract.EndContractBlock(); - - var channel = CreateChannel(-1, singleReader); - _ = ToChannelAsync(command, channel.Writer, true, fieldMappingOverrides, cancellationToken); - return channel.Reader; - } - - /// - /// Asynchronously iterates an DbDataReader through the transform function and writes each record to an unbound channel. - /// Be sure to await the completion. - /// - /// The IDataReader to iterate. - /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. - /// The channel reader containing the results. - public static ChannelReader AsChannelAsync(this IExecuteReaderAsync command, - bool singleReader) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - Contract.EndContractBlock(); - - var channel = CreateChannel(-1, singleReader); - _ = ToChannelAsync(command, channel.Writer, true); - return channel.Reader; - } - - /// - /// Asynchronously iterates an DbDataReader through the transform function and writes each record to an unbound channel. - /// Be sure to await the completion. - /// - /// The IDataReader to iterate. - /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. - /// The array pool to acquire buffers from. - /// The channel reader containing the results. - public static ChannelReader AsChannelAsync(this IExecuteReaderAsync command, - bool singleReader, - ArrayPool arrayPool) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - Contract.EndContractBlock(); - - var channel = CreateChannel(-1, singleReader); - _ = ToChannelAsync(command, channel.Writer, true, arrayPool); - return channel.Reader; - } - - /// - /// Asynchronously iterates an DbDataReader through the transform function and writes each record to an unbound channel. - /// Be sure to await the completion. - /// - /// The return type of the transform function. - /// The IDataReader to iterate. - /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. - /// The transform function for each IDataRecord. - /// The channel reader containing the results. - public static ChannelReader AsChannelAsync(this IExecuteReaderAsync command, - bool singleReader, - Func transform) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - var channel = CreateChannel(-1, singleReader); - _ = ToChannelAsync(command, channel.Writer, true, transform); - return channel.Reader; - } - - /// - /// Asynchronously iterates an DbDataReader mapping the results to classes of type and writes each record an unbound channel. - /// - /// The return type of the transform function. - /// The IDataReader to iterate. - /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. - /// The channel reader containing the results. - public static ChannelReader AsChannelAsync(this IExecuteReaderAsync command, - bool singleReader) - where T : new() - { - if (command is null) throw new ArgumentNullException(nameof(command)); - Contract.EndContractBlock(); - - var channel = CreateChannel(-1, singleReader); - _ = ToChannelAsync(command, channel.Writer, true); - return channel.Reader; - } - - /// - /// Asynchronously iterates an DbDataReader mapping the results to classes of type and writes each record an unbound channel. - /// - /// The return type of the transform function. - /// The IDataReader to iterate. - /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. - /// An optional override map of field names to column names. - /// The channel reader containing the results. - public static ChannelReader AsChannelAsync(this IExecuteReaderAsync command, - bool singleReader, - IEnumerable<(string Field, string? Column)> fieldMappingOverrides) - where T : new() - { - if (command is null) throw new ArgumentNullException(nameof(command)); - Contract.EndContractBlock(); - - var channel = CreateChannel(-1, singleReader); - _ = ToChannelAsync(command, channel.Writer, true, fieldMappingOverrides); - return channel.Reader; - } -#endif - -} diff --git a/Source/Channel/CreateChannel.cs b/Source/Channel/CreateChannel.cs index 79e7008..3216197 100644 --- a/Source/Channel/CreateChannel.cs +++ b/Source/Channel/CreateChannel.cs @@ -1,8 +1,4 @@ -using System; -using System.Diagnostics.Contracts; -using System.Threading.Channels; - -namespace Open.Database.Extensions; +namespace Open.Database.Extensions; public static partial class ChannelDbExtensions { diff --git a/Source/Channel/Open.Database.Extensions.Channel.csproj b/Source/Channel/Open.Database.Extensions.Channel.csproj index 407b31d..837dc88 100644 --- a/Source/Channel/Open.Database.Extensions.Channel.csproj +++ b/Source/Channel/Open.Database.Extensions.Channel.csproj @@ -1,49 +1,20 @@  - Open.Database.Extensions - netstandard2.0; netstandard2.1 - latest - enable - electricessence - © electricessence (Oren F.) All rights reserved. - MIT - https://github.com/Open-NET-Libraries/Open.Database.Extensions - https://github.com/Open-NET-Libraries/Open.Database.Extensions Database extensions for pipelining data through channels. Includes Open.Database.Extensions.Core. ado;ado extensions;sql;connection factory;extensions;channel;channels - true - git - 7.0.0 - - true - true - snupkg - logo.png - latest - True - README.md - - - Documentation.xml - - + + + + + - - - + - - True - - - - True - \ - + diff --git a/Source/Channel/ToChannel.Target.cs b/Source/Channel/ToChannel.Target.cs new file mode 100644 index 0000000..1d8c1cd --- /dev/null +++ b/Source/Channel/ToChannel.Target.cs @@ -0,0 +1,800 @@ +namespace Open.Database.Extensions; + +/// +/// Extensions for writing data to a channel. +/// +[SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Simplification and readability.")] +public static partial class ChannelDbExtensions +{ + private static async ValueTask ToChannelCore( + IDbCommand command, + ChannelWriter writer, + bool complete, + CancellationToken cancellationToken, + Func> transform) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (writer is null) throw new ArgumentNullException(nameof(writer)); + if (command.Connection is null) throw new InvalidOperationException("Command has no connection."); + Contract.EndContractBlock(); + + if (!command.Connection.State.HasFlag(ConnectionState.Open)) + { + await writer + .WaitToWriteAndThrowIfClosedAsync(true, cancellationToken) + .ConfigureAwait(false); + } + + if (!complete) + { + await command + .ExecuteReaderAsync(transform, cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + Exception? exception = null; + try + { + return await command + .ExecuteReaderAsync(transform, cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + catch (Exception ex) + { + exception = ex; + throw; + } + finally + { + writer.TryComplete(exception); + } + } + + private static async ValueTask ToChannelCore( + IExecuteReader command, + ChannelWriter writer, + bool complete, + Func> transform) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (writer is null) throw new ArgumentNullException(nameof(writer)); + Contract.EndContractBlock(); + + await writer + .WaitToWriteAndThrowIfClosedAsync(true, command.CancellationToken) + .ConfigureAwait(false); + + if (!complete) + { + await command + .ExecuteReaderAsync(transform) + .ConfigureAwait(false); + } + + Exception? exception = null; + try + { + return await command + .ExecuteReaderAsync(transform) + .ConfigureAwait(false); + } + catch (Exception ex) + { + exception = ex; + throw; + } + finally + { + if (complete) + writer.TryComplete(exception); + } + } + + /// + /// Iterates an and writes each record as an array to the channel. + /// + /// The IDataReader to iterate. + /// The target channel to receive the results. + /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// An optional cancellation token. + /// The number of records processed. + public static ValueTask ToChannel(this IDataReader reader, + ChannelWriter target, + bool complete, + CancellationToken cancellationToken = default) + => target.WriteAll( + reader.AsEnumerable(), + complete, + false, + cancellationToken); + + /// + /// Iterates an and writes each record as an array to the channel. + /// + /// The IDataReader to iterate. + /// The target channel to receive the results. + /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// The array pool to acquire buffers from. + /// An optional cancellation token. + /// The number of records processed. + public static ValueTask ToChannel(this IDataReader reader, + ChannelWriter target, + bool complete, + ArrayPool? arrayPool, + CancellationToken cancellationToken = default) + => target.WriteAll( + reader.AsEnumerable(arrayPool), + complete, + false, + cancellationToken); + + /// + /// Iterates an through the transform function and writes each record to the channel. + /// + /// The return type of the transform function. + /// The IDataReader to iterate. + /// The target channel to receive the results. + /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// The transform function for each IDataRecord. + /// An optional cancellation token. + /// The number of records processed. + public static ValueTask ToChannel(this IDataReader reader, + ChannelWriter target, + bool complete, + Func transform, + CancellationToken cancellationToken = default) + => target.WriteAll( + reader.Select(transform, cancellationToken), + complete, + false, + cancellationToken); + + /// + /// Iterates an mapping the results to classes of type and writes each record to the channel. + /// + /// The return type of the transform function. + /// The IDataReader to iterate. + /// The target channel to receive the results. + /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// An optional cancellation token. + /// The number of records processed. + public static ValueTask ToChannel(this IDataReader reader, + ChannelWriter target, + bool complete, + CancellationToken cancellationToken = default) + where T : new() + => Transformer + .Create() + .PipeResultsTo(reader, target, complete, cancellationToken); + + /// + /// Iterates an mapping the results to classes of type and writes each record to the channel. + /// + /// The return type of the transform function. + /// The IDataReader to iterate. + /// The target channel to receive the results. + /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// An optional override map of field names to column names. + /// An optional cancellation token. + /// The number of records processed. + public static ValueTask ToChannel(this IDataReader reader, + ChannelWriter target, + bool complete, + IEnumerable<(string Field, string? Column)> fieldMappingOverrides, + CancellationToken cancellationToken = default) + where T : new() + => Transformer + .Create(fieldMappingOverrides) + .PipeResultsTo(reader, target, complete, cancellationToken); + + /// + /// Iterates an and writes each record as an array to the channel. + /// If a connection is desired to remain open after completion, you must open the connection before calling this method. + /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// + /// The DbCommand to generate a reader from. + /// The target channel to receive the results. + /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// An optional cancellation token. + /// The number of records processed. + public static ValueTask ToChannel(this IDbCommand command, + ChannelWriter target, + bool complete, + CancellationToken cancellationToken = default) + => ToChannelCore( + command, target, complete, cancellationToken, + reader => ToChannel(reader, target, false, cancellationToken)); + + /// + /// Iterates an and writes each record as an array to the channel. + /// If a connection is desired to remain open after completion, you must open the connection before calling this method. + /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// + /// The DbCommand to generate a reader from. + /// The target channel to receive the results. + /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// The array pool to acquire buffers from. + /// An optional cancellation token. + /// The number of records processed. + public static ValueTask ToChannel(this IDbCommand command, + ChannelWriter target, + bool complete, + ArrayPool arrayPool, + CancellationToken cancellationToken = default) + => ToChannelCore( + command, target, complete, cancellationToken, + reader => ToChannel(reader, target, false, arrayPool, cancellationToken)); + + /// + /// Iterates an through the transform function and writes each record to the channel. + /// If a connection is desired to remain open after completion, you must open the connection before calling this method. + /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// + /// The return type of the transform function. + /// The DbCommand to generate a reader from. + /// The target channel to receive the results. + /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// The transform function for each IDataRecord. + /// An optional cancellation token. + /// The number of records processed. + public static async ValueTask ToChannel(this IDbCommand command, + ChannelWriter target, + bool complete, + Func transform, + CancellationToken cancellationToken = default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (target is null) throw new ArgumentNullException(nameof(target)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + if (command.Connection is null) throw new InvalidOperationException("Command has no connection."); + Contract.EndContractBlock(); + + if (!command.Connection.State.HasFlag(ConnectionState.Open)) + await target.WaitToWriteAndThrowIfClosedAsync(true, cancellationToken).ConfigureAwait(false); + + Exception? exception = null; + try + { + ConnectionState state = command.Connection.EnsureOpen(); + CommandBehavior behavior = CommandBehavior.SingleResult; + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; + using IDataReader reader = await command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(false); + return await reader.ToChannel(target, false, transform, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + exception = ex; + throw; + } + finally + { + if (complete) + target.TryComplete(exception); + } + } + + /// + /// Iterates an mapping the results to classes of type and writes each record to the channel. + /// If a connection is desired to remain open after completion, you must open the connection before calling this method. + /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// + /// The return type of the transform function. + /// The DbCommand to generate a reader from. + /// The target channel to receive the results. + /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// An optional cancellation token. + /// The number of records processed. + public static ValueTask ToChannel(this IDbCommand command, + ChannelWriter target, + bool complete, + CancellationToken cancellationToken = default) + where T : new() + => ToChannelCore( + command, target, complete, cancellationToken, + reader => ToChannel(reader, target, false, cancellationToken)); + + /// + /// Iterates an mapping the results to classes of type and writes each record to the channel. + /// If a connection is desired to remain open after completion, you must open the connection before calling this method. + /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// + /// The return type of the transform function. + /// The DbCommand to generate a reader from. + /// The target channel to receive the results. + /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// An optional override map of field names to column names. + /// An optional cancellation token. + /// The number of records processed. + public static ValueTask ToChannel(this IDbCommand command, + ChannelWriter target, + bool complete, + IEnumerable<(string Field, string? Column)> fieldMappingOverrides, + CancellationToken cancellationToken = default) + where T : new() + => ToChannelCore( + command, target, complete, cancellationToken, + reader => ToChannel(reader, target, false, fieldMappingOverrides, cancellationToken)); + + /// + /// Iterates an and writes each record as an array to the channel. + /// If a connection is desired to remain open after completion, you must open the connection before calling this method. + /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// + /// The command to generate a reader from. + /// The target channel writer to receive the results. + /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// The number of records processed. + public static ValueTask ToChannel(this IExecuteReader command, + ChannelWriter target, + bool complete) + => ToChannelCore( + command, target, complete, + reader => reader.ToChannel(target, false, command.CancellationToken)); + + /// + /// Iterates an and writes each record as an array to the channel. + /// If a connection is desired to remain open after completion, you must open the connection before calling this method. + /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// + /// The command to generate a reader from. + /// The target channel writer to receive the results. + /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// The array pool to acquire buffers from. + /// The number of records processed. + public static ValueTask ToChannel(this IExecuteReader command, + ChannelWriter target, + bool complete, + ArrayPool arrayPool) + => ToChannelCore( + command, target, complete, + reader => reader.ToChannel(target, false, arrayPool, command.CancellationToken)); + + /// + /// Iterates an and through the transform function and posts each record it to the target channel. + /// If a connection is desired to remain open after completion, you must open the connection before calling this method. + /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// + /// The return type of the transform function. + /// The command to generate a reader from. + /// The target channel writer to receive the results. + /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// The transform function for each IDataRecord. + /// The number of records processed. + public static ValueTask ToChannel(this IExecuteReader command, + ChannelWriter target, + bool complete, + Func transform) + => ToChannelCore( + command, target, complete, + reader => reader.ToChannel(target, false, transform, command.CancellationToken)); + + /// + /// Iterates an mapping the results to classes of type and writes each record to the channel. + /// If a connection is desired to remain open after completion, you must open the connection before calling this method. + /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// + /// The return type of the transform function. + /// The command to generate a reader from. + /// The target channel writer to receive the results. + /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// The number of records processed. + public static ValueTask ToChannel(this IExecuteReader command, + ChannelWriter target, + bool complete) + where T : new() + => ToChannelCore( + command, target, complete, + reader => reader.ToChannel(target, false, command.CancellationToken)); + + /// + /// Iterates an mapping the results to classes of type and writes each record to the channel. + /// If a connection is desired to remain open after completion, you must open the connection before calling this method. + /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// + /// The return type of the transform function. + /// The command to generate a reader from. + /// The target channel writer to receive the results. + /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// An optional override map of field names to column names. + /// The number of records processed. + public static ValueTask ToChannel(this IExecuteReader command, + ChannelWriter target, + bool complete, + IEnumerable<(string Field, string? Column)> fieldMappingOverrides) + where T : new() + => ToChannelCore( + command, target, complete, + reader => reader.ToChannel(target, false, fieldMappingOverrides, command.CancellationToken)); + +#if NETSTANDARD2_0 +#else + + private static async ValueTask ToChannelAsyncCore( + DbCommand command, + ChannelWriter writer, + bool complete, + CancellationToken cancellationToken, + Func> transform) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (writer is null) throw new ArgumentNullException(nameof(writer)); + if (command.Connection is null) throw new InvalidOperationException("Command has no connection."); + + if (!command.Connection.State.HasFlag(ConnectionState.Open)) + { + await writer + .WaitToWriteAndThrowIfClosedAsync(true, cancellationToken) + .ConfigureAwait(false); + } + + if (!complete) + { + await command + .ExecuteReaderAsync(transform, cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + Exception? exception = null; + try + { + return await command + .ExecuteReaderAsync(transform, cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + catch (Exception ex) + { + exception = ex; + throw; + } + finally + { + writer.TryComplete(exception); + } + } + + private static async ValueTask ToChannelAsyncCore( + IExecuteReaderAsync command, + ChannelWriter writer, + bool complete, + Func> transform) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (writer is null) throw new ArgumentNullException(nameof(writer)); + Contract.EndContractBlock(); + + await writer + .WaitToWriteAndThrowIfClosedAsync(true, command.CancellationToken) + .ConfigureAwait(false); + + if (!complete) + { + await command + .ExecuteReaderAsync(transform) + .ConfigureAwait(false); + } + + Exception? exception = null; + try + { + return await command + .ExecuteReaderAsync(transform) + .ConfigureAwait(false); + } + catch (Exception ex) + { + exception = ex; + throw; + } + finally + { + if (complete) + writer.TryComplete(exception); + } + } + + /// + /// Asynchronously iterates an DbDataReader and writes each record as an array to the channel. + /// + /// The IDataReader to iterate. + /// The target channel to receive the results. + /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// An optional cancellation token. + public static ValueTask ToChannelAsync(this DbDataReader reader, + ChannelWriter target, + bool complete, + CancellationToken cancellationToken = default) + => target.WriteAllAsync( + reader.AsAsyncEnumerable(cancellationToken), + complete, + false, + cancellationToken); + + /// + /// Asynchronously iterates an DbDataReader and writes each record as an array to the channel. + /// + /// The IDataReader to iterate. + /// The target channel to receive the results. + /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// The array pool to acquire buffers from. + /// An optional cancellation token. + public static ValueTask ToChannelAsync(this DbDataReader reader, + ChannelWriter target, + bool complete, + ArrayPool? arrayPool, + CancellationToken cancellationToken = default) + => target.WriteAllAsync( + reader.AsAsyncEnumerable(arrayPool, cancellationToken), + complete, + false, + cancellationToken); + + /// + /// Asynchronously iterates an DbDataReader through the transform function and writes each record to the channel. + /// + /// The return type of the transform function. + /// The IDataReader to iterate. + /// The target channel to receive the results. + /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// The transform function for each IDataRecord. + /// An optional cancellation token. + public static ValueTask ToChannelAsync(this DbDataReader reader, + ChannelWriter target, + bool complete, + Func transform, + CancellationToken cancellationToken = default) + { + if (reader is null) throw new ArgumentNullException(nameof(reader)); + if (target is null) throw new ArgumentNullException(nameof(target)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + return target.WriteAllAsync( + reader.SelectAsync(transform, cancellationToken), + complete, + false, + cancellationToken); + } + + /// + /// Asynchronously iterates an mapping the results to classes of type and writes each record to the channel. + /// + /// The return type of the transform function. + /// The IDataReader to iterate. + /// The target channel to receive the results. + /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// An optional cancellation token. + public static ValueTask ToChannelAsync(this DbDataReader reader, + ChannelWriter target, + bool complete, + CancellationToken cancellationToken = default) + where T : new() + => Transformer + .Create() + .PipeResultsToAsync(reader, target, complete, cancellationToken); + + /// + /// Asynchronously iterates an mapping the results to classes of type and writes each record to the channel. + /// + /// The return type of the transform function. + /// The IDataReader to iterate. + /// The target channel to receive the results. + /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// An optional override map of field names to column names. + /// An optional cancellation token. + public static ValueTask ToChannelAsync(this DbDataReader reader, + ChannelWriter target, + bool complete, + IEnumerable<(string Field, string? Column)> fieldMappingOverrides, + CancellationToken cancellationToken = default) + where T : new() + => Transformer + .Create(fieldMappingOverrides) + .PipeResultsToAsync(reader, target, complete, cancellationToken); + + /// + /// Asynchronously iterates an DbDataReader and writes each record as an array to the channel. + /// If a connection is desired to remain open after completion, you must open the connection before calling this method. + /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// + /// The DbCommand to generate a reader from. + /// The target channel to receive the results. + /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// An optional cancellation token. + /// The number of records processed. + public static ValueTask ToChannelAsync(this DbCommand command, + ChannelWriter target, + bool complete, + CancellationToken cancellationToken = default) + => ToChannelAsyncCore( + command, target, complete, cancellationToken, + reader => ToChannelAsync(reader, target, false, cancellationToken)); + + /// + /// Asynchronously iterates an DbDataReader and writes each record as an array to the channel. + /// If a connection is desired to remain open after completion, you must open the connection before calling this method. + /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// + /// The DbCommand to generate a reader from. + /// The target channel to receive the results. + /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// The array pool to acquire buffers from. + /// An optional cancellation token. + /// The number of records processed. + public static ValueTask ToChannelAsync(this DbCommand command, + ChannelWriter target, + bool complete, + ArrayPool arrayPool, + CancellationToken cancellationToken = default) + => ToChannelAsyncCore( + command, target, complete, cancellationToken, + reader => ToChannelAsync(reader, target, false, arrayPool, cancellationToken)); + + /// + /// Asynchronously iterates an DbDataReader through the transform function and writes each record to the channel. + /// If a connection is desired to remain open after completion, you must open the connection before calling this method. + /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// + /// The return type of the transform function. + /// The DbCommand to generate a reader from. + /// The target channel to receive the results. + /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// The transform function for each IDataRecord. + /// An optional cancellation token. + /// The number of records processed. + public static ValueTask ToChannelAsync(this DbCommand command, + ChannelWriter target, + bool complete, + Func transform, + CancellationToken cancellationToken = default) + { + if (transform is null) throw new ArgumentNullException(nameof(transform)); + return ToChannelAsyncCore( + command, target, complete, cancellationToken, + reader => ToChannelAsync(reader, target, false, transform, cancellationToken)); + } + + /// + /// Asynchronously iterates an mapping the results to classes of type and writes each record to the channel. + /// If a connection is desired to remain open after completion, you must open the connection before calling this method. + /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// + /// The return type of the transform function. + /// The DbCommand to generate a reader from. + /// The target channel to receive the results. + /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// An optional cancellation token. + /// The number of records processed. + public static ValueTask ToChannelAsync(this DbCommand command, + ChannelWriter target, + bool complete, + CancellationToken cancellationToken = default) + where T : new() + => ToChannelAsyncCore( + command, target, complete, cancellationToken, + reader => ToChannelAsync(reader, target, false, cancellationToken)); + + /// + /// Asynchronously iterates an mapping the results to classes of type and writes each record to the channel. + /// If a connection is desired to remain open after completion, you must open the connection before calling this method. + /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// + /// The return type of the transform function. + /// The DbCommand to generate a reader from. + /// The target channel to receive the results. + /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// An optional override map of field names to column names. + /// An optional cancellation token. + /// The number of records processed. + public static ValueTask ToChannelAsync(this DbCommand command, + ChannelWriter target, + bool complete, + IEnumerable<(string Field, string? Column)> fieldMappingOverrides, + CancellationToken cancellationToken = default) + where T : new() + => ToChannelAsyncCore( + command, target, complete, cancellationToken, + reader => ToChannelAsync(reader, target, false, fieldMappingOverrides, cancellationToken)); + + /// + /// Asynchronously iterates an DbDataReader and writes each record as an array to the channel. + /// If a connection is desired to remain open after completion, you must open the connection before calling this method. + /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// + /// The command to generate a reader from. + /// The target channel writer to receive the results. + /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// The number of records processed. + public static ValueTask ToChannelAsync(this IExecuteReaderAsync command, + ChannelWriter target, + bool complete) + => ToChannelAsyncCore( + command, target, complete, + reader => command.UseAsyncRead && reader is DbDataReader r + ? r.ToChannelAsync(target, false, command.CancellationToken) + : reader.ToChannel(target, false, command.CancellationToken)); + + /// + /// Asynchronously iterates an DbDataReader and writes each record as an array to the channel. + /// If a connection is desired to remain open after completion, you must open the connection before calling this method. + /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// + /// The command to generate a reader from. + /// The target channel writer to receive the results. + /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// The array pool to acquire buffers from. + /// The number of records processed. + public static ValueTask ToChannelAsync(this IExecuteReaderAsync command, + ChannelWriter target, + bool complete, + ArrayPool? arrayPool) + => ToChannelAsyncCore( + command, target, complete, + reader => command.UseAsyncRead && reader is DbDataReader r + ? r.ToChannelAsync(target, false, arrayPool, command.CancellationToken) + : reader.ToChannel(target, false, arrayPool, command.CancellationToken)); + + /// + /// Asynchronously iterates an DbDataReader through the transform function and writes each record to the channel. + /// If a connection is desired to remain open after completion, you must open the connection before calling this method. + /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// + /// The return type of the transform function. + /// The command to generate a reader from. + /// The target channel writer to receive the results. + /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// The transform function for each IDataRecord. + /// The number of records processed. + public static ValueTask ToChannelAsync(this IExecuteReaderAsync command, + ChannelWriter target, + bool complete, + Func transform) + => ToChannelAsyncCore( + command, target, complete, + reader => command.UseAsyncRead && reader is DbDataReader r + ? r.ToChannelAsync(target, false, transform, command.CancellationToken) + : reader.ToChannel(target, false, transform, command.CancellationToken)); + + /// + /// Asynchronously iterates an DbDataReader through the transform function and writes each record to the channel. + /// If a connection is desired to remain open after completion, you must open the connection before calling this method. + /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// + /// The return type of the transform function. + /// The command to generate a reader from. + /// The target channel writer to receive the results. + /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// The number of records processed. + public static ValueTask ToChannelAsync(this IExecuteReaderAsync command, + ChannelWriter target, + bool complete) + where T : new() + => ToChannelAsyncCore( + command, target, complete, + reader => command.UseAsyncRead && reader is DbDataReader r + ? r.ToChannelAsync(target, false, command.CancellationToken) + : reader.ToChannel(target, false, command.CancellationToken)); + + /// + /// Asynchronously iterates an DbDataReader through the transform function and writes each record to the channel. + /// If a connection is desired to remain open after completion, you must open the connection before calling this method. + /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// + /// The return type of the transform function. + /// The command to generate a reader from. + /// The target channel writer to receive the results. + /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// An optional override map of field names to column names. + /// The number of records processed. + public static ValueTask ToChannelAsync(this IExecuteReaderAsync command, + ChannelWriter target, + bool complete, + IEnumerable<(string Field, string? Column)> fieldMappingOverrides) + where T : new() + => ToChannelAsyncCore( + command, target, complete, + reader => command.UseAsyncRead && reader is DbDataReader r + ? r.ToChannelAsync(target, false, fieldMappingOverrides, command.CancellationToken) + : reader.ToChannel(target, false, fieldMappingOverrides, command.CancellationToken)); +#endif + +} diff --git a/Source/Channel/ToChannel.cs b/Source/Channel/ToChannel.cs index b475545..ceb71ef 100644 --- a/Source/Channel/ToChannel.cs +++ b/Source/Channel/ToChannel.cs @@ -1,1137 +1,654 @@ -using Open.ChannelExtensions; -using System; -using System.Buffers; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; -using System.Diagnostics.Contracts; -using System.Threading; -using System.Threading.Channels; -using System.Threading.Tasks; +namespace Open.Database.Extensions; -namespace Open.Database.Extensions; - -/// -/// Extensions for writing data to a channel. -/// +[SuppressMessage("Reliability", "CA2012:Use ValueTasks correctly", Justification = "Intentionally running in the background.")] +[SuppressMessage("Roslynator", "RCS1047:Non-asynchronous method name should not end with 'Async'.", Justification = "")] public static partial class ChannelDbExtensions { /// - /// Iterates an and writes each record as an array to the channel. + /// Iterates an and writes each record as an array to an unbound channel. + /// Be sure to await the completion. /// /// The IDataReader to iterate. - /// The target channel to receive the results. - /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. /// An optional cancellation token. - /// The number of records processed. - public static ValueTask ToChannel(this IDataReader reader, - ChannelWriter target, - bool complete, + /// The channel reader containing the results. + public static ChannelReader ToChannel(this IDataReader reader, + bool singleReader, CancellationToken cancellationToken = default) { if (reader is null) throw new ArgumentNullException(nameof(reader)); - if (target is null) throw new ArgumentNullException(nameof(target)); Contract.EndContractBlock(); - return target.WriteAll( - reader.AsEnumerable(), - complete, - false, - cancellationToken); + Channel channel = CreateChannel(-1, singleReader); + _ = ToChannel(reader, channel.Writer, true, cancellationToken); + return channel.Reader; } /// - /// Iterates an and writes each record as an array to the channel. + /// Iterates an and writes each record as an array to an unbound channel. + /// Be sure to await the completion. /// /// The IDataReader to iterate. - /// The target channel to receive the results. - /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. /// The array pool to acquire buffers from. /// An optional cancellation token. - /// The number of records processed. - public static ValueTask ToChannel(this IDataReader reader, - ChannelWriter target, - bool complete, - ArrayPool arrayPool, + /// The channel reader containing the results. + public static ChannelReader ToChannel(this IDataReader reader, + bool singleReader, + ArrayPool? arrayPool, CancellationToken cancellationToken = default) { if (reader is null) throw new ArgumentNullException(nameof(reader)); - if (target is null) throw new ArgumentNullException(nameof(target)); Contract.EndContractBlock(); - return target.WriteAll( - reader.AsEnumerable(arrayPool), - complete, - false, - cancellationToken); + Channel channel = CreateChannel(-1, singleReader); + _ = ToChannel(reader, channel.Writer, true, arrayPool, cancellationToken); + return channel.Reader; } /// - /// Iterates an through the transform function and writes each record to the channel. + /// Iterates an through the transform function and writes each record to an unbound channel. + /// Be sure to await the completion. /// /// The return type of the transform function. /// The IDataReader to iterate. - /// The target channel to receive the results. - /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. /// The transform function for each IDataRecord. /// An optional cancellation token. - /// The number of records processed. - public static ValueTask ToChannel(this IDataReader reader, - ChannelWriter target, - bool complete, + /// The channel reader containing the results. + public static ChannelReader ToChannel(this IDataReader reader, + bool singleReader, Func transform, CancellationToken cancellationToken = default) { if (reader is null) throw new ArgumentNullException(nameof(reader)); - if (target is null) throw new ArgumentNullException(nameof(target)); if (transform is null) throw new ArgumentNullException(nameof(transform)); Contract.EndContractBlock(); - return target.WriteAll( - reader.Select(transform, cancellationToken), - complete, - false, - cancellationToken); + Channel channel = CreateChannel(-1, singleReader); + _ = ToChannel(reader, channel.Writer, true, transform, cancellationToken); + return channel.Reader; } /// - /// Iterates an mapping the results to classes of type and writes each record to the channel. + /// Iterates an mapping the results to classes of type and writes each record an unbound channel. /// /// The return type of the transform function. /// The IDataReader to iterate. - /// The target channel to receive the results. - /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. /// An optional cancellation token. - /// The number of records processed. - public static ValueTask ToChannel(this IDataReader reader, - ChannelWriter target, - bool complete, + /// The channel reader containing the results. + public static ChannelReader ToChannel(this IDataReader reader, + bool singleReader, CancellationToken cancellationToken = default) where T : new() - => Transformer - .Create() - .PipeResultsTo(reader, target, complete, cancellationToken); + { + if (reader is null) throw new ArgumentNullException(nameof(reader)); + Contract.EndContractBlock(); + + Channel channel = CreateChannel(-1, singleReader); + _ = ToChannel(reader, channel.Writer, true, cancellationToken); + return channel.Reader; + } /// - /// Iterates an mapping the results to classes of type and writes each record to the channel. + /// Iterates an mapping the results to classes of type and writes each record an unbound channel. /// /// The return type of the transform function. /// The IDataReader to iterate. - /// The target channel to receive the results. - /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. /// An optional override map of field names to column names. /// An optional cancellation token. - /// The number of records processed. - public static ValueTask ToChannel(this IDataReader reader, - ChannelWriter target, - bool complete, + /// The channel reader containing the results. + public static ChannelReader ToChannel(this IDataReader reader, + bool singleReader, IEnumerable<(string Field, string? Column)> fieldMappingOverrides, CancellationToken cancellationToken = default) where T : new() - => Transformer - .Create(fieldMappingOverrides) - .PipeResultsTo(reader, target, complete, cancellationToken); + { + if (reader is null) throw new ArgumentNullException(nameof(reader)); + Contract.EndContractBlock(); + + Channel channel = CreateChannel(-1, singleReader); + _ = ToChannel(reader, channel.Writer, true, fieldMappingOverrides, cancellationToken); + return channel.Reader; + } /// - /// Iterates an and writes each record as an array to the channel. - /// If a connection is desired to remain open after completion, you must open the connection before calling this method. - /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// Iterates an and writes each record as an array to an unbound channel. + /// Be sure to await the completion. /// - /// The DbCommand to generate a reader from. - /// The target channel to receive the results. - /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// The command to acquire a reader from to iterate. + /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. /// An optional cancellation token. - /// The number of records processed. - public static async ValueTask ToChannel(this IDbCommand command, - ChannelWriter target, - bool complete, + /// The channel reader containing the results. + public static ChannelReader ToChannel(this IDbCommand command, + bool singleReader, CancellationToken cancellationToken = default) { if (command is null) throw new ArgumentNullException(nameof(command)); - if (target is null) throw new ArgumentNullException(nameof(target)); Contract.EndContractBlock(); - if (!command.Connection.State.HasFlag(ConnectionState.Open)) - await target.WaitToWriteAndThrowIfClosedAsync(true, cancellationToken).ConfigureAwait(false); - - try - { - return await command.ExecuteReader(reader => - ToChannel(reader, target, false, cancellationToken)).ConfigureAwait(false); - } - catch (Exception ex) - { - if (complete) - { - complete = false; - target.Complete(ex); - } - throw; - } - finally - { - if (complete) - target.Complete(); - } + Channel channel = CreateChannel(-1, singleReader); + _ = ToChannel(command, channel.Writer, true, cancellationToken); + return channel.Reader; } /// - /// Iterates an and writes each record as an array to the channel. - /// If a connection is desired to remain open after completion, you must open the connection before calling this method. - /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// Iterates an and writes each record as an array to an unbound channel. + /// Be sure to await the completion. /// - /// The DbCommand to generate a reader from. - /// The target channel to receive the results. - /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// The command to acquire a reader from to iterate. + /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. /// The array pool to acquire buffers from. /// An optional cancellation token. - /// The number of records processed. - public static async ValueTask ToChannel(this IDbCommand command, - ChannelWriter target, - bool complete, - ArrayPool arrayPool, + /// The channel reader containing the results. + public static ChannelReader ToChannel(this IDbCommand command, + bool singleReader, + ArrayPool arrayPool, CancellationToken cancellationToken = default) { if (command is null) throw new ArgumentNullException(nameof(command)); - if (target is null) throw new ArgumentNullException(nameof(target)); + if (arrayPool is null) throw new ArgumentNullException(nameof(arrayPool)); Contract.EndContractBlock(); - if (!command.Connection.State.HasFlag(ConnectionState.Open)) - await target.WaitToWriteAndThrowIfClosedAsync(true, cancellationToken).ConfigureAwait(false); - try - { - return await command.ExecuteReader(reader => - ToChannel(reader, target, false, arrayPool, cancellationToken)).ConfigureAwait(false); - } - catch (Exception ex) - { - if (complete) - { - complete = false; - target.Complete(ex); - } - throw; - } - finally - { - if (complete) - target.Complete(); - } + Channel channel = CreateChannel(-1, singleReader); + _ = ToChannel(command, channel.Writer, true, arrayPool, cancellationToken); + return channel.Reader; } /// - /// Iterates an through the transform function and writes each record to the channel. - /// If a connection is desired to remain open after completion, you must open the connection before calling this method. - /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// Iterates an through the transform function and writes each record to an unbound channel. + /// Be sure to await the completion. /// /// The return type of the transform function. - /// The DbCommand to generate a reader from. - /// The target channel to receive the results. - /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// The command to acquire a reader from to iterate. + /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. /// The transform function for each IDataRecord. /// An optional cancellation token. - /// The number of records processed. - public static async ValueTask ToChannel(this IDbCommand command, - ChannelWriter target, - bool complete, + /// The channel reader containing the results. + public static ChannelReader ToChannel(this IDbCommand command, + bool singleReader, Func transform, CancellationToken cancellationToken = default) { if (command is null) throw new ArgumentNullException(nameof(command)); - if (target is null) throw new ArgumentNullException(nameof(target)); if (transform is null) throw new ArgumentNullException(nameof(transform)); Contract.EndContractBlock(); - if (!command.Connection.State.HasFlag(ConnectionState.Open)) - await target.WaitToWriteAndThrowIfClosedAsync(true, cancellationToken).ConfigureAwait(false); - - try - { - var state = command.Connection.EnsureOpen(); - var behavior = CommandBehavior.SingleResult; - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var reader = command.ExecuteReader(behavior); - return await reader.ToChannel(target, false, transform, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - if (complete) - { - complete = false; - target.Complete(ex); - } - throw; - } - finally - { - if (complete) - target.Complete(); - } + Channel channel = CreateChannel(-1, singleReader); + _ = ToChannel(command, channel.Writer, true, transform, cancellationToken); + return channel.Reader; } /// - /// Iterates an mapping the results to classes of type and writes each record to the channel. - /// If a connection is desired to remain open after completion, you must open the connection before calling this method. - /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// Iterates an mapping the results to classes of type and writes each record an unbound channel. /// /// The return type of the transform function. - /// The DbCommand to generate a reader from. - /// The target channel to receive the results. - /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// The command to acquire a reader from to iterate. + /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. /// An optional cancellation token. - /// The number of records processed. - public static async ValueTask ToChannel(this IDbCommand command, - ChannelWriter target, - bool complete, + /// The channel reader containing the results. + public static ChannelReader ToChannel(this IDbCommand command, + bool singleReader, CancellationToken cancellationToken = default) where T : new() { if (command is null) throw new ArgumentNullException(nameof(command)); - if (target is null) throw new ArgumentNullException(nameof(target)); Contract.EndContractBlock(); - if (!command.Connection.State.HasFlag(ConnectionState.Open)) - await target.WaitToWriteAndThrowIfClosedAsync(true, cancellationToken).ConfigureAwait(false); - - try - { - return await command.ExecuteReader(reader => - ToChannel(reader, target, false, cancellationToken)).ConfigureAwait(false); - } - catch (Exception ex) - { - if (complete) - { - complete = false; - target.Complete(ex); - } - throw; - } - finally - { - if (complete) - target.Complete(); - } + Channel channel = CreateChannel(-1, singleReader); + _ = ToChannel(command, channel.Writer, true, cancellationToken); + return channel.Reader; } /// - /// Iterates an mapping the results to classes of type and writes each record to the channel. - /// If a connection is desired to remain open after completion, you must open the connection before calling this method. - /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// Iterates an mapping the results to classes of type and writes each record an unbound channel. /// /// The return type of the transform function. - /// The DbCommand to generate a reader from. - /// The target channel to receive the results. - /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// The command to acquire a reader from to iterate. + /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. /// An optional override map of field names to column names. /// An optional cancellation token. - /// The number of records processed. - public static async ValueTask ToChannel(this IDbCommand command, - ChannelWriter target, - bool complete, + /// The channel reader containing the results. + public static ChannelReader ToChannel(this IDbCommand command, + bool singleReader, IEnumerable<(string Field, string? Column)> fieldMappingOverrides, CancellationToken cancellationToken = default) where T : new() { if (command is null) throw new ArgumentNullException(nameof(command)); - if (target is null) throw new ArgumentNullException(nameof(target)); Contract.EndContractBlock(); - if (!command.Connection.State.HasFlag(ConnectionState.Open)) - await target.WaitToWriteAndThrowIfClosedAsync(true, cancellationToken).ConfigureAwait(false); - - try - { - return await command.ExecuteReader(reader => - ToChannel(reader, target, false, fieldMappingOverrides, cancellationToken)).ConfigureAwait(false); - } - catch (Exception ex) - { - if (complete) - { - complete = false; - target.Complete(ex); - } - throw; - } - finally - { - if (complete) - target.Complete(); - } + Channel channel = CreateChannel(-1, singleReader); + _ = ToChannel(command, channel.Writer, true, fieldMappingOverrides, cancellationToken); + return channel.Reader; } /// - /// Iterates an and writes each record as an array to the channel. - /// If a connection is desired to remain open after completion, you must open the connection before calling this method. - /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// Iterates an and writes each record as an array to an unbound channel. + /// Be sure to await the completion. /// - /// The command to generate a reader from. - /// The target channel writer to receive the results. - /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). - /// The number of records processed. - public static async ValueTask ToChannel(this IExecuteReader command, - ChannelWriter target, - bool complete) + /// The IDataReader to iterate. + /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. + /// The channel reader containing the results. + public static ChannelReader ToChannel(this IExecuteReader command, + bool singleReader) { if (command is null) throw new ArgumentNullException(nameof(command)); - if (target is null) throw new ArgumentNullException(nameof(target)); Contract.EndContractBlock(); - var cancellationToken = command.CancellationToken; - await target.WaitToWriteAndThrowIfClosedAsync(true, cancellationToken).ConfigureAwait(false); - try - { - return await command.ExecuteReaderAsync( // Must be ExecuteReaderAsync to await the to channel completion. - reader => reader.ToChannel(target, false, cancellationToken)).ConfigureAwait(false); - } - catch (Exception ex) - { - if (complete) - { - complete = false; - target.Complete(ex); - } - throw; - } - finally - { - if (complete) - target.Complete(); - } + Channel channel = CreateChannel(-1, singleReader); + _ = ToChannel(command, channel.Writer, true); + return channel.Reader; } /// - /// Iterates an and writes each record as an array to the channel. - /// If a connection is desired to remain open after completion, you must open the connection before calling this method. - /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// Iterates an and writes each record as an array to an unbound channel. + /// Be sure to await the completion. /// - /// The command to generate a reader from. - /// The target channel writer to receive the results. - /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// The IDataReader to iterate. + /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. /// The array pool to acquire buffers from. - /// The number of records processed. - public static async ValueTask ToChannel(this IExecuteReader command, - ChannelWriter target, - bool complete, - ArrayPool arrayPool) + /// The channel reader containing the results. + public static ChannelReader ToChannel(this IExecuteReader command, + bool singleReader, + ArrayPool arrayPool) { if (command is null) throw new ArgumentNullException(nameof(command)); - if (target is null) throw new ArgumentNullException(nameof(target)); + if (arrayPool is null) throw new ArgumentNullException(nameof(arrayPool)); Contract.EndContractBlock(); - var cancellationToken = command.CancellationToken; - await target.WaitToWriteAndThrowIfClosedAsync(true, cancellationToken).ConfigureAwait(false); - try - { - return await command.ExecuteReaderAsync( // Must be ExecuteReaderAsync to await the to channel completion. - reader => reader.ToChannel(target, false, arrayPool, cancellationToken)).ConfigureAwait(false); - } - catch (Exception ex) - { - if (complete) - { - complete = false; - target.Complete(ex); - } - throw; - } - finally - { - if (complete) - target.Complete(); - } + Channel channel = CreateChannel(-1, singleReader); + _ = ToChannel(command, channel.Writer, true, arrayPool); + return channel.Reader; } /// - /// Iterates an and through the transform function and posts each record it to the target channel. - /// If a connection is desired to remain open after completion, you must open the connection before calling this method. - /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// Iterates an through the transform function and writes each record to an unbound channel. + /// Be sure to await the completion. /// /// The return type of the transform function. - /// The command to generate a reader from. - /// The target channel writer to receive the results. - /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// The IDataReader to iterate. + /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. /// The transform function for each IDataRecord. - /// The number of records processed. - public static async ValueTask ToChannel(this IExecuteReader command, - ChannelWriter target, - bool complete, + /// The channel reader containing the results. + public static ChannelReader ToChannel(this IExecuteReader command, + bool singleReader, Func transform) { if (command is null) throw new ArgumentNullException(nameof(command)); - if (target is null) throw new ArgumentNullException(nameof(target)); if (transform is null) throw new ArgumentNullException(nameof(transform)); Contract.EndContractBlock(); - var cancellationToken = command.CancellationToken; - await target.WaitToWriteAndThrowIfClosedAsync(true, cancellationToken).ConfigureAwait(false); - try - { - return await command.ExecuteReaderAsync( // Must be ExecuteReaderAsync to await the to channel completion. - reader => reader.ToChannel(target, false, transform, cancellationToken)).ConfigureAwait(false); - } - catch (Exception ex) - { - if (complete) - { - complete = false; - target.Complete(ex); - } - throw; - } - finally - { - if (complete) - target.Complete(); - } + Channel channel = CreateChannel(-1, singleReader); + _ = ToChannel(command, channel.Writer, true, transform); + return channel.Reader; } /// - /// Iterates an mapping the results to classes of type and writes each record to the channel. - /// If a connection is desired to remain open after completion, you must open the connection before calling this method. - /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// Iterates an mapping the results to classes of type and writes each record an unbound channel. /// /// The return type of the transform function. - /// The command to generate a reader from. - /// The target channel writer to receive the results. - /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). - /// The number of records processed. - public static async ValueTask ToChannel(this IExecuteReader command, - ChannelWriter target, - bool complete) + /// The IDataReader to iterate. + /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. + /// The channel reader containing the results. + public static ChannelReader ToChannel(this IExecuteReader command, + bool singleReader) where T : new() { if (command is null) throw new ArgumentNullException(nameof(command)); - if (target is null) throw new ArgumentNullException(nameof(target)); Contract.EndContractBlock(); - var cancellationToken = command.CancellationToken; - await target.WaitToWriteAndThrowIfClosedAsync(true, cancellationToken).ConfigureAwait(false); - try - { - return await command.ExecuteReaderAsync( // Must be ExecuteReaderAsync to await the to channel completion. - reader => reader.ToChannel(target, false, cancellationToken)).ConfigureAwait(false); - } - catch (Exception ex) - { - if (complete) - { - complete = false; - target.Complete(ex); - } - throw; - } - finally - { - if (complete) - target.Complete(); - } + Channel channel = CreateChannel(-1, singleReader); + _ = ToChannel(command, channel.Writer, true); + return channel.Reader; } /// - /// Iterates an mapping the results to classes of type and writes each record to the channel. - /// If a connection is desired to remain open after completion, you must open the connection before calling this method. - /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// Iterates an mapping the results to classes of type and writes each record an unbound channel. /// /// The return type of the transform function. - /// The command to generate a reader from. - /// The target channel writer to receive the results. - /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// The IDataReader to iterate. + /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. /// An optional override map of field names to column names. - /// The number of records processed. - public static async ValueTask ToChannel(this IExecuteReader command, - ChannelWriter target, - bool complete, + /// The channel reader containing the results. + public static ChannelReader ToChannel(this IExecuteReader command, + bool singleReader, IEnumerable<(string Field, string? Column)> fieldMappingOverrides) where T : new() { if (command is null) throw new ArgumentNullException(nameof(command)); - if (target is null) throw new ArgumentNullException(nameof(target)); Contract.EndContractBlock(); - var cancellationToken = command.CancellationToken; - await target.WaitToWriteAndThrowIfClosedAsync(true, cancellationToken).ConfigureAwait(false); - try - { - return await command.ExecuteReaderAsync( // Must be ExecuteReaderAsync to await the to channel completion. - reader => reader.ToChannel(target, false, fieldMappingOverrides, cancellationToken)).ConfigureAwait(false); - } - catch (Exception ex) - { - if (complete) - { - complete = false; - target.Complete(ex); - } - throw; - } - finally - { - if (complete) - target.Complete(); - } + Channel channel = CreateChannel(-1, singleReader); + _ = ToChannel(command, channel.Writer, true, fieldMappingOverrides); + return channel.Reader; } -#if NETSTANDARD2_1 +#if NETSTANDARD2_0 +#else /// - /// Asynchronously iterates an DbDataReader and writes each record as an array to the channel. + /// Asynchronously iterates an DbDataReader and writes each record as an array to an unbound channel. + /// Iterates an DbDataReader through the transform function and writes each record to an unbound channel. + /// Be sure to await the completion. /// /// The IDataReader to iterate. - /// The target channel to receive the results. - /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. /// An optional cancellation token. - public static ValueTask ToChannelAsync(this DbDataReader reader, - ChannelWriter target, - bool complete, + /// The channel reader containing the results. + public static ChannelReader ToChannelAsync(this DbDataReader reader, + bool singleReader, CancellationToken cancellationToken = default) { if (reader is null) throw new ArgumentNullException(nameof(reader)); - if (target is null) throw new ArgumentNullException(nameof(target)); Contract.EndContractBlock(); - return target.WriteAllAsync( - reader.AsAsyncEnumerable(cancellationToken), - complete, - false, - cancellationToken); + Channel channel = CreateChannel(-1, singleReader); + _ = ToChannelAsync(reader, channel.Writer, true, cancellationToken); + return channel.Reader; } /// - /// Asynchronously iterates an DbDataReader and writes each record as an array to the channel. + /// Asynchronously iterates an DbDataReader and writes each record as an array to an unbound channel. + /// Be sure to await the completion. /// /// The IDataReader to iterate. - /// The target channel to receive the results. - /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. /// The array pool to acquire buffers from. /// An optional cancellation token. - public static ValueTask ToChannelAsync(this DbDataReader reader, - ChannelWriter target, - bool complete, - ArrayPool arrayPool, + /// The channel reader containing the results. + public static ChannelReader ToChannelAsync(this DbDataReader reader, + bool singleReader, + ArrayPool arrayPool, CancellationToken cancellationToken = default) { if (reader is null) throw new ArgumentNullException(nameof(reader)); - if (target is null) throw new ArgumentNullException(nameof(target)); + if (arrayPool is null) throw new ArgumentNullException(nameof(arrayPool)); Contract.EndContractBlock(); - return target.WriteAllAsync( - reader.AsAsyncEnumerable(arrayPool, cancellationToken), - complete, - false, - cancellationToken); + Channel channel = CreateChannel(-1, singleReader); + _ = ToChannelAsync(reader, channel.Writer, true, arrayPool, cancellationToken); + return channel.Reader; } /// - /// Asynchronously iterates an DbDataReader through the transform function and writes each record to the channel. + /// Asynchronously iterates an DbDataReader through the transform function and writes each record to an unbound channel. + /// Be sure to await the completion. /// /// The return type of the transform function. /// The IDataReader to iterate. - /// The target channel to receive the results. - /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. /// The transform function for each IDataRecord. /// An optional cancellation token. - public static ValueTask ToChannelAsync(this DbDataReader reader, - ChannelWriter target, - bool complete, + /// The channel reader containing the results. + public static ChannelReader ToChannelAsync(this DbDataReader reader, + bool singleReader, Func transform, CancellationToken cancellationToken = default) { if (reader is null) throw new ArgumentNullException(nameof(reader)); - if (target is null) throw new ArgumentNullException(nameof(target)); if (transform is null) throw new ArgumentNullException(nameof(transform)); Contract.EndContractBlock(); - return target.WriteAllAsync( - reader.SelectAsync(transform, cancellationToken), - complete, - false, - cancellationToken); + Channel channel = CreateChannel(-1, singleReader); + _ = ToChannelAsync(reader, channel.Writer, true, transform, cancellationToken); + return channel.Reader; } /// - /// Asynchronously iterates an mapping the results to classes of type and writes each record to the channel. + /// Asynchronously iterates an DbDataReader mapping the results to classes of type and writes each record an unbound channel. /// /// The return type of the transform function. /// The IDataReader to iterate. - /// The target channel to receive the results. - /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. /// An optional cancellation token. - public static ValueTask ToChannelAsync(this DbDataReader reader, - ChannelWriter target, - bool complete, + /// The channel reader containing the results. + public static ChannelReader ToChannelAsync(this DbDataReader reader, + bool singleReader, CancellationToken cancellationToken = default) where T : new() - => Transformer - .Create() - .PipeResultsToAsync(reader, target, complete, cancellationToken); + { + if (reader is null) throw new ArgumentNullException(nameof(reader)); + Contract.EndContractBlock(); + + Channel channel = CreateChannel(-1, singleReader); + _ = ToChannelAsync(reader, channel.Writer, true, cancellationToken); + return channel.Reader; + } /// - /// Asynchronously iterates an mapping the results to classes of type and writes each record to the channel. + /// Asynchronously iterates an DbDataReader mapping the results to classes of type and writes each record an unbound channel. /// /// The return type of the transform function. /// The IDataReader to iterate. - /// The target channel to receive the results. - /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. /// An optional override map of field names to column names. /// An optional cancellation token. - public static ValueTask ToChannelAsync(this DbDataReader reader, - ChannelWriter target, - bool complete, + /// The channel reader containing the results. + public static ChannelReader ToChannelAsync(this DbDataReader reader, + bool singleReader, IEnumerable<(string Field, string? Column)> fieldMappingOverrides, CancellationToken cancellationToken = default) where T : new() - => Transformer - .Create(fieldMappingOverrides) - .PipeResultsToAsync(reader, target, complete, cancellationToken); + { + if (reader is null) throw new ArgumentNullException(nameof(reader)); + Contract.EndContractBlock(); + + Channel channel = CreateChannel(-1, singleReader); + _ = ToChannelAsync(reader, channel.Writer, true, fieldMappingOverrides, cancellationToken); + return channel.Reader; + } /// - /// Asynchronously iterates an DbDataReader and writes each record as an array to the channel. - /// If a connection is desired to remain open after completion, you must open the connection before calling this method. - /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// Asynchronously iterates an DbDataReader and writes each record as an array to an unbound channel. + /// Be sure to await the completion. /// - /// The DbCommand to generate a reader from. - /// The target channel to receive the results. - /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// The command to acquire a reader from to iterate. + /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. /// An optional cancellation token. - /// The number of records processed. - public static async ValueTask ToChannelAsync(this DbCommand command, - ChannelWriter target, - bool complete, + /// The channel reader containing the results. + public static ChannelReader ToChannelAsync(this DbCommand command, + bool singleReader, CancellationToken cancellationToken = default) { if (command is null) throw new ArgumentNullException(nameof(command)); - if (target is null) throw new ArgumentNullException(nameof(target)); Contract.EndContractBlock(); - if (!command.Connection.State.HasFlag(ConnectionState.Open)) - await target.WaitToWriteAndThrowIfClosedAsync(true, cancellationToken).ConfigureAwait(false); - - try - { - return await command.ExecuteReaderAsync(reader => - ToChannelAsync(reader, target, false, cancellationToken), cancellationToken: cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - if (complete) - { - complete = false; - target.Complete(ex); - } - throw; - } - finally - { - if (complete) - target.Complete(); - } + Channel channel = CreateChannel(-1, singleReader); + _ = ToChannelAsync(command, channel.Writer, true, cancellationToken); + return channel.Reader; } /// - /// Asynchronously iterates an DbDataReader and writes each record as an array to the channel. - /// If a connection is desired to remain open after completion, you must open the connection before calling this method. - /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// Asynchronously iterates an DbDataReader and writes each record as an array to an unbound channel. + /// Be sure to await the completion. /// - /// The DbCommand to generate a reader from. - /// The target channel to receive the results. - /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// The command to acquire a reader from to iterate. + /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. /// The array pool to acquire buffers from. /// An optional cancellation token. - /// The number of records processed. - public static async ValueTask ToChannelAsync(this DbCommand command, - ChannelWriter target, - bool complete, - ArrayPool arrayPool, + /// The channel reader containing the results. + public static ChannelReader ToChannelAsync(this DbCommand command, + bool singleReader, + ArrayPool arrayPool, CancellationToken cancellationToken = default) { if (command is null) throw new ArgumentNullException(nameof(command)); - if (target is null) throw new ArgumentNullException(nameof(target)); + if (arrayPool is null) throw new ArgumentNullException(nameof(arrayPool)); Contract.EndContractBlock(); - if (!command.Connection.State.HasFlag(ConnectionState.Open)) - await target.WaitToWriteAndThrowIfClosedAsync(true, cancellationToken).ConfigureAwait(false); - - try - { - return await command.ExecuteReaderAsync(reader => - ToChannelAsync(reader, target, false, arrayPool, cancellationToken), cancellationToken: cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - if (complete) - { - complete = false; - target.Complete(ex); - } - throw; - } - finally - { - if (complete) - target.Complete(); - } + Channel channel = CreateChannel(-1, singleReader); + _ = ToChannelAsync(command, channel.Writer, true, arrayPool, cancellationToken); + return channel.Reader; } /// - /// Asynchronously iterates an DbDataReader through the transform function and writes each record to the channel. - /// If a connection is desired to remain open after completion, you must open the connection before calling this method. - /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// Asynchronously iterates an DbDataReader through the transform function and writes each record to an unbound channel. + /// Be sure to await the completion. /// /// The return type of the transform function. - /// The DbCommand to generate a reader from. - /// The target channel to receive the results. - /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// The command to acquire a reader from to iterate. + /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. /// The transform function for each IDataRecord. /// An optional cancellation token. - /// The number of records processed. - public static async ValueTask ToChannelAsync(this DbCommand command, - ChannelWriter target, - bool complete, + /// The channel reader containing the results. + public static ChannelReader ToChannelAsync(this DbCommand command, + bool singleReader, Func transform, CancellationToken cancellationToken = default) { if (command is null) throw new ArgumentNullException(nameof(command)); - if (target is null) throw new ArgumentNullException(nameof(target)); if (transform is null) throw new ArgumentNullException(nameof(transform)); Contract.EndContractBlock(); - if (!command.Connection.State.HasFlag(ConnectionState.Open)) - await target.WaitToWriteAndThrowIfClosedAsync(true, cancellationToken).ConfigureAwait(false); - - try - { - return await command.ExecuteReaderAsync(reader => - ToChannelAsync(reader, target, false, transform, cancellationToken), cancellationToken: cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - if (complete) - { - complete = false; - target.Complete(ex); - } - throw; - } - finally - { - if (complete) - target.Complete(); - } + Channel channel = CreateChannel(-1, singleReader); + _ = ToChannelAsync(command, channel.Writer, true, transform, cancellationToken); + return channel.Reader; } /// - /// Asynchronously iterates an mapping the results to classes of type and writes each record to the channel. - /// If a connection is desired to remain open after completion, you must open the connection before calling this method. - /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// Asynchronously iterates an DbDataReader mapping the results to classes of type and writes each record an unbound channel. /// /// The return type of the transform function. - /// The DbCommand to generate a reader from. - /// The target channel to receive the results. - /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// The command to acquire a reader from to iterate. + /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. /// An optional cancellation token. - /// The number of records processed. - public static async ValueTask ToChannelAsync(this DbCommand command, - ChannelWriter target, - bool complete, + /// The channel reader containing the results. + public static ChannelReader ToChannelAsync(this DbCommand command, + bool singleReader, CancellationToken cancellationToken = default) where T : new() { if (command is null) throw new ArgumentNullException(nameof(command)); - if (target is null) throw new ArgumentNullException(nameof(target)); Contract.EndContractBlock(); - if (!command.Connection.State.HasFlag(ConnectionState.Open)) - await target.WaitToWriteAndThrowIfClosedAsync(true, cancellationToken).ConfigureAwait(false); - - try - { - return await command.ExecuteReaderAsync(reader => - ToChannelAsync(reader, target, false, cancellationToken), cancellationToken: cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - if (complete) - { - complete = false; - target.Complete(ex); - } - throw; - } - finally - { - if (complete) - target.Complete(); - } + Channel channel = CreateChannel(-1, singleReader); + _ = ToChannelAsync(command, channel.Writer, true, cancellationToken); + return channel.Reader; } /// - /// Asynchronously iterates an mapping the results to classes of type and writes each record to the channel. - /// If a connection is desired to remain open after completion, you must open the connection before calling this method. - /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// Asynchronously iterates an DbDataReader mapping the results to classes of type and writes each record an unbound channel. /// /// The return type of the transform function. - /// The DbCommand to generate a reader from. - /// The target channel to receive the results. - /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// The command to acquire a reader from to iterate. + /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. /// An optional override map of field names to column names. /// An optional cancellation token. - /// The number of records processed. - public static async ValueTask ToChannelAsync(this DbCommand command, - ChannelWriter target, - bool complete, + /// The channel reader containing the results. + public static ChannelReader ToChannelAsync(this DbCommand command, + bool singleReader, IEnumerable<(string Field, string? Column)> fieldMappingOverrides, CancellationToken cancellationToken = default) where T : new() { if (command is null) throw new ArgumentNullException(nameof(command)); - if (target is null) throw new ArgumentNullException(nameof(target)); Contract.EndContractBlock(); - if (!command.Connection.State.HasFlag(ConnectionState.Open)) - await target.WaitToWriteAndThrowIfClosedAsync(true, cancellationToken).ConfigureAwait(false); - - try - { - return await command.ExecuteReaderAsync(reader => - ToChannelAsync(reader, target, false, fieldMappingOverrides, cancellationToken), cancellationToken: cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - if (complete) - { - complete = false; - target.Complete(ex); - } - throw; - } - finally - { - if (complete) - target.Complete(); - } + Channel channel = CreateChannel(-1, singleReader); + _ = ToChannelAsync(command, channel.Writer, true, fieldMappingOverrides, cancellationToken); + return channel.Reader; } /// - /// Asynchronously iterates an DbDataReader and writes each record as an array to the channel. - /// If a connection is desired to remain open after completion, you must open the connection before calling this method. - /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// Asynchronously iterates an DbDataReader through the transform function and writes each record to an unbound channel. + /// Be sure to await the completion. /// - /// The command to generate a reader from. - /// The target channel writer to receive the results. - /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). - /// The number of records processed. - public static async ValueTask ToChannelAsync(this IExecuteReaderAsync command, - ChannelWriter target, - bool complete) + /// The IDataReader to iterate. + /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. + /// The channel reader containing the results. + public static ChannelReader ToChannelAsync(this IExecuteReaderAsync command, + bool singleReader) { if (command is null) throw new ArgumentNullException(nameof(command)); - if (target is null) throw new ArgumentNullException(nameof(target)); Contract.EndContractBlock(); - var cancellationToken = command.CancellationToken; - await target.WaitToWriteAndThrowIfClosedAsync(true, cancellationToken).ConfigureAwait(false); - try - { - return await command.ExecuteReaderAsync(reader => - command.UseAsyncRead && reader is DbDataReader r - ? r.ToChannelAsync(target, false, cancellationToken) - : reader.ToChannel(target, false, cancellationToken)).ConfigureAwait(false); - } - catch (Exception ex) - { - if (complete) - { - complete = false; - target.Complete(ex); - } - throw; - } - finally - { - if (complete) - target.Complete(); - } + Channel channel = CreateChannel(-1, singleReader); + _ = ToChannelAsync(command, channel.Writer, true); + return channel.Reader; } /// - /// Asynchronously iterates an DbDataReader and writes each record as an array to the channel. - /// If a connection is desired to remain open after completion, you must open the connection before calling this method. - /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// Asynchronously iterates an DbDataReader through the transform function and writes each record to an unbound channel. + /// Be sure to await the completion. /// - /// The command to generate a reader from. - /// The target channel writer to receive the results. - /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// The IDataReader to iterate. + /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. /// The array pool to acquire buffers from. - /// The number of records processed. - public static async ValueTask ToChannelAsync(this IExecuteReaderAsync command, - ChannelWriter target, - bool complete, - ArrayPool arrayPool) + /// The channel reader containing the results. + public static ChannelReader ToChannelAsync(this IExecuteReaderAsync command, + bool singleReader, + ArrayPool? arrayPool) { if (command is null) throw new ArgumentNullException(nameof(command)); - if (target is null) throw new ArgumentNullException(nameof(target)); Contract.EndContractBlock(); - var cancellationToken = command.CancellationToken; - await target.WaitToWriteAndThrowIfClosedAsync(true, cancellationToken).ConfigureAwait(false); - try - { - return await command.ExecuteReaderAsync(reader => - command.UseAsyncRead && reader is DbDataReader r - ? r.ToChannelAsync(target, false, arrayPool, cancellationToken) - : reader.ToChannel(target, false, arrayPool, cancellationToken)).ConfigureAwait(false); - } - catch (Exception ex) - { - if (complete) - { - complete = false; - target.Complete(ex); - } - throw; - } - finally - { - if (complete) - target.Complete(); - } + Channel channel = CreateChannel(-1, singleReader); + _ = ToChannelAsync(command, channel.Writer, true, arrayPool); + return channel.Reader; } /// - /// Asynchronously iterates an DbDataReader through the transform function and writes each record to the channel. - /// If a connection is desired to remain open after completion, you must open the connection before calling this method. - /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// Asynchronously iterates an DbDataReader through the transform function and writes each record to an unbound channel. + /// Be sure to await the completion. /// /// The return type of the transform function. - /// The command to generate a reader from. - /// The target channel writer to receive the results. - /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// The IDataReader to iterate. + /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. /// The transform function for each IDataRecord. - /// The number of records processed. - public static async ValueTask ToChannelAsync(this IExecuteReaderAsync command, - ChannelWriter target, - bool complete, + /// The channel reader containing the results. + public static ChannelReader ToChannelAsync(this IExecuteReaderAsync command, + bool singleReader, Func transform) { if (command is null) throw new ArgumentNullException(nameof(command)); - if (target is null) throw new ArgumentNullException(nameof(target)); if (transform is null) throw new ArgumentNullException(nameof(transform)); Contract.EndContractBlock(); - var cancellationToken = command.CancellationToken; - await target.WaitToWriteAndThrowIfClosedAsync(true, cancellationToken).ConfigureAwait(false); - try - { - return await command.ExecuteReaderAsync(reader => - command.UseAsyncRead && reader is DbDataReader r - ? r.ToChannelAsync(target, false, transform, cancellationToken) - : reader.ToChannel(target, false, transform, cancellationToken)).ConfigureAwait(false); - } - catch (Exception ex) - { - if (complete) - { - complete = false; - target.Complete(ex); - } - throw; - } - finally - { - if (complete) - target.Complete(); - } + Channel channel = CreateChannel(-1, singleReader); + _ = ToChannelAsync(command, channel.Writer, true, transform); + return channel.Reader; } /// - /// Asynchronously iterates an DbDataReader through the transform function and writes each record to the channel. - /// If a connection is desired to remain open after completion, you must open the connection before calling this method. - /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// Asynchronously iterates an DbDataReader mapping the results to classes of type and writes each record an unbound channel. /// /// The return type of the transform function. - /// The command to generate a reader from. - /// The target channel writer to receive the results. - /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). - /// The number of records processed. - public static async ValueTask ToChannelAsync(this IExecuteReaderAsync command, - ChannelWriter target, - bool complete) + /// The IDataReader to iterate. + /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. + /// The channel reader containing the results. + public static ChannelReader ToChannelAsync(this IExecuteReaderAsync command, + bool singleReader) where T : new() { if (command is null) throw new ArgumentNullException(nameof(command)); - if (target is null) throw new ArgumentNullException(nameof(target)); Contract.EndContractBlock(); - var cancellationToken = command.CancellationToken; - await target.WaitToWriteAndThrowIfClosedAsync(true, cancellationToken).ConfigureAwait(false); - try - { - return await command.ExecuteReaderAsync(reader => - command.UseAsyncRead && reader is DbDataReader r - ? r.ToChannelAsync(target, false, cancellationToken) - : reader.ToChannel(target, false, cancellationToken)).ConfigureAwait(false); - } - catch (Exception ex) - { - if (complete) - { - complete = false; - target.Complete(ex); - } - throw; - } - finally - { - if (complete) - target.Complete(); - } + Channel channel = CreateChannel(-1, singleReader); + _ = ToChannelAsync(command, channel.Writer, true); + return channel.Reader; } /// - /// Asynchronously iterates an DbDataReader through the transform function and writes each record to the channel. - /// If a connection is desired to remain open after completion, you must open the connection before calling this method. - /// If the connection is already open, the reading will commence immediately. Otherwise this will yield to the caller. + /// Asynchronously iterates an DbDataReader mapping the results to classes of type and writes each record an unbound channel. /// /// The return type of the transform function. - /// The command to generate a reader from. - /// The target channel writer to receive the results. - /// If true, will call .Complete() if all the results have successfully been written (or the source is emtpy). + /// The IDataReader to iterate. + /// True will cause the resultant reader to optimize for the assumption that no concurrent read operations will occur. /// An optional override map of field names to column names. - /// The number of records processed. - public static async ValueTask ToChannelAsync(this IExecuteReaderAsync command, - ChannelWriter target, - bool complete, + /// The channel reader containing the results. + public static ChannelReader ToChannelAsync(this IExecuteReaderAsync command, + bool singleReader, IEnumerable<(string Field, string? Column)> fieldMappingOverrides) where T : new() { if (command is null) throw new ArgumentNullException(nameof(command)); - if (target is null) throw new ArgumentNullException(nameof(target)); Contract.EndContractBlock(); - var cancellationToken = command.CancellationToken; - await target.WaitToWriteAndThrowIfClosedAsync(true, cancellationToken).ConfigureAwait(false); - try - { - return await command.ExecuteReaderAsync(reader => - command.UseAsyncRead && reader is DbDataReader r - ? r.ToChannelAsync(target, false, fieldMappingOverrides, cancellationToken) - : reader.ToChannel(target, false, fieldMappingOverrides, cancellationToken)).ConfigureAwait(false); - } - catch (Exception ex) - { - if (complete) - { - complete = false; - target.Complete(ex); - } - throw; - } - finally - { - if (complete) - target.Complete(); - } + Channel channel = CreateChannel(-1, singleReader); + _ = ToChannelAsync(command, channel.Writer, true, fieldMappingOverrides); + return channel.Reader; } #endif diff --git a/Source/Channel/Transformer.cs b/Source/Channel/Transformer.cs index 29c436b..9d2904d 100644 --- a/Source/Channel/Transformer.cs +++ b/Source/Channel/Transformer.cs @@ -1,58 +1,45 @@ -using Open.ChannelExtensions; -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Data; -using System.Data.Common; -using System.Diagnostics.Contracts; -using System.Linq; -using System.Threading; -using System.Threading.Channels; -using System.Threading.Tasks; +namespace Open.Database.Extensions; -namespace Open.Database.Extensions; - -internal class Transformer : Core.Transformer +/// Constructs a . +/// An optional override map of field names to column names where the keys are the property names, and values are the column names. +internal class Transformer(IEnumerable<(string Field, string? Column)>? fieldMappingOverrides = null) + : Core.Transformer(fieldMappingOverrides) where T : new() { - /// Constructs a . - /// An optional override map of field names to column names where the keys are the property names, and values are the column names. - public Transformer(IEnumerable<(string Field, string? Column)>? fieldMappingOverrides = null) - : base(fieldMappingOverrides) - { - } - /// /// Static utility for creating a Transformer . /// - /// + /// An optional override map of field names to column names where the keys are the property names, and values are the column names. + [ExcludeFromCodeCoverage] public static new Transformer Create(IEnumerable<(string Field, string? Column)>? fieldMappingOverrides = null) => new(fieldMappingOverrides); /// - /// Transforms the results from the reader by first buffering the results and if/when the buffer size is reached, the results are transformed to a channel for reading. + /// Transforms the results from the reader by first buffering the results and then the final results are transformed to the target channel for reading. /// - /// The reader to read from. - /// The target channel to write to. - /// Will call complete when no more results are avaiable. - /// The cancellation token. - /// The ChannelReader of the target. - internal async ValueTask PipeResultsTo(IDataReader reader, ChannelWriter target, bool complete, CancellationToken cancellationToken) + /// + /// This is necessary to absorb as much data as possible first and defer the transformation till later. + /// + private ChannelWriter PipeResultsToPrep( + IDataReader reader, + ChannelWriter target, + bool complete, + CancellationToken cancellationToken) { if (reader is null) throw new ArgumentNullException(nameof(reader)); + if (target is null) throw new ArgumentNullException(nameof(target)); Contract.EndContractBlock(); - var columns = reader.GetMatchingOrdinals(ColumnNames, true); - var ordinals = columns.Select(m => m.Ordinal).ToImmutableArray(); + (string Name, int Ordinal)[] columns = reader.GetMatchingOrdinals(ColumnNames, true); var names = columns.Select(m => m.Name).ToImmutableArray(); var processor = new Processor(this, names); - var transform = processor.Transform; + Func transform = processor.Transform; - var channel = ChannelDbExtensions.CreateChannel(MaxArrayBuffer, true); - var writer = channel.Writer; + Channel channel = ChannelDbExtensions.CreateChannel(MaxArrayBuffer, true); + ChannelWriter writer = channel.Writer; - var piped = channel + _ = channel .Reader .Transform(a => { @@ -65,27 +52,12 @@ internal async ValueTask PipeResultsTo(IDataReader reader, ChannelWriter - { - if (t.IsFaulted) target.Complete(t.Exception); - else target.Complete(); - }, - CancellationToken.None, - TaskContinuationOptions.ExecuteSynchronously, - TaskScheduler.Current); - } - - return await reader - .ToChannel(writer, false, LocalPool, cancellationToken).ConfigureAwait(false); + return writer; } -#if NETSTANDARD2_1 /// /// Transforms the results from the reader by first buffering the results and if/when the buffer size is reached, the results are transformed to a channel for reading. /// @@ -94,53 +66,34 @@ internal async ValueTask PipeResultsTo(IDataReader reader, ChannelWriterWill call complete when no more results are avaiable. /// The cancellation token. /// The ChannelReader of the target. - internal ValueTask PipeResultsToAsync(DbDataReader reader, ChannelWriter target, bool complete, CancellationToken cancellationToken) - { - if (reader is null) throw new ArgumentNullException(nameof(reader)); - Contract.EndContractBlock(); - - var columns = reader.GetMatchingOrdinals(ColumnNames, true); - var ordinals = columns.Select(m => m.Ordinal).ToImmutableArray(); - var names = columns.Select(m => m.Name).ToImmutableArray(); - - var processor = new Processor(this, names); - var transform = processor.Transform; - - var channel = ChannelDbExtensions.CreateChannel(MaxArrayBuffer, true); - var writer = channel.Writer; - - var piped = channel - .Reader - .Transform(a => - { - try - { - return transform(a); - } - finally - { - LocalPool.Return(a); - } - }) - .PipeTo(target, complete, cancellationToken); - - if (complete) - { - _ = piped - .AsTask() - .ContinueWith(t => - { - if (t.IsFaulted) target.Complete(t.Exception); - else target.Complete(); - }, - CancellationToken.None, - TaskContinuationOptions.ExecuteSynchronously, - TaskScheduler.Current); - } - - return reader - .ToChannelAsync(writer, false, LocalPool, cancellationToken); - } + internal ValueTask PipeResultsTo( + IDataReader reader, + ChannelWriter target, + bool complete, + CancellationToken cancellationToken) + => reader.ToChannel( + PipeResultsToPrep(reader, target, complete, cancellationToken), + true, LocalPool, cancellationToken); + +#if NETSTANDARD2_0 +#else + /// + /// Transforms the results from the reader by first buffering the results and if/when the buffer size is reached, the results are transformed to a channel for reading. + /// + /// The reader to read from. + /// The target channel to write to. + /// Will call complete when no more results are avaiable. + /// The cancellation token. + /// The ChannelReader of the target. + [ExcludeFromCodeCoverage] + internal ValueTask PipeResultsToAsync( + DbDataReader reader, + ChannelWriter target, + bool complete, + CancellationToken cancellationToken) + => reader.ToChannelAsync( + PipeResultsToPrep(reader, target, complete, cancellationToken), + true, LocalPool, cancellationToken); #endif } diff --git a/Source/Channel/_Imports.cs b/Source/Channel/_Imports.cs new file mode 100644 index 0000000..8147100 --- /dev/null +++ b/Source/Channel/_Imports.cs @@ -0,0 +1,8 @@ +global using Open.ChannelExtensions; +global using System.Buffers; +global using System.Collections.Immutable; +global using System.Data; +global using System.Data.Common; +global using System.Diagnostics.CodeAnalysis; +global using System.Diagnostics.Contracts; +global using System.Threading.Channels; \ No newline at end of file diff --git a/Source/Core/.gitignore b/Source/Core/.gitignore deleted file mode 100644 index 4378419..0000000 --- a/Source/Core/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -############### -# folder # -############### -/**/DROP/ -/**/TEMP/ -/**/packages/ -/**/bin/ -/**/obj/ -_site diff --git a/Source/Core/Constants.cs b/Source/Core/Constants.cs index 29b5700..3d803cd 100644 --- a/Source/Core/Constants.cs +++ b/Source/Core/Constants.cs @@ -2,10 +2,10 @@ internal static class ConnectionTimeout { - public const ushort DEFAULT_SECONDS = 30; + public const ushort DEFAULT_SECONDS = 30; } internal static class CommandTimeout { - public const ushort DEFAULT_SECONDS = 60; + public const ushort DEFAULT_SECONDS = 60; } diff --git a/Source/Core/Core/DbConnectionProvider.cs b/Source/Core/Core/DbConnectionProvider.cs index 5c762cb..21c01da 100644 --- a/Source/Core/Core/DbConnectionProvider.cs +++ b/Source/Core/Core/DbConnectionProvider.cs @@ -1,56 +1,47 @@ -using System; -using System.Data; -using System.Diagnostics.Contracts; - -namespace Open.Database.Extensions.Core; +namespace Open.Database.Extensions.Core; /// /// Simplifies handling connections. /// -/// -internal class DbConnectionProvider : IDbConnectionPool - where TConnection : class, IDbConnection +internal class DbConnectionProvider(TConnection connection) + : IDbConnectionPool + where TConnection : class, IDbConnection { - public DbConnectionProvider(TConnection connection) - { - Connection = connection ?? throw new ArgumentNullException(nameof(connection)); - } - - private TConnection Connection { get; } + private TConnection Connection { get; } = connection ?? throw new ArgumentNullException(nameof(connection)); - private ConnectionState? TakenConnectionState; + private ConnectionState? _takenConnectionState; - /// - public TConnection Take() - { - if (TakenConnectionState.HasValue) - throw new InvalidOperationException("Concurrent use of a single connection is not supported."); + /// + public TConnection Take() + { + if (_takenConnectionState.HasValue) + throw new InvalidOperationException("Concurrent use of a single connection is not supported."); - TakenConnectionState = Connection.State; - return Connection; - } + _takenConnectionState = Connection.State; + return Connection; + } - IDbConnection IDbConnectionPool.Take() => Take(); + IDbConnection IDbConnectionPool.Take() => Take(); - /// - public void Give(IDbConnection connection) - { - if (connection is null) + /// + public void Give(IDbConnection connection) + { + if (connection is null) throw new ArgumentNullException(nameof(connection)); - if (connection != Connection) + if (connection != Connection) throw new ArgumentException("Does not belong to this provider.", nameof(connection)); - Contract.EndContractBlock(); + Contract.EndContractBlock(); - if (TakenConnectionState == ConnectionState.Closed) - connection.Close(); + if (_takenConnectionState == ConnectionState.Closed) + connection.Close(); - TakenConnectionState = null; - } + _takenConnectionState = null; + } } internal static class DbConnectionProvider { - public static DbConnectionProvider Create(TConnection connection) - where TConnection : class, IDbConnection - => new(connection); + public static DbConnectionProvider Create(TConnection connection) + where TConnection : class, IDbConnection + => new(connection); } diff --git a/Source/Core/Core/QueryResult.cs b/Source/Core/Core/QueryResult.cs index 2f5e315..5cc62ee 100644 --- a/Source/Core/Core/QueryResult.cs +++ b/Source/Core/Core/QueryResult.cs @@ -1,11 +1,4 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics.Contracts; -using System.Linq; -using System.Threading.Tasks; - + namespace Open.Database.Extensions.Core; /// @@ -15,129 +8,129 @@ namespace Open.Database.Extensions.Core; /// The type of the result property. public class QueryResult { - /// Constructs a . - /// The ordinal values requested - /// The column names requested. - /// The result. - public QueryResult( + /// Constructs a . + /// The ordinal values requested + /// The column names requested. + /// The result. + public QueryResult( ImmutableArray ordinals, ImmutableArray names, TResult result) - { - if (ordinals.Length != names.Length) + { + if (ordinals.Length != names.Length) throw new ArgumentException("Mismatched array lengths of ordinals and names."); - Ordinals = ordinals; - Names = names; - Result = result; - Contract.EndContractBlock(); - ColumnCount = ordinals.Length; - } + Ordinals = ordinals; + Names = names; + Result = result; + Contract.EndContractBlock(); + + ColumnCount = ordinals.Length; + } - /// - public QueryResult( + /// + public QueryResult( IEnumerable ordinals, IEnumerable names, TResult result) - : this(Immute(ordinals), Immute(names), result) - { - } + : this(Immute(ordinals), Immute(names), result) + { + } - static ImmutableArray Immute(IEnumerable source) - => source is ImmutableArray o ? o : source?.ToImmutableArray() + static ImmutableArray Immute(IEnumerable source) + => source is ImmutableArray o ? o : source?.ToImmutableArray() ?? throw new ArgumentNullException(nameof(source)); - /// - /// The number of columns. - /// - public readonly int ColumnCount; - - /// - /// The ordinal values requested. - /// - public readonly ImmutableArray Ordinals; - - /// - /// The column names requested. - /// - public readonly ImmutableArray Names; - - /// - /// The values requested. A Queue is used since values are typically used - /// first in first out and dequeuing results helps reduced redundant memory - /// usage. - /// - public readonly TResult Result; - - /// Implicity returns the result from this instance. - /// The source of the result. - public static implicit operator TResult(QueryResult result) => (result ?? throw new ArgumentNullException(nameof(result))).Result; + /// + /// The number of columns. + /// + public readonly int ColumnCount; + + /// + /// The ordinal values requested. + /// + public readonly ImmutableArray Ordinals; + + /// + /// The column names requested. + /// + public readonly ImmutableArray Names; + + /// + /// The values requested. A Queue is used since values are typically used + /// first in first out and dequeuing results helps reduced redundant memory + /// usage. + /// + public readonly TResult Result; + + /// Implicitly returns the result from this instance. + /// The source of the result. + public static implicit operator TResult(QueryResult result) => (result ?? throw new ArgumentNullException(nameof(result))).Result; } /// /// A container for data reader results that also provides the column names and /// other helpful data methods. /// -/// The type of the items in the resultant -/// enumerble. +/// The type of the items in the resultant enumerable. /// The type of the result property. public class QueryResultCollection : QueryResult, IEnumerable - where TResult : IEnumerable + where TResult : IEnumerable { - /// Constructs a . - /// - public QueryResultCollection( + /// Constructs a . + /// + public QueryResultCollection( ImmutableArray ordinals, ImmutableArray names, TResult result) - : base(ordinals, names, result) - { - if (result == null) throw new ArgumentNullException(nameof(result)); - } + : base(ordinals, names, result) + { + if (result == null) throw new ArgumentNullException(nameof(result)); + } - /// - public QueryResultCollection( + /// + public QueryResultCollection( IEnumerable ordinals, IEnumerable names, TResult result) - : base(ordinals, names, result) - { - } + : base(ordinals, names, result) + { + } - /// - public IEnumerator GetEnumerator() - => Result.GetEnumerator(); + /// + public IEnumerator GetEnumerator() + => Result.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() - => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); } /// /// A container for data reader results that also provides the column names and /// other helpful data methods. /// -/// The type of the items in the resultant enumerble. +/// The type of the items in the resultant enumerable. public class QueryResultCollection : QueryResultCollection> { - /// Constructs a . - /// - public QueryResultCollection( + /// Constructs a . + /// + public QueryResultCollection( ImmutableArray ordinals, ImmutableArray names, IEnumerable result) - : base(ordinals, names, result) - { - } + : base(ordinals, names, result) + { + } - /// - public QueryResultCollection( + /// + public QueryResultCollection( IEnumerable ordinals, IEnumerable names, IEnumerable result) - : base(ordinals, names, result) - { - } + : base(ordinals, names, result) + { + } } /// @@ -145,27 +138,36 @@ public QueryResultCollection( /// other helpful data methods. /// /// The type of the items in the resultant -/// enumerble. +/// enumerable. public class QueryResultQueue : QueryResult> // Not exposed as enumerable to avoid confusion with the queue. { - /// Constructs a . - /// - public QueryResultQueue( + /// Constructs a . + /// + public QueryResultQueue( ImmutableArray ordinals, ImmutableArray names, Queue result) - : base(ordinals, names, result) - { - } + : base(ordinals, names, result) + { + } - /// - public QueryResultQueue( + /// + public QueryResultQueue( IEnumerable ordinals, IEnumerable names, Queue result) - : base(ordinals, names, result) - { - } + : base(ordinals, names, result) + { + } + + /// + public QueryResultQueue( + IEnumerable ordinals, + IEnumerable names, + IEnumerable result) + : base(ordinals, names, new (result)) + { + } } /// @@ -173,57 +175,57 @@ public QueryResultQueue( /// public static class QueryResultExtensions { - /// - /// Returns an enumerable that dequeues the results and returns a column - /// mapped dictionary for each entry. values are converted to null. - /// - /// The query result. Typically produced by a - /// .Retrieve method. - /// An enumerable that dequeues the results and returns a column - /// mapped dictionary for each entry - public static IEnumerable> DequeueAsMappedDictionaries(this QueryResult> source) - { + /// + /// Returns an enumerable that dequeues the results and returns a column + /// mapped dictionary for each entry. values are converted to null. + /// + /// The query result. Typically produced by a + /// .Retrieve method. + /// An enumerable that dequeues the results and returns a column + /// mapped dictionary for each entry + public static IEnumerable> DequeueAsMappedDictionaries(this QueryResult> source) + { return source is null - ? throw new ArgumentNullException(nameof(source)) - : DequeueAsMappedDictionariesCore(source); + ? throw new ArgumentNullException(nameof(source)) + : DequeueAsMappedDictionariesCore(source); static IEnumerable> DequeueAsMappedDictionariesCore(QueryResult> source) - { - var q = source.Result; - var names = source.Names; - var count = source.ColumnCount; - while (q.Count != 0) - { - var r = q.Dequeue(); - var d = new Dictionary(count); - for (var i = 0; i < count; i++) - d.Add(names[i], CoreExtensions.DBNullValueToNull(r[i])); - yield return d; - } - } - } - - /// - public static async ValueTask>> DequeueAsMappedDictionaries(this Task>> source) - => (await (source ?? throw new ArgumentNullException(nameof(source))).ConfigureAwait(false)).DequeueAsMappedDictionaries(); - - /// - public static async ValueTask>> DequeueAsMappedDictionaries(this ValueTask>> source) - => (await source.ConfigureAwait(false)).DequeueAsMappedDictionaries(); - - /// - public static IEnumerable DequeueAs(this QueryResult> source, IEnumerable<(string Field, string? Column)>? fieldMappingOverrides = null) - where T : new() - { - if (source is null) throw new ArgumentNullException(nameof(source)); - Contract.EndContractBlock(); - - var x = new Transformer(fieldMappingOverrides); - return x.AsDequeueingEnumerable(source); - } + { + Queue q = source.Result; + ImmutableArray names = source.Names; + int count = source.ColumnCount; + while (q.Count != 0) + { + object?[] r = q.Dequeue(); + var d = new Dictionary(count); + for (int i = 0; i < count; i++) + d.Add(names[i], CoreExtensions.DBNullValueToNull(r[i])); + yield return d; + } + } + } + + /// + public static async ValueTask>> DequeueAsMappedDictionaries(this Task>> source) + => (await (source ?? throw new ArgumentNullException(nameof(source))).ConfigureAwait(false)).DequeueAsMappedDictionaries(); + + /// + public static async ValueTask>> DequeueAsMappedDictionaries(this ValueTask>> source) + => (await source.ConfigureAwait(false)).DequeueAsMappedDictionaries(); + + /// + public static IEnumerable DequeueAs(this QueryResult> source, IEnumerable<(string Field, string? Column)>? fieldMappingOverrides = null) + where T : new() + { + if (source is null) throw new ArgumentNullException(nameof(source)); + Contract.EndContractBlock(); + + var x = new Transformer(fieldMappingOverrides); + return x.AsDequeueingEnumerable(source); + } /// /// Returns an enumerable that dequeues the results and attempts to map the @@ -240,78 +242,78 @@ public static IEnumerable DequeueAs(this QueryResult> sou public static IEnumerable DequeueAs( this QueryResult> source, IEnumerable>? fieldMappingOverrides) - where T : new() - => DequeueAs(source, fieldMappingOverrides?.Select(kvp => (kvp.Key, kvp.Value))); - - /// - public static async ValueTask> DequeueAs(this Task>> source, IEnumerable<(string, string?)>? fieldMappingOverrides = null) - where T : new() - { - if (source is null) throw new ArgumentNullException(nameof(source)); - Contract.EndContractBlock(); - - var x = new Transformer(fieldMappingOverrides); - return x.AsDequeueingEnumerable(await source.ConfigureAwait(false)); - } - - /// - public static async ValueTask> DequeueAs(this ValueTask>> source, IEnumerable<(string, string?)>? fieldMappingOverrides = null) - where T : new() - { - var x = new Transformer(fieldMappingOverrides); - return x.AsDequeueingEnumerable(await source.ConfigureAwait(false)); - } - - /// - public static ValueTask> DequeueAs(this Task>> source, IEnumerable>? fieldMappingOverrides) - where T : new() - => DequeueAs(source, fieldMappingOverrides?.Select(kvp => (kvp.Key, kvp.Value))); - - /// - public static ValueTask> DequeueAs(this ValueTask>> source, IEnumerable>? fieldMappingOverrides) - where T : new() - => DequeueAs(source, fieldMappingOverrides?.Select(kvp => (kvp.Key, kvp.Value))); - - /// - /// Returns an enumerable that dequeues the results and returns a column - /// mapped dictionary for each entry. values are converted to null. - /// - /// The query result. Typically produced by a - /// .Retrieve method. - /// An enumerable that dequeues the results and returns a column - /// mapped dictionary for each entry - public static IEnumerable> AsMappedDictionaries(this QueryResult> source) - { + where T : new() + => DequeueAs(source, fieldMappingOverrides?.Select(kvp => (kvp.Key, kvp.Value))); + + /// + public static async ValueTask> DequeueAs(this Task>> source, IEnumerable<(string, string?)>? fieldMappingOverrides = null) + where T : new() + { + if (source is null) throw new ArgumentNullException(nameof(source)); + Contract.EndContractBlock(); + + var x = new Transformer(fieldMappingOverrides); + return x.AsDequeueingEnumerable(await source.ConfigureAwait(false)); + } + + /// + public static async ValueTask> DequeueAs(this ValueTask>> source, IEnumerable<(string, string?)>? fieldMappingOverrides = null) + where T : new() + { + var x = new Transformer(fieldMappingOverrides); + return x.AsDequeueingEnumerable(await source.ConfigureAwait(false)); + } + + /// + public static ValueTask> DequeueAs(this Task>> source, IEnumerable>? fieldMappingOverrides) + where T : new() + => DequeueAs(source, fieldMappingOverrides?.Select(kvp => (kvp.Key, kvp.Value))); + + /// + public static ValueTask> DequeueAs(this ValueTask>> source, IEnumerable>? fieldMappingOverrides) + where T : new() + => DequeueAs(source, fieldMappingOverrides?.Select(kvp => (kvp.Key, kvp.Value))); + + /// + /// Returns an enumerable that dequeues the results and returns a column + /// mapped dictionary for each entry. values are converted to null. + /// + /// The query result. Typically produced by a + /// .Retrieve method. + /// An enumerable that dequeues the results and returns a column + /// mapped dictionary for each entry + public static IEnumerable> AsMappedDictionaries(this QueryResult> source) + { return source is null - ? throw new ArgumentNullException(nameof(source)) - : AsMappedDictionariesCore(source); + ? throw new ArgumentNullException(nameof(source)) + : AsMappedDictionariesCore(source); static IEnumerable> AsMappedDictionariesCore(QueryResult> source) - { - var q = source.Result; - var names = source.Names; - var count = source.ColumnCount; - foreach (var r in q) - { - var d = new Dictionary(count); - for (var i = 0; i < count; i++) - d.Add(names[i], CoreExtensions.DBNullValueToNull(r[i])); - yield return d; - } - } - } - - /// - public static async ValueTask>> AsMappedDictionaries(this ValueTask>> source) - => AsMappedDictionaries(await source.ConfigureAwait(false)); - - /// - public static async ValueTask>> AsMappedDictionaries(this Task>> source) - => AsMappedDictionaries(await (source ?? throw new ArgumentNullException(nameof(source))).ConfigureAwait(false)); + { + IEnumerable q = source.Result; + ImmutableArray names = source.Names; + int count = source.ColumnCount; + foreach (object?[] r in q) + { + var d = new Dictionary(count); + for (int i = 0; i < count; i++) + d.Add(names[i], CoreExtensions.DBNullValueToNull(r[i])); + yield return d; + } + } + } + + /// + public static async ValueTask>> AsMappedDictionaries(this ValueTask>> source) + => AsMappedDictionaries(await source.ConfigureAwait(false)); + + /// + public static async ValueTask>> AsMappedDictionaries(this Task>> source) + => AsMappedDictionaries(await (source ?? throw new ArgumentNullException(nameof(source))).ConfigureAwait(false)); } diff --git a/Source/Core/Core/Transformer.cs b/Source/Core/Core/Transformer.cs index 9415436..cbdba4a 100644 --- a/Source/Core/Core/Transformer.cs +++ b/Source/Core/Core/Transformer.cs @@ -1,15 +1,4 @@ -using System; -using System.Buffers; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Data; -using System.Data.Common; -using System.Diagnostics.Contracts; -using System.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Threading; - + namespace Open.Database.Extensions.Core; /// @@ -28,29 +17,29 @@ public class Transformer /// /// Buffers for transforming. /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Roslynator", "RCS1158:Static member in generic type should use a type parameter.", Justification = "")] - protected internal static readonly ArrayPool LocalPool = ArrayPool.Create(1024, MaxArrayBuffer); + [SuppressMessage("Roslynator", "RCS1158:Static member in generic type should use a type parameter.", Justification = "")] + protected internal static readonly ArrayPool LocalPool = ArrayPool.Create(1024, MaxArrayBuffer); /// /// The type of . /// public Type Type { get; } - private readonly PropertyInfo[] Properties; + private readonly PropertyInfo[] _properties; // Allow mapping key = object property, value = column name. - readonly Dictionary PropertyMap; - readonly Dictionary ColumnToPropertyMap; + readonly Dictionary _propertyMap; + readonly Dictionary _columnToPropertyMap; /// /// The property names. /// - public IEnumerable PropertyNames => PropertyMap.Keys; + public IEnumerable PropertyNames => _propertyMap.Keys; /// /// The column names. /// - public IEnumerable ColumnNames => PropertyMap.Values; + public IEnumerable ColumnNames => _propertyMap.Values; /// /// Constructs a transformer using the optional field overrides. @@ -58,22 +47,22 @@ public class Transformer protected internal Transformer(IEnumerable<(string Field, string? Column)>? overrides = null) { Type = typeof(T); - Properties = Type.GetProperties(); - PropertyMap = Properties.Select(p => p.Name).ToDictionary(n => n); + _properties = Type.GetProperties(); + _propertyMap = _properties.Select(p => p.Name).ToDictionary(n => n); - var pm = Properties.ToDictionary(p => p.Name); + Dictionary pm = _properties.ToDictionary(p => p.Name); if (overrides != null) { - foreach (var (Field, Column) in overrides) + foreach ((string Field, string? Column) in overrides) { - var cn = Column; - if (cn == null) PropertyMap.Remove(Field); // Null values indicate a desire to 'ignore' a field. - else PropertyMap[Field] = cn; + string? cn = Column; + if (cn == null) _propertyMap.Remove(Field); // Null values indicate a desire to 'ignore' a field. + else _propertyMap[Field] = cn; } } - ColumnToPropertyMap = PropertyMap.ToDictionary(kvp => kvp.Value.ToUpperInvariant(), kvp => pm[kvp.Key]); + _columnToPropertyMap = _propertyMap.ToDictionary(kvp => kvp.Value.ToUpperInvariant(), kvp => pm[kvp.Key]); } /// @@ -98,10 +87,10 @@ public Processor(Transformer transformer) Transform = record => { var model = new T(); - var count = _names.Length; - for (var i = 0; i < count; i++) + int count = _names.Length; + for (int i = 0; i < count; i++) { - var p = _propertySetters[i]; + Action? p = _propertySetters[i]; if (p != null) { object? value = record[i]; @@ -127,18 +116,15 @@ public Processor(Transformer transformer) /// The transformer to use. /// The names of columns/properties to acquire. public Processor(Transformer transformer, ImmutableArray names) - : this(transformer) - { - SetNames(names); - } + : this(transformer) => SetNames(names); /// /// The transformer being used. /// public Transformer Transformer { get; } - ImmutableArray _names = ImmutableArray.Empty; - Action?[] _propertySetters = Array.Empty?>(); + ImmutableArray _names = []; + Action?[] _propertySetters = []; /// /// The resultant transform function. @@ -151,10 +137,10 @@ public Processor(Transformer transformer, ImmutableArray names) /// The column/property names to process. public void SetNames(ImmutableArray names) { - var map = Transformer.ColumnToPropertyMap; + Dictionary map = Transformer._columnToPropertyMap; _names = names; _propertySetters = names - .Select(n => map.TryGetValue(n.ToUpperInvariant(), out var p) ? p.BuildUntypedSetter() : null) + .Select(n => map.TryGetValue(n.ToUpperInvariant(), out PropertyInfo? p) ? p.BuildUntypedSetter() : null) .ToArray(); } } @@ -166,7 +152,7 @@ public IEnumerable AsDequeueingEnumerable(QueryResult> resul Contract.EndContractBlock(); var processor = new Processor(this, results.Names); - var q = results.Result; + Queue q = results.Result; return q.DequeueEach().Select(processor.Transform); } @@ -178,15 +164,15 @@ public IEnumerable AsDequeueingEnumerable(QueryResult> resul /// The array pool to return the buffers to. /// Indicates whether the contents of the buffers should be cleared before reuse. /// A dequeuing enumerable of the transformed results. - public IEnumerable AsDequeueingEnumerable(QueryResult> results, ArrayPool arrayPool, bool clearArrays = false) + public IEnumerable AsDequeueingEnumerable(QueryResult> results, ArrayPool arrayPool, bool clearArrays = false) { if (results is null) throw new ArgumentNullException(nameof(results)); if (arrayPool is null) throw new ArgumentNullException(nameof(arrayPool)); Contract.EndContractBlock(); var processor = new Processor(this, results.Names); - var transform = processor.Transform; - var q = results.Result; + Func transform = processor.Transform; + Queue q = results.Result; return q.DequeueEach().Select(a => { @@ -208,9 +194,9 @@ internal IEnumerable Results(IDataReader reader) Contract.EndContractBlock(); // Ignore missing columns. - var columns = reader.GetMatchingOrdinals(PropertyMap.Values, true); + (string Name, int Ordinal)[] columns = reader.GetMatchingOrdinals(_propertyMap.Values, true); var processor = new Processor(this, columns.Select(m => m.Name).ToImmutableArray()); - var transform = processor.Transform; + Func transform = processor.Transform; return reader .AsEnumerable(columns.Select(m => m.Ordinal), LocalPool) @@ -242,7 +228,7 @@ internal IEnumerable ResultsBuffered(IDataReader reader, bool readStarted) return Enumerable.Empty(); // Ignore missing columns. - var columns = reader.GetMatchingOrdinals(PropertyMap.Values, true); + (string Name, int Ordinal)[] columns = reader.GetMatchingOrdinals(_propertyMap.Values, true); return AsDequeueingEnumerable( CoreExtensions.RetrieveInternal( @@ -254,23 +240,24 @@ internal IEnumerable ResultsBuffered(IDataReader reader, bool readStarted) LocalPool); } -#if NETSTANDARD2_1 - /// The reader to read from. - /// The cancellation token. - /// - internal IAsyncEnumerable ResultsAsync(DbDataReader reader, CancellationToken cancellationToken) - { +#if NETSTANDARD2_0 +#else + /// The reader to read from. + /// The cancellation token. + /// + internal IAsyncEnumerable ResultsAsync(DbDataReader reader, CancellationToken cancellationToken) + { return reader is null - ? throw new ArgumentNullException(nameof(reader)) - : ResultsAsyncCore(reader, cancellationToken); + ? throw new ArgumentNullException(nameof(reader)) + : ResultsAsyncCore(reader, cancellationToken); async IAsyncEnumerable ResultsAsyncCore(DbDataReader reader, [EnumeratorCancellation] CancellationToken cancellationToken) { // Ignore missing columns. - var columns = reader.GetMatchingOrdinals(PropertyMap.Values, true); + (string Name, int Ordinal)[] columns = reader.GetMatchingOrdinals(_propertyMap.Values, true); var processor = new Processor(this, columns.Select(m => m.Name).ToImmutableArray()); - await foreach (var a in reader.AsAsyncEnumerable(columns.Select(m => m.Ordinal), LocalPool, cancellationToken)) + await foreach (object[] a in reader.AsAsyncEnumerable(columns.Select(m => m.Ordinal), LocalPool, cancellationToken)) { try { @@ -296,15 +283,15 @@ public IEnumerable Results(DataTable table, bool clearTable) if (table is null) throw new ArgumentNullException(nameof(table)); Contract.EndContractBlock(); - var columnCount = table.Columns.Count; - var columns = table.Columns.AsEnumerable(); - var results = new QueryResult>( + int columnCount = table.Columns.Count; + IEnumerable columns = table.Columns.AsEnumerable(); + var results = new QueryResult>( columns.Select(c => c.Ordinal), columns.Select(c => c.ColumnName), - new Queue(table.Rows.AsEnumerable().Select(r => + new Queue(table.Rows.AsEnumerable().Select(r => { - var a = LocalPool.Rent(columnCount); - for (var i = 0; i < columnCount; i++) a[i] = r[i]; + object[] a = LocalPool.Rent(columnCount); + for (int i = 0; i < columnCount; i++) a[i] = r[i]; return a; }))); diff --git a/Source/Core/DbConnectionFactory.cs b/Source/Core/DbConnectionFactory.cs index 8645c9b..c336b3a 100644 --- a/Source/Core/DbConnectionFactory.cs +++ b/Source/Core/DbConnectionFactory.cs @@ -1,79 +1,76 @@ -using System; -using System.Data; -using System.Diagnostics.Contracts; - -namespace Open.Database.Extensions; +namespace Open.Database.Extensions; /// /// DbConnection factory implementation that accepts a factory function. /// public class DbConnectionFactory : IDbConnectionFactory { - /// - /// Constructs a DbConnectionFactory. - /// - /// The factory function. - protected DbConnectionFactory(Func factory) - { - _factory = factory ?? throw new ArgumentNullException(nameof(factory)); - Contract.EndContractBlock(); - } + /// + /// Constructs a DbConnectionFactory. + /// + /// The factory function. + protected DbConnectionFactory(Func factory) + { + _factory = factory ?? throw new ArgumentNullException(nameof(factory)); + Contract.EndContractBlock(); + } - readonly Func _factory; + readonly Func _factory; - /// - /// Creates a connection of from the underlying factory function. - /// - public IDbConnection Create() => _factory(); + /// + /// Creates a connection of from the underlying factory function. + /// + public IDbConnection Create() => _factory(); - /// - /// Creates a Non-Generic DbConnectionFactory. - /// - /// The factory function. - /// A Non-Generic DbConnectionFactory - public static DbConnectionFactory Create(Func factory) - where TConnection : IDbConnection - => new(factory); + /// + /// Creates a Non-Generic DbConnectionFactory. + /// + /// The factory function. + /// A Non-Generic DbConnectionFactory + public static DbConnectionFactory Create(Func factory) + where TConnection : IDbConnection + => new(factory); - /// - /// Implicitly converts a connection factory function to a connection factory instance. - /// - /// The factory function. - public static implicit operator DbConnectionFactory(Func factory) - => Create(factory); + /// + /// Implicitly converts a connection factory function to a connection factory instance. + /// + /// The factory function. + public static implicit operator DbConnectionFactory(Func factory) + => Create(factory); } /// /// Generic connection factory implementation that accepts a factory function. /// /// The connection type. -public class DbConnectionFactory : DbConnectionFactory, IDbConnectionFactory - where TConnection : IDbConnection +public class DbConnectionFactory + : DbConnectionFactory, IDbConnectionFactory + where TConnection : IDbConnection { - readonly Func _factory; + readonly Func _factory; - /// - /// Constructs a DbConnectionFactory. - /// - /// The factory function. - public DbConnectionFactory(Func factory) - : base(() => factory()) - { - _factory = factory ?? throw new ArgumentNullException(nameof(factory)); - Contract.EndContractBlock(); - } + /// + /// Constructs a DbConnectionFactory. + /// + /// The factory function. + public DbConnectionFactory(Func factory) + : base(() => factory()) + { + _factory = factory ?? throw new ArgumentNullException(nameof(factory)); + Contract.EndContractBlock(); + } - IDbConnection IDbConnectionFactory.Create() => Create(); + IDbConnection IDbConnectionFactory.Create() => Create(); - /// - /// Creates a connection of from the underlying factory function. - /// - public new TConnection Create() => _factory(); + /// + /// Creates a connection of from the underlying factory function. + /// + public new TConnection Create() => _factory(); - /// - /// Implicitly converts a connection factory function to a genetic-typed connection factory instance. - /// - /// - public static implicit operator DbConnectionFactory(Func factory) - => Create(factory); + /// + /// Implicitly converts a connection factory function to a genetic-typed connection factory instance. + /// + /// + public static implicit operator DbConnectionFactory(Func factory) + => Create(factory); } diff --git a/Source/Core/Documentation.xml b/Source/Core/Documentation.xml deleted file mode 100644 index 23402c7..0000000 --- a/Source/Core/Documentation.xml +++ /dev/null @@ -1,3467 +0,0 @@ - - - - Open.Database.Extensions.Core - - - - - Simplifies handling connections. - - - - - - - - - - - - A container for data reader results that also provides the column names - and other helpful data methods. - - The type of the result property. - - - Constructs a . - The ordinal values requested - The column names requested. - The result. - - - - - - - The number of columns. - - - - - The ordinal values requested. - - - - - The column names requested. - - - - - The values requested. A Queue is used since values are typically used - first in first out and dequeuing results helps reduced redundant memory - usage. - - - - Implicity returns the result from this instance. - The source of the result. - - - - A container for data reader results that also provides the column names and - other helpful data methods. - - The type of the items in the resultant - enumerble. - The type of the result property. - - - Constructs a . - - - - - - - - - - - A container for data reader results that also provides the column names and - other helpful data methods. - - The type of the items in the resultant enumerble. - - - Constructs a . - - - - - - - - A container for data reader results that also provides the column names and - other helpful data methods. - - The type of the items in the resultant - enumerble. - - - Constructs a . - - - - - - - - A set of extensions for getting column data from a QueryResult. - - - - - Returns an enumerable that dequeues the results and returns a column - mapped dictionary for each entry. values are converted to null. - - The query result. Typically produced by a - .Retrieve method. - An enumerable that dequeues the results and returns a column - mapped dictionary for each entry - - - - - - - - - - - - - Returns an enumerable that dequeues the results and attempts to map the - fields to type T. - - DBNull values are converted to null. - The query result. Typically produced by a - .Retrieve method. - An optional override map of field - names to column names where the keys are the property names, and values - are the column names. - An enumerable that dequeues the results and returns an entity - of type T. - - - - - - - - - - - - - - - - Returns an enumerable that dequeues the results and returns a column - mapped dictionary for each entry. values are converted to null. - - The query result. Typically produced by a - .Retrieve method. - An enumerable that dequeues the results and returns a column - mapped dictionary for each entry - - - - - - - - - - Utility of transforming retrieved data into models matching the type parameter provided. - - The type of the model to create from the data. - - - - Maximum number of arrays to hold in the local array pool per bucket. - May also define how many records are pre buffered before transforming. - - - - - Buffers for transforming. - - - - - The type of . - - - - - The property names. - - - - - The column names. - - - - - Constructs a transformer using the optional field overrides. - - - - - Static utility for creating a Transformer . - - - - - - A sub class for processing the transformer results. - - - - - Constructs a processor. - - The transformer to use. - - - - Constructs a processor. - - The transformer to use. - The names of columns/properties to acquire. - - - - The transformer being used. - - - - - The resultant transform function. - - - - - Allows for deferred initialization. - - The column/property names to process. - - - - - - - Dequeues the results and transforms each one by one during enumeration. - - The results to process. - The array pool to return the buffers to. - Indicates whether the contents of the buffers should be cleared before reuse. - A dequeuing enumerable of the transformed results. - - - - - - - Transforms the results - - The reader to read from. - - An enumerable that transforms the results. - - - - Processes the data from the data table into a queue. Then dequeues the results and transforms each one by one during enumeration. - - The data to process. - If true, will clear the table after buffering the data. - A dequeuing enumerable of the transformed results. - - - - DbConnection factory implementation that accepts a factory function. - - - - - Constructs a DbConnectionFactory. - - The factory function. - - - - Creates a connection of from the underlying factory function. - - - - - Creates a Non-Generic DbConnectionFactory. - - The factory function. - A Non-Generic DbConnectionFactory - - - - Implicitly converts a connection factory function to a connection factory instance. - - The factory function. - - - - Generic connection factory implementation that accepts a factory function. - - The connection type. - - - - Constructs a DbConnectionFactory. - - The factory function. - - - - Creates a connection of from the underlying factory function. - - - - - Implicitly converts a connection factory function to a genetic-typed connection factory instance. - - - - - - An abstraction for executing commands on a database using best practices and simplified expressive syntax. - - - - Constructs a . - - - - Constructs a . - - - - Constructs a . - - - - Constructs a . - - - - Constructs a . - - - - - - - - Base class for developing expressive commands. - Includes methods for use with IDbConnection and IDbCommand types. - - The type of the connection to be used. - The type of the commands generated by the connection. - The type of reader created by the command. - The DB type enum to use for parameters. - The type of this class in order to facilitate proper expressive notation. - - - - Utility for simplifying param concatenation. - - The type of the enumerable. - The first value. - The remaining values. - - - - The connection provider to used to acquire connections. - - - - - The transaction to execute commands on if not using a connection factory. - - - - Constructs a . - The pool to acquire connections from. - The command type. - The SQL command. - The list of params - - - Constructs a . - The factory to generate connections from. - The command type. - The SQL command. - The list of params - - - Constructs a . - The connection to execute the command on. - The optional transaction to execute the command on. - The command type. - The SQL command. - The list of params - - - Constructs a . - The connection to execute the command on. - The command type. - The SQL command. - The list of params - - - Constructs a . - The optional transaction to execute the command on. - The command type. - The SQL command. - The list of params - - - - The command text or procedure name to use. - - - - - The command type. - - - - - The list of params to apply to the command before execution. - - - - - The command timeout value. - - - - - Creates the expected command type from the connection provided. - - The connection to create the command from. - The new command to use. - - - - The optional cancellation token to use with supported methods. - - - - - Sets the cancellation token. - - - - - Adds a parameter to the params list. - - The name of the parameter. - The value of the parameter. - The database type of the parameter. - This instance for use in method chaining. - - - - Adds a parameter to the params list. - - The name of the parameter. - The value of the parameter. - This instance for use in method chaining. - - - - Adds a parameter to the params list. - - The name of the parameter. - The value of the parameter. - The database type of the parameter. - This instance for use in method chaining. - - - - Adds a parameter to the params list. - - The name of the parameter. - The value of the parameter. - This instance for use in method chaining. - - - - Adds a parameter to the params list. - - The name of the parameter. - This instance for use in method chaining. - - - - Conditionally adds a parameter to the params list. - - The condition to add the param by. Only adds if true. - The name of the parameter. - The value of the parameter. - This instance for use in method chaining. - - - - Conditionally adds a parameter to the params list. - - The condition to add the param by. Only adds if true. - The name of the parameter. - The value of the parameter. - This instance for use in method chaining. - - - - Conditionally adds a parameter to the params list. - - The condition to add the param by. Only adds if true. - The name of the parameter. - The value of the parameter. - The database type of the parameter. - This instance for use in method chaining. - - - - Conditionally adds a parameter to the params list. - - The condition to add the param by. Only adds if true. - The name of the parameter. - The value of the parameter. - The database type of the parameter. - This instance for use in method chaining. - - - - Conditionally adds a parameter to the params list. - - The condition to add the param by. Only adds if true. - The name of the parameter. - This instance for use in method chaining. - - - - Handles adding the list of parameters to a new command. - - The command to add parameters to. - - - - Sets the timeout value. - - The number of seconds to wait before the connection times out. - This instance for use in method chaining. - - - - - - - - - - - - - - - - Validates and properly acquires the expected type of the reader. - - The actual type of the reader. - The reader to cast. - The expected reader. - - - - - - - - - - - - - - - - - - - - - - Calls ExecuteNonQuery on the underlying command but sets up a return parameter and returns that value. - - The value from the return parameter. - - - - Calls ExecuteNonQuery on the underlying command but sets up a return parameter and returns that value. - - The value from the return parameter. - - - - Calls ExecuteNonQueryAsync on the underlying command but sets up a return parameter and returns that value. - - The value from the return parameter. - - - - Calls ExecuteNonQueryAsync on the underlying command but sets up a return parameter and returns that value. - - The value from the return parameter. - - - - Calls ExecuteNonQuery on the underlying command. - - The integer response from the method. (Records updated.) - - - - Calls ExecuteScalar on the underlying command. - - The value returned from the method. - - - - Calls ExecuteScalar on the underlying command. - - The type expected. - The value returned from the method. - - - - Calls ExecuteScalar on the underlying command. - - The type expected. - The value returned from the method. - - - - A struct that represents the param to be created when the command is executed. - TDbType facilitates the difference between DbType and SqlDbType. - - - - - The name of the param. - - - - - The value of the param. - - - - - The DbType of the param. - - - - - - - - - - - - - - Equality operator. - - - - - Inequality operator. - - - - - An abstraction for executing commands on a database using best practices and simplified expressive syntax. - - - - Constructs a . - - - - Constructs a . - - - - Constructs a . - - - - Constructs a . - - - - Constructs a . - - - - - - - - An base class for executing commands on a database using best practices and simplified expressive syntax. - Includes methods for use with DbConnection and DbCommand types. - - The type of the connection to be used. - The type of the commands generated by the connection. - The type of reader created by the command. - The DB type enum to use for parameters. - The type of this class in order to facilitate proper expressive notation. - - - Constructs a an expressive command. - The pool to acquire connections from. - The command type. - The SQL command. - The list of params - - - Constructs a an expressive command. - The factory to generate connections from. - The command type. - The SQL command. - The list of params - - - Constructs a an expressive command. - The connection to execute the command on. - The optional transaction to execute the command on. - The command type. - The SQL command. - The list of params - - - Constructs a an expressive command. - The connection to execute the command on. - The command type. - The SQL command. - The list of params - - - Constructs a an expressive command. - The transaction to execute the command on. - The command type. - The SQL command. - The list of params - - - - By default (false), for async methods, the underlying iteration operation for a reader will be .Read() whenever possible. If set to true, .ReadAsync() will be used. - Using .ReadAsync() can introduce unexpected latency and additional CPU overhead. - This should only be set to true if there is a clear reason why and should be profiled before and after. - - - - - Sets the UseAsyncRead value. - - - - - Calls ExecuteNonQueryAsync on the underlying command. - - The integer response from the method. - - - - Calls ExecuteScalarAsync on the underlying command. - - The value returned from the method. - - - - Asynchronously executes scalar on the underlying command. - - The type expected. - The transform function for the result. - The value returned from the method. - - - - Asynchronously executes scalar on the underlying command and casts to the expected type. - - The type expected. - The value returned from the method. - - - - Asynchronously executes scalar on the underlying command. - - The type expected. - The transform function (task) for the result. - The value returned from the method. - - - - Asynchronously iterates a IDataReader and returns the each result until the count is met. - - The return type of the transform function. - The transform function to process each IDataRecord. - The maximum number of records before complete. - The behavior to use with the data reader. - The value from the transform. - - - - Reads the first column from every record and returns the results as a list.. - values are converted to null. - - The list of transformed records. - - - - Reads the first column from every record.. - values are converted to null. - - The enumerable of casted values. - - - - Asynchronously iterates all records within the current result set using an IDataReader and returns the desired results. - - The first ordinal to include in the request to the reader for each record. - The remaining ordinals to request from the reader for each record. - The QueryResult that contains all the results and the column mappings. - - - - Iterates all records within the current result set using an IDataReader and returns the desired results. - - The first column name to include in the request to the reader for each record. - The remaining column names to request from the reader for each record. - The QueryResult that contains all the results and the column mappings. - - - - Asynchronously returns all records via a transform function. - - The desired column names. - The behavior to use with the data reader. - A task containing the list of results. - - - - Asynchronously returns all records and iteratively attempts to map the fields to type T. - - The model type to map the values to (using reflection). - An override map of field names to column names where the keys are the property names, and values are the column names. - A task containing the list of results. - - - - Asynchronously returns all records and iteratively attempts to map the fields to type T. - - The model type to map the values to (using reflection). - An override map of field names to column names where the keys are the property names, and values are the column names. - A task containing the list of results. - - - - Asynchronously iterates all records within the first result set using an IDataReader and returns the results. - - The QueryResult that contains all the results and the column mappings. - - - - Asynchronously iterates all records within the current result set using an IDataReader and returns the desired results. - - The ordinals to request from the reader for each record. - The QueryResult that contains all the results and the column mappings. - - - - Iterates all records within the first result set using an IDataReader and returns the desired results as a list of Dictionaries containing only the specified column values. - - The column names to select. - Orders the results arrays by ordinal. - The QueryResult that contains all the results and the column mappings. - - - - Asynchronously returns all records and iteratively attempts to map the fields to type T. - - The model type to map the values to (using reflection). - An override map of field names to column names where the keys are the property names, and values are the column names. - A task containing the list of results. - - - - Core non-DB-specific extensions for retrieving data from a command using best practices. - - - - - - - - Shortcut for adding a command parameter. - - The command to add a parameter to. - The name of the parameter. - The value of the parameter. - The of the parameter. - The direction of the parameter. - The created . - - - - Shortcut for adding a typed (non-input) command parameter. - - - - - - Shortcut for adding command a typed return parameter. - - - - - - Shortcut for adding command a return parameter. - - - - - - Iterates all records using an and returns the desired results as a list. - - The return type of the transform function. - The to generate a reader from. - The behavior to use with the data reader. - The transform function to process each . - - - - - - - - - - - - - Asynchronously iterates all records using an and returns the desired results as a list. - - The return type of the transform function. - The to generate a reader from. - The behavior to use with the data reader. - The transform function to process each . - If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. - The cancellation token. - A task containing a list of all results. - - - - - - - - - - Iterates all records using an and returns the desired results as an array. - - The return type of the transform function. - The to generate a reader from. - The behavior to use with the data reader. - The transform function to process each . - - - - - - - - - - - - - Loads all data from a command through an into a DataTable. - - The to generate a reader from. - The behavior to use with the data reader. - The resultant DataTable. - - - - Loads all data from a command through an into a DataTables. - Calls .NextResult() to check for more results. - - The to generate a reader from. - The behavior to use with the data reader. - The resultant list of DataTables. - - - - Executes a reader on a command with a handler function. - - The to generate a reader from. - The handler function for each . - The behavior to use with the data reader. - - - - Executes a reader on a command with a transform function. - - The default behavior will open a connection, execute the reader and close the connection it if was not already open. - The return type of the transform function. - The to generate a reader from. - The transform function for each . - The behavior to use with the data reader. - The result of the transform. - - - - Asynchronously executes a reader on a command with a handler function. - - The to generate a reader from. - The handler function for each . - The behavior to use with the data reader. - The cancellation token. - - - - Asynchronously executes a reader on a command with a handler function. - - The to generate a reader from. - The handler function for each . - The behavior to use with the data reader. - The cancellation token. - - - - The to generate a reader from. - The handler function for each . - The behavior to use with the data reader. - The cancellation token. - - - - - Asynchronously executes a reader on a command with a transform function. - - The return type of the transform function. - The to generate a reader from. - The transform function for each . - The behavior to use with the data reader. - The cancellation token. - The result of the transform. - - - The to generate a reader from. - The transform function for each . - The behavior to use with the data reader. - The cancellation token. - - - - - - - - - - - Executes a reader on a command with a transform function. - - The return type of the transform function applied to each record. - The type returned by the selector. - The to generate a reader from. - The behavior to use with the data reader. - The transform function for each . - Provides an IEnumerable<TEntity> to select individual results by. - - - - - Iterates an on a command with a handler function. - - The to generate a reader from. - The behavior to use with the data reader. - The handler function for each . - - - - - - - Iterates an on a command while the predicate returns true. - - The to generate a reader from. - The handler function that processes each and decides if iteration should continue. - The behavior to use with the data reader. - - - - Asynchronously iterates all records from an . - - The to generate a reader from. - The handler function for each . - The behavior to use with the data reader. - If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. - The cancellation token. - - - - - - - - - - - - - Asynchronously iterates an on a command while the predicate returns true. - - The to generate a reader from. - The handler function that processes each and decides if iteration should continue. - The behavior to use with the data reader. - If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. - The cancellation token. - - - - Iterates an and returns the first result through a transform function. - - Throws an if there are no results. - The return type of the transform function. - The to generate a reader from. - The transform function to process each . - The behavior to use with the data reader. - The value from the transform. - - - Returns default(T) if thre are no results. - - - - Throws an if there is anything other than a single result. - - - - - Returns default(T) if thre are no results. - Throws an if there is more than one result. - - - - - - Iterates an and returns the first number of results defined by the count. - - The return type of the transform function. - The to generate a reader from. - The maximum number of records to return. - The transform function to process each . - The behavior to use with the data reader. - The results from the transform limited by the take count. - - - - Iterates an and skips the first number of results defined by the count. - - The return type of the transform function. - The to generate a reader from. - The number of records to skip. - The transform function to process each . - The behavior to use with the data reader. - The results from the transform after the skip count. - - - - Iterates an and skips by the skip parameter returns the maximum remaining defined by the take parameter. - - The return type of the transform function. - The to generate a reader from. - The number of entries to skip before starting to take results. - The maximum number of records to return. - The transform function to process each . - The behavior to use with the data reader. - The results from the skip, transform and take operation. - - - values are converted to null. - - - - - - - The expected type of the first ordinal. - - - - values are converted to null. - - - - - Reads the first column from every record. - - The expected type of the first ordinal. - Any values are then converted to null and casted to type . - The to generate a reader from. - The behavior to use with the data reader. - If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. - The cancellation token. - The enumerable of casted values. - - - - Core non-DB-specific extensions for database connections. - - - Core non-DB-specific extensions for acquiring and operating on different connection factories. - - - Core non-DB-specific extensions for acquiring and operating on different connection factories. - - - Core non-DB-specific extensions for acquiring and operating on different connection factories. - - - - - Shortcut for creating an from any . - - The connection to create a command from. - The command type. , , or . - The command text or stored procedure name to use. - The number of seconds to wait before the command times out. - - - - Shortcut for creating a text from any . - - - - - - Shortcut for creating a stored procedure from any . - - - - - - Shortcut for creating an from any . - - - - - - Shortcut for creating a text from any . - - - - - - Shortcut for creating a stored procedure from any .s - - - - - The prior connection state. - - - - - If the connection isn't open, opens the connection.
- If the connection is in neither open or close, first closes the connection, then opens it. -
- The connection to transact with. - If true (default) will retain the context after opening. - An cancellation token to cancel opening. - A task containing the prior connection state. -
- - - - - - - - - Creates an for subsequent configuration and execution. - - The connection to execute the command on. - The command text or stored procedure name to use. - The command type. The default is . - - - - Creates an for subsequent configuration and execution. - - The transaction to execute the command on. - The command text or stored procedure name to use. - The command type. The default is . - - - - Creates an for subsequent configuration and execution. - - - - - - Creates an for subsequent configuration and execution. - - The transaction to execute the command on. - The command text or stored procedure name to use. - The command type. Default = CommandType.Text. - - - - Creates an with command type set to for subsequent configuration and execution. - - The connection to execute the command on. - The stored procedure name to use. - - - - Creates an with command type set to for subsequent configuration and execution. - - The transaction to execute the command on. - The stored procedure name to use. - - - - Creates an with command type set to for subsequent configuration and execution. - - - - - - Creates an with command type set to for subsequent configuration and execution. - - - - - - Creates an for subsequent configuration and execution. - - The connection factory to generate connections and subsequently commands from. - The command text or stored procedure name to use. - The command type. Default = CommandType.Text. - - - - Creates an with command type set to for subsequent configuration and execution. - - The connection factory to generate connections and subsequently commands from. - The stored procedure name to use. - - - - Creates an for subsequent configuration and execution. - - The connection pool to take connections from. - The command text or stored procedure name to use. - The command type. Default = CommandType.Text. - - - - Creates an with command type set to for subsequent configuration and execution. - - The connection pool to take connections from. - The stored procedure name to use. - - - - Creates an for subsequent configuration and execution. - - The connection type. - The connection factory to generate connections and subsequently commands from. - The command text or stored procedure name to use. - The command type. Default = CommandType.Text. - - - - Creates an with command type set to for subsequent configuration and execution. - - The connection type. - The connection factory to generate connections and subsequently commands from. - The stored procedure name to use. - The resultant ExpressiveDbCommand. - - - - Creates an for subsequent configuration and execution. - - The connection type. - The connection pool to take connections from. - The command text or stored procedure name to use. - The command type. Default = CommandType.Text. - - - - Creates an with command type set to for subsequent configuration and execution. - - The connection type. - The connection pool to take connections from. - The stored procedure name to use. - - - - Creates an for subsequent configuration and execution. - - The connection factory to generate a commands from. - The command text or stored procedure name to use. - The command type. Default = CommandType.Text. - - - - Creates an with command type set to for subsequent configuration and execution. - - The connection factory to generate a commands from. - The stored procedure name to use. - - - - Creates an for subsequent configuration and execution. - - The connection type. - The connection factory to generate a commands from. - The command text or stored procedure name to use. - The command type. Default = CommandType.Text. - - - - Creates an with command type set to for subsequent configuration and execution. - - The connection type. - The connection factory to generate a commands from. - The stored procedure name to use. - - - The type returned from the action. - The value from the action. - - - - - Generates a connection. Ensures it's open. Invokes the action.
- Ensures the connection is disposed of when the action is complete. -
- Useful for single-line operations. - The connection factory to generate connections from. - The action to execute. -
- - The connection type. - The type returned from the action. - The connection factory to generate connections from. - The action to execute. - The value from the action. - - - - The connection type. - The connection factory to generate connections from. - The action to execute. - - - - The type returned from the action. - The value from the action. - - - - - Acquires a connection from the pool. Ensures it's open. Invokes the action.
- Ensures the connection is returned to the pool when the action is complete. -
- The connection pool to acquire connections from. - The action to execute. - -
- - The connection type. - The type returned from the action. - The value from the action. - - - - The connection type. - - - - The value from the action. - - - - - - - The value from the action. - - - - - Generates a connection. Ensures it's open. Invokes the action.
- Ensures the connection is disposed of when the action is complete. -
- The connection factory to generate connections from. - The action to execute. - The cancellation token. - -
- - The connection type. - The type returned from the action. - - The value from the action. - - - - - - The value from the action. - - - - - Acquires a connection from the pool. Ensures it's open. Invokes the action.
- Ensures the connection is returned to the pool when the action is complete. -
- The connection pool to acquire connections from. - The action to execute. - The cancellation token. - -
- - The connection type. - The type returned from the action. - The value from the action. - - - - The connection type. - - - - - Generates a connection. Ensures it's open. Invokes the action. - Ensures the connection is disposed of when the action is complete. - Useful for single-line operations. - - The connection type. - The type returned from the action. - The connection factory to generate connections from. - The action to execute. - An optional cancellation token. - The value from the action. - - - - - - The type returned from the action. - The value from the action. - - - - - Generates a connection and executes the action within a using statement. - - Useful for single-line operations. - The connection factory to generate connections from. - The action to execute. - - - The connection type. - The type returned from the action. - The value from the action. - - - - The connection type. - - - - The type returned from the action. - The value from the action. - - - - - Acquires a connection from the pool, returning it after the action is complete. - Useful for single-line operations. - - The connection pool to acquire connections from. - The action to execute. - - - The connection type. - The type returned from the action. - The value from the action. - - - - The connection type. - - - - The connection type. - The type returned from the action. - The value from the action. - - - - The connection type. - - - - The type returned from the action. - The value from the action. - - - - - - - - - - The connection type. - - - - The type returned from the action. - The value from the action. - - - - - - - The connection type. - The type returned from the action. - The value from the action. - - - - The connection type. - - - - The connection type. - The type returned from the action. - - - - The connection type. - - - - - Extension methods for Data Readers. - - - - - Iterates all records from an . - - The IDataReader to iterate. - The handler function for each . - If true, when cancelled, may exit the iteration via an exception. Otherwise when cancelled will simply stop iterating and return without exception. - An optional cancellation token for stopping the iteration. - - - - - - - Iterates all records from an . - - The IDataReader to iterate. - The handler function for each . - If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. - Optional cancellation token. - - - - - - - - - - - - - - - - - - The reader to enumerate. - The limited set of ordinals to include. If none are specified, the returned objects will be empty. - The array pool to acquire buffers from. - - - - The reader to enumerate. - The first ordinal to include in the request to the reader for each record. - The remaining ordinals to request from the reader for each record. - - - - - Provides an enumerable for iterating all the remaining values of the current result set of a data reader. - - values are retained. - The reader to enumerate. - The array pool to acquire buffers from. - The first ordinal to include in the request to the reader for each record. - The remaining ordinals to request from the reader for each record. - An enumerable of the values returned from a data reader. - - - - - - - Iterates records from an and passes the IDataRecord to a transform function. - - The return type of the transform function. - The reader to iterate. - The transform function to process each . - A cancellation token for stopping the iteration. - If true, when cancelled, may exit the iteration via an exception. Otherwise when cancelled will simply stop iterating and return without exception. - An enumerable used to iterate the results. - - - - - - - Shortcut for .Iterate(transform).ToList(); - - The return type of the transform function. - The IDataReader to iterate. - The transform function to process each . - Optional cancellation token. - A list of the transformed results. - - - - Asynchronously iterates all records using the data reader and returns the desired results as a list. - - The return type of the transform function. - The SqlDataReader to read from. - The transform function to process each . - Optional cancellation token. - A task containing a list of all results. - - - - - - - Shortcut for .Select(transform).ToArray(); - - The return type of the transform function. - The IDataReader to iterate. - The transform function to process each . - An array of the transformed results. - - - - Shortcut for .Select(transform).ToImmutableArray(); - - The return type of the transform function. - The IDataReader to iterate. - The transform function to process each . - An immutable array of the transformed results. - - - - Loads all remaining data from an into a DataTable. - - The IDataReader to load data from. - The resultant DataTable. - - - - Loads all data from a command through an into a DataTables. - Calls .NextResult() to check for more results. - - The IDataReader to load data from. - The resultant list of DataTables. - - - - Iterates an while the predicate returns true. - - The to iterate. - The handler function that processes each and decides if iteration should continue. - If true, when cancelled, may exit the iteration via an exception. Otherwise when cancelled will simply stop iterating and return without exception. - An optional cancellation token for stopping the iteration. - - - - - - - - - The DbDataReader to load data from. - The handler function that processes each and decides if iteration should continue. - If true will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. - Optional cancellation token. - - - - - - - The to iterate. - The handler function that processes each and decides if iteration should continue. - If true will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results. - Optional cancellation token. - - - - - Reads the first column values from every record. - values are then converted to null. - - The enumerable first ordinal values. - - - - Reads the first column values from every record. - Any values are then converted to null and casted to type T0; - - The enumerable of casted values. - - - - Reads the first column values from every record. - Any values are then converted to null and casted to type T0; - - The enumerable of casted values. - - - - Reads the first column values from every record. - values are converted to null. - - The IDataReader to iterate. - If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. - Optional cancellation token. - The list of values. - - - - Reads the first column values from every record. - Any values are then converted to null and casted to type T0; - - The IDataReader to iterate. - If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. - Optional cancellation token. - The enumerable of casted values. - - - - Extension methods for IDataRecord access. - - - - - - - - Returns an array of values with the specified field count. - - The reader to get column names from. - The size of the resultant array. - The array of values. - - - The reader to get column names from. - The minimum size of the resultant array. - The array pool to acquire buffers from. - - - - The reader to get column names from. - The array pool to acquire buffers from. - - - - - Returns all the column names for the current result set. - - The reader to get column names from. - The enumerable of column names. - - - - Returns all the column names for the current result set. - - The reader to get column names from. - The array of column names. - - - - Returns all the column names for the current result set by index provided by the ordinals. - - The reader to get column names from. - The list (and order) of ordinals to look up. - The array of column names. - - - - Returns the (name,ordinal) mapping for current result set. - - The reader to get column names from. - An enumerable of the mappings. - - - - Returns an array of name to ordinal mappings. - - The to query the ordinals from. - The requested column names. - An enumerable of the mappings. - - - - Returns an array of name to ordinal mappings. - - The to query the ordinals from. - The requested column names. - If true, will order the results by ordinal ascending. - An enumerable of the mappings. - - - - Returns an array of name to ordinal mappings. - - The to query the ordinals from. - The requested column names. - If true, will order the results by ordinal ascending. - - - - - Produces an array of values based upon their ordinal positions. - - - - - - Produces a selective set of column values based upon the desired ordinal positions. - - The to query. - The set of ordinals to query. - An enumerable of values matching the ordinal positions requested. - - - - - - The to query. - The first ordinal to query. - The remaining set of ordinals to query. - - - - The to query. - The list of ordinals to query. - The target to store the values. - The provided span, updated with values matching the ordinal positions requested. - - - - The provided list, updated with values matching the ordinal positions requested. - - - - An array of values matching the ordinal positions requested. - - - - - Returns an enumerable of name to ordinal mappings. - - The to query the ordinals from. - The requested column names. - If true, will order the results by ordinal ascending. - The enumerable of name to ordinal mappings. - - - - Returns an array of name to ordinal mappings. - - The to query the ordinals from. - The requested column names. - If true, will order the results by ordinal ascending. - The array of name to ordinal mappings. - - - - Returns all the data type names for the columns of current result set. - - The reader to get data type names from. - The enumerable of data type names. - - - - Returns all the data type names for the columns of current result set. - - The reader to get data type names from. - The array of data type names. - - - - Returns the specified column data of as a Dictionary. - - values are converted to null. - The to extract values from. - The column ids and resultant names to query. - The resultant Dictionary of values. - - - - Returns the specified column data of as a Dictionary. - - values are converted to null. - The to extract values from. - The column ids and resultant names to query. - The resultant Dictionary of values. - - - - Returns the specified column data of as a Dictionary. - - values are converted to null. - The to extract values from. - The column ids and resultant names to query. - The resultant Dictionary of values. - - - - Returns the specified column data of as a Dictionary. - - values are converted to null. - The to extract values from. - The column ids and resultant names to query. - The resultant Dictionary of values. - - - - Returns the specified column data of as a Dictionary. - - values are converted to null. - The to extract values from. - The column names to query. - The resultant Dictionary of values. - - - - Returns the specified column data of as a Dictionary. - - values are converted to null. - The to extract values from. - The column names to query. - The resultant Dictionary of values. - - - - Returns the specified column data of as a Dictionary. - - values are converted to null. - The to extract values from. - The column names to query. - The resultant Dictionary of values. - - - - Core non-DB-specific extensions for retrieving data from a command using best practices. - - - - - Iterates a reader on a command with a handler function. - - The IExecuteReader to iterate. - The handler function for each IDataRecord. - The behavior to use with the data reader. - - - - Iterates a reader on a command while the handler function returns true. - - The IExecuteReader to iterate. - The handler function for each IDataRecord. - The behavior to use with the data reader. - - - - Iterates a reader on a command with a handler function. - - The IExecuteReader to iterate. - The handler function for each IDataRecord. - The behavior to use with the data reader. - - - - Iterates a reader on a command while the handler function returns true. - - The IExecuteReader to iterate. - The handler function for each IDataRecord. - The behavior to use with the data reader. - - - - Iterates a reader on a command while the handler function returns true. - - The IExecuteReader to iterate. - The handler function for each IDataRecord. - The behavior to use with the data reader. - - - - Executes a reader on a command with a transform function. - - The return type of the transform function applied to each record. - The type returned by the selector. - The IExecuteReader to iterate. - The transform function for each IDataRecord. - Provides an IEnumerable<TEntity> to select individual results by. - The behavior to use with the data reader. - The result of the transform. - - - - Iterates an IDataReader and returns the first result through a transform function. Throws if none. - - The return type of the transform function. - The IExecuteReader to iterate. - The transform function to process each IDataRecord. - The value from the transform. - - - - Iterates an IDataReader and returns the first result through a transform function. Returns default(T) if none. - - The return type of the transform function. - The IExecuteReader to iterate. - The transform function to process each IDataRecord. - The value from the transform. - - - - Iterates a IDataReader and returns the first result through a transform function. Throws if none or more than one entry. - - The return type of the transform function. - The IExecuteReader to iterate. - The transform function to process each IDataRecord. - The value from the transform. - - - - Iterates an IDataReader and returns the first result through a transform function. Returns default(T) if none. Throws if more than one entry. - - The return type of the transform function. - The IExecuteReader to iterate. - The transform function to process each IDataRecord. - The value from the transform. - - - - Iterates an IDataReader and returns the first number of results defined by the count. - - The return type of the transform function. - The IExecuteReader to iterate. - The maximum number of records to return. - The transform function to process each IDataRecord. - The results from the transform limited by the take count. - - - - Iterates an IDataReader and skips the first number of results defined by the count. - - The return type of the transform function. - The IExecuteReader to iterate. - The number of records to skip. - The transform function to process each IDataRecord. - The results from the transform after the skip count. - - - - Iterates an IDataReader and skips by the skip parameter returns the maximum remaining defined by the take parameter. - - The return type of the transform function. - The IExecuteReader to iterate. - The number of entries to skip before starting to take results. - The maximum number of records to return. - The transform function to process each IDataRecord. - The results from the skip, transform and take operation. - - - - Converts all IDataRecords into a list using a transform function. - - The expected return type. - The IExecuteReader to iterate. - The transform function. - The command behavior for once the command the reader is complete. - The list of transformed records. - - - - Converts all IDataRecords into an array using a transform function. - - The expected return type. - The IExecuteReader to iterate. - The transform function. - The command behavior for once the command the reader is complete. - The array of transformed records. - - - - Converts all IDataRecords into an immutable array using a transform function. - - The expected return type. - The IExecuteReader to iterate. - The transform function. - The command behavior for once the command the reader is complete. - The array of transformed records. - - - - Iterates all records within the first result set using an IDataReader and returns the results. - values are left unchanged (retained). - - The IExecuteReader to iterate. - The QueryResult that contains all the results and the column mappings. - - - - Iterates all records within the current result set using an IDataReader and returns the desired results. - values are left unchanged (retained). - - The IExecuteReader to iterate. - The ordinals to request from the reader for each record. - The QueryResult that contains all the results and the column mappings. - - - - Iterates all records within the current result set using an IDataReader and returns the desired results. - values are left unchanged (retained). - - The IExecuteReader to iterate. - The first ordinal to include in the request to the reader for each record. - The remaining ordinals to request from the reader for each record. - The QueryResult that contains all the results and the column mappings. - - - - Iterates all records within the first result set using an IDataReader and returns the desired results as a list of Dictionaries containing only the specified column values. - values are left unchanged (retained). - - The IExecuteReader to iterate. - The column names to select. - The QueryResult that contains all the results and the column mappings. - - - - Iterates all records within the current result set using an IDataReader and returns the desired results. - values are left unchanged (retained). - - The IExecuteReader to iterate. - The first column name to include in the request to the reader for each record. - The remaining column names to request from the reader for each record. - The QueryResult that contains all the results and the column mappings. - - - - Iterates each record and attempts to map the fields to type T. - Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. - - The IExecuteReader to iterate. - The model type to map the values to (using reflection). - An optional override map of field names to column names where the keys are the property names, and values are the column names. - The enumerable to pull the transformed results from. - - - - Iterates each record and attempts to map the fields to type T. - Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. - - The IExecuteReader to iterate. - The model type to map the values to (using reflection). - An optional override map of field names to column names where the keys are the property names, and values are the column names. - The enumerable to pull the transformed results from. - - - - Iterates each record and attempts to map the fields to type T. - Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. - - The IExecuteReader to iterate. - The model type to map the values to (using reflection). - An optional override map of field names to column names where the keys are the property names, and values are the column names. - The enumerable to pull the transformed results from. - - - - Reads the first column from every record and returns the results as a list.. - values are converted to null. - - The IExecuteReader to iterate. - The list of transformed records. - - - - Reads the first column from every record.. - values are converted to null. - - The IExecuteReader to iterate. - The enumerable of casted values. - - - - Imports all data using an IDataReader into a DataTable. - - The IExecuteReader to iterate. - The resultant DataTable. - - - - Loads all data from a command through an IDataReader into a DataTables. - Calls .NextResult() to check for more results. - - The IExecuteReader to iterate. - The resultant list of DataTables. - - - - Core non-DB-specific extensions for building a command and retrieving data using best practices. - - - - - Iterates each record and attempts to map the fields to type T. - - The model type to map the values to (using reflection). - The IDataReader to read results from. - An optional override map of field names to column names where the keys are the property names, and values are the column names. - The enumerable to pull the transformed results from. - - - - Iterates each record and attempts to map the fields to type T. - - The model type to map the values to (using reflection). - The IDataReader to read results from. - An optional override map of field names to column names where the keys are the property names, and values are the column names. - The enumerable to pull the transformed results from. - - - - Iterates each record and attempts to map the fields to type T. - - The model type to map the values to (using reflection). - The IDataReader to read results from. - An optional override map of field names to column names where the keys are the property names, and values are the column names. - The enumerable to pull the transformed results from. - - - - Iterates each record and attempts to map the fields to type T. - Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. - - The model type to map the values to (using reflection). - The IDataReader to read results from. - An optional override map of field names to column names where the keys are the property names, and values are the column names. - The enumerable to pull the transformed results from. - - - - Iterates each record and attempts to map the fields to type T. - Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. - - The model type to map the values to (using reflection). - The IDataReader to read results from. - An optional override map of field names to column names where the keys are the property names, and values are the column names. - The enumerable to pull the transformed results from. - - - - Iterates each record and attempts to map the fields to type T. - Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. - - The model type to map the values to (using reflection). - The IDataReader to read results from. - An optional override map of field names to column names where the keys are the property names, and values are the column names. - The enumerable to pull the transformed results from. - - - - Iterates each record and attempts to map the fields to type T. - Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. - - The model type to map the values to (using reflection). - The command to generate a reader from. - An optional override map of field names to column names where the keys are the property names, and values are the column names. - The enumerable to pull the transformed results from. - - - - Iterates each record and attempts to map the fields to type T. - Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. - - The model type to map the values to (using reflection). - The command to generate a reader from. - An optional override map of field names to column names where the keys are the property names, and values are the column names. - The enumerable to pull the transformed results from. - - - - Iterates each record and attempts to map the fields to type T. - Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. - - The model type to map the values to (using reflection). - The command to generate a reader from. - An optional override map of field names to column names where the keys are the property names, and values are the column names. - The enumerable to pull the transformed results from. - - - - Asynchronously returns all records and iteratively attempts to map the fields to type T. - - The model type to map the values to (using reflection). - The IDataReader to read results from. - An override map of field names to column names where the keys are the property names, and values are the column names. - If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. - Optional cancellation token. - A task containing the list of results. - - - - Asynchronously returns all records and iteratively attempts to map the fields to type T. - - The model type to map the values to (using reflection). - The IDataReader to read results from. - An override map of field names to column names where the keys are the property names, and values are the column names. - The cancellation token. - A task containing the list of results. - - - - Asynchronously returns all records and iteratively attempts to map the fields to type T. - - The model type to map the values to (using reflection). - The IDataReader to read results from. - An override map of field names to column names where the keys are the property names, and values are the column names. - If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. - Optional cancellation token. - A task containing the list of results. - - - - Asynchronously returns all records and iteratively attempts to map the fields to type T. - - The model type to map the values to (using reflection). - The IDataReader to read results from. - An override map of field names to column names where the keys are the property names, and values are the column names. - Optional cancellation token. - A task containing the list of results. - - - - Asynchronously returns all records and iteratively attempts to map the fields to type T. - - The model type to map the values to (using reflection). - The IDataReader to read results from. - An override map of field names to column names where the keys are the property names, and values are the column names. - A task containing the list of results. - - - - Asynchronously returns all records and iteratively attempts to map the fields to type T. - - The model type to map the values to (using reflection). - The IDataReader to read results from. - The cancellation token. - An override map of field names to column names where the keys are the property names, and values are the column names. - A task containing the list of results. - - - - Asynchronously returns all records and iteratively attempts to map the fields to type T. - - The model type to map the values to (using reflection). - The command to generate a reader from. - An override map of field names to column names where the keys are the property names, and values are the column names. - If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. - Optional cancellation token. - A task containing the list of results. - - - - Asynchronously returns all records and iteratively attempts to map the fields to type T. - - The model type to map the values to (using reflection). - The command to generate a reader from. - An override map of field names to column names where the keys are the property names, and values are the column names. - The cancellation token. - A task containing the list of results. - - - - Asynchronously returns all records and iteratively attempts to map the fields to type T. - - The model type to map the values to (using reflection). - The command to generate a reader from. - An override map of field names to column names where the keys are the property names, and values are the column names. - If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. - Optional cancellation token. - A task containing the list of results. - - - - Asynchronously returns all records and iteratively attempts to map the fields to type T. - - The model type to map the values to (using reflection). - The command to generate a reader from. - An override map of field names to column names where the keys are the property names, and values are the column names. - The cancellation token. - A task containing the list of results. - - - - Asynchronously returns all records and iteratively attempts to map the fields to type T. - - The model type to map the values to (using reflection). - The command to generate a reader from. - An override map of field names to column names where the keys are the property names, and values are the column names. - A task containing the list of results. - - - - Asynchronously returns all records and iteratively attempts to map the fields to type T. - - The model type to map the values to (using reflection). - The command to generate a reader from. - A cancellation token. - An override map of field names to column names where the keys are the property names, and values are the column names. - A task containing the list of results. - - - - Loads all data into a DataTable before Iterates each record and attempts to map the fields to type T. - Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. - - The model type to map the values to (using reflection). - The IDataReader to read results from. - An optional override map of field names to column names where the keys are the property names, and values are the column names. - The enumerable to pull the transformed results from. - - - - Loads all data into a DataTable before Iterates each record and attempts to map the fields to type T. - Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. - - The model type to map the values to (using reflection). - The command to generate a reader from. - An optional override map of field names to column names where the keys are the property names, and values are the column names. - The enumerable to pull the transformed results from. - - - - - - - Iterates all records within the current result set using an and returns the desired results. - - values are left unchanged (retained). - The to read results from. - The ordinals to request from the reader for each record. - The query result that contains all the results and the column mappings. - - - The to read results from. - The first ordinal to include in the request to the reader for each record. - The remaining ordinals to request from the reader for each record. - - - - The to read results from. - The column names to select. - - - - The to read results from. - The first column name to include in the request to the reader for each record. - The remaining column names to request from the reader for each record. - - - - The to generate the reader from. - The flags to use with the data reader. - - - - - Executes a reader and iterates all records within the remaining result set using an and returns the desired results. - - - values are left unchanged (retained). - The default behavior will open a connection, execute the reader and close the connection it if was not already open. - The to generate the reader from. - The ordinals to request from the reader for each record. - - - - The to generate the reader from. - The first ordinal to include in the request to the reader for each record. - The remaining ordinals to request from the reader for each record. - - - - The to generate the reader from. - The column names to select. - - - - The to generate the reader from. - The first column name to include in the request to the reader for each record. - The remaining column names to request from the reader for each record. - - - - - - - - - - The reader to enumerate. - The limited set of ordinals to include. If none are specified, the returned objects will be empty. - If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. - The cancellation token. - - - - - - - - - - The reader to enumerate. - A cancellation token. - The first ordinal to include in the request to the reader for each record. - The remaining ordinals to request from the reader for each record. - - - - - Asynchronously enumerates all records within the current result set using an and returns the desired results. - - The to read results from. - The column names to select. - Orders the results arrays by ordinal. - If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. - The cancellation token. - - - - - - - - - - - - - The to read results from. - The cancellation token. - The first column name to include in the request to the reader for each record. - The remaining column names to request from the reader for each record. - - - - - Asynchronously executes a reader and enumerates all the remaining values of the current result set and returns the desired results. - - The command to generate a reader from. - If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. - Optional cancellation token. - - - - - - - The command to generate a reader from. - The limited set of ordinals to include. If none are specified, the returned objects will be empty. - If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. - The cancellation token. - - - - - - - - - - - The command to generate a reader from. - The cancellation token. - The first ordinal to include in the request to the reader for each record. - The remaining ordinals to request from the reader for each record. - - - - The to generate a reader from. - The column names to select. - Orders the results arrays by ordinal. - If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. - The cancellation token. - - - - - - - - - - - - - The to generate a reader from. - The cancellation token. - The first column name to include in the request to the reader for each record. - The remaining column names to request from the reader for each record. - - - - - Any values are yielded as null. - - The source enumerable. - - - - Returns a copy of with any values converted to null. - - The source values. - - - - Returns a copy of the contents of this span as an array with any values converted to null. - - - - - - - - - Replaces any in the with null; - - The source values. - - - - - - - Generic enumerable extension for . - - The column collection. - An enumerable of s. - - - - Generic enumerable extension for . - - The row collection. - An enumerable of s. - - - - - - - - - - Loads all data into a queue before iterating (dequeuing) the results as type . - - The model type to map the values to (using reflection). - The to read from. - An optional override map of field names to column names where the keys are the property names, and values are the column names. - Clears the source table before providing the enumeration. - An enumerable used to iterate the results. - - - - Useful extension for dequeuing items from a queue. - Not thread safe but queueing/dequeuing items in between items is supported. - - Return type of the source queue - An enumerable of the items contained within the queue. - - - - Core non-DB-specific extensions for database transactions. - - - - - Shortcut for creating an IDbCommand from any IDbTransaction. - - The transaction to create a command from. - The command type. Text, StoredProcedure, or TableDirect. - The command text or stored procedure name to use. - The number of seconds to wait before the command times out. - The created SqlCommand. - - - - Shortcut for creating a text IDbCommand from any IDbTransaction. - - The transaction to create a command from. - The command text or stored procedure name to use. - The number of seconds to wait before the command times out. - The created SqlCommand. - - - - Shortcut for creating a stored procedure IDbCommand from any IDbTransaction. - - The transaction to create a command from. - The command text or stored procedure name to use. - The number of seconds to wait before the command times out. - The created SqlCommand. - - - - Shortcut for creating an DbCommand from any DbTransaction. - - The transaction to create a command from. - The command type. Text, StoredProcedure, or TableDirect. - The command text or stored procedure name to use. - The number of seconds to wait before the command times out. - The created SqlCommand. - - - - Shortcut for creating a text DbCommand from any DbTransaction. - - The transaction to create a command from. - The command text or stored procedure name to use. - The number of seconds to wait before the command times out. - The created SqlCommand. - - - - Shortcut for creating a stored procedure DbCommand from any DbTransaction. - - The transaction to create a command from. - The command text or stored procedure name to use. - The number of seconds to wait before the command times out. - The created SqlCommand. - - - - Begins a transaction before executing the action. Commits if there are no exceptions, the 'Commit' value from the action is true and the optional cancellation token has not been cancelled. Otherwise rolls-back the transaction. - - The value returned from the action. - The connection to transact with. - The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. - The isolation level for the transaction. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The value returned from the conditional action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions, the conditional action returns true, and the optional cancellation token is not cancelled. Otherwise rolls-back the transaction. - - The connection to transact with. - The handler to execute while a transaction is pending. Returning true signals to commit the transaction. - The isolation level for the transaction. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - True if committed. - - - - Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - - The value returned from the action. - The connection to transact with. - The handler to execute while a transaction is pending. - The isolation level for the transaction. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The value of the action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - - The connection to transact with. - The handler to execute while a transaction is pending. - The isolation level for the transaction. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - - - - Begins a transaction before executing the action. Commits if there are no exceptions, the 'Commit' value from the action is true, and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - - The value returned from the action. - The connection to transact with. - The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. - The isolation level for the transaction. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The value of the awaited action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions, the 'Commit' value from the action is true, and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - - The connection to transact with. - The handler to execute while a transaction is pending. Returning true signals to commit the transaction. - The isolation level for the transaction. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The value of the awaited action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - - The value returned from the action. - The connection to transact with. - The handler to execute while a transaction is pending. - The isolation level for the transaction. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The value of the awaited action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - - The connection to transact with. - The handler to execute while a transaction is pending. - The isolation level for the transaction. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - - - - Begins a transaction before executing the action. Commits if there are no exceptions and 'Commit' value from the action is true. Otherwise rolls-back the transaction. - - The value returned from the action. - The connection to transact with. - The isolation level for the transaction. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. - The value returned from the conditional action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions and the conditional action returns true. Otherwise rolls-back the transaction. - - The connection to transact with. - The isolation level for the transaction. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The handler to execute while a transaction is pending. Returning true signals to commit the transaction. - True if committed. - - - - Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. - - The value returned from the action. - The connection to transact with. - The isolation level for the transaction. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The handler to execute while a transaction is pending. - The value of the action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. - - The connection to transact with. - The isolation level for the transaction. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The handler to execute while a transaction is pending. - - - - Begins a transaction before executing the action. Commits if there are no exceptions and the 'Commit' value from the action is true. Otherwise rolls-back the transaction. - - The value returned from the action. - The connection to transact with. - The isolation level for the transaction. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. - The value of the awaited action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions and the value from the action is true. Otherwise rolls-back the transaction. - - The connection to transact with. - The isolation level for the transaction. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The handler to execute while a transaction is pending. Returning true signals to commit the transaction. - The value of the awaited action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. - - The value returned from the action. - The connection to transact with. - The isolation level for the transaction. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The handler to execute while a transaction is pending. - The value of the awaited action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. - - The connection to transact with. - The isolation level for the transaction. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The handler to execute while a transaction is pending. - - - - Begins a transaction before executing the action. Commits if there are no exceptions and 'Commit' value from the action is true. Otherwise rolls-back the transaction. - - The value returned from the action. - The connection to transact with. - The isolation level for the transaction. - The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. - An optional token that if cancelled will cause this transaction to be aborted or rolled-back. - The value returned from the conditional action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions and the conditional action returns true. Otherwise rolls-back the transaction. - - The connection to transact with. - The isolation level for the transaction. - The handler to execute while a transaction is pending. Returning true signals to commit the transaction. - An optional token that if cancelled will cause this transaction to be aborted or rolled-back. - True if committed. - - - - Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. - - The value returned from the action. - The connection to transact with. - The isolation level for the transaction. - The handler to execute while a transaction is pending. - An optional token that if cancelled will cause this transaction to be aborted or rolled-back. - The value of the action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. - - The connection to transact with. - The isolation level for the transaction. - The handler to execute while a transaction is pending. - An optional token that if cancelled will cause this transaction to be aborted or rolled-back. - - - - Begins a transaction before executing the action. Commits if there are no exceptions and the 'Commit' value from the action is true. Otherwise rolls-back the transaction. - - The value returned from the action. - The connection to transact with. - The isolation level for the transaction. - The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. - An optional token that if cancelled will cause this transaction to be aborted or rolled-back. - The value of the awaited action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions and the value from the action is true. Otherwise rolls-back the transaction. - - The connection to transact with. - The isolation level for the transaction. - The handler to execute while a transaction is pending. Returning true signals to commit the transaction. - An optional token that if cancelled will cause this transaction to be aborted or rolled-back. - The value of the awaited action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. - - The value returned from the action. - The connection to transact with. - The isolation level for the transaction. - The handler to execute while a transaction is pending. - An optional token that if cancelled will cause this transaction to be aborted or rolled-back. - The value of the awaited action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. - - The connection to transact with. - The isolation level for the transaction. - The handler to execute while a transaction is pending. - An optional token that if cancelled will cause this transaction to be aborted or rolled-back. - - - - Begins a transaction before executing the action. Commits if there are no exceptions, the 'Commit' value from the action is true and the optional cancellation token has not been cancelled. Otherwise rolls-back the transaction. - - The value returned from the action. - The connection to transact with. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. - The value returned from the conditional action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions, the conditional action returns true, and the optional cancellation token is not cancelled. Otherwise rolls-back the transaction. - - The connection to transact with. - An optional token that if cancelled will cause this transaction to be aborted or rolled-back. - The handler to execute while a transaction is pending. Returning true signals to commit the transaction. - True if committed. - - - - Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - - The value returned from the action. - The connection to transact with. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The handler to execute while a transaction is pending. - The value of the action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - - The connection to transact with. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The handler to execute while a transaction is pending. - - - - Begins a transaction before executing the action. Commits if there are no exceptions, the 'Commit' value from the action is true, and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - - The value returned from the action. - The connection to transact with. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. - The value of the awaited action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions, the value from the action is true, and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - - The connection to transact with. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The handler to execute while a transaction is pending. Returning true signals to commit the transaction. - The value of the awaited action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - - The value returned from the action. - The connection to transact with. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The handler to execute while a transaction is pending. - The value of the awaited action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - - The connection to transact with. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The handler to execute while a transaction is pending. - - - - Common interface for creating a connection. Can easily be used with dependency injection. - - - - - Creates a new connection ready for use. - - An IDbConnection. - - - - The actual connection type. - - - - An connection of type . - - - - Extensions for converting a connection factory into a pool. - - - - - Provides a connection pool that simply creates from a connection factory and disposes when returned. - - The connection factory to generate connections from. - - - - - Provides a connection pool that simply creates from a connection factory and disposes when returned. - - The connection factory to generate connections from. - - - - - Coerces a non-generic connection factory to a generic one. - - The source connection factory. - The generic version of the source factory. - - - - A unifying common interface for creating/managing connections. Can easily be - used with dependency injection. Commonly a pool will simply host a single - connection for nonconcurrent operations where giving back to the pool - guarantees the connection returns to the state it was in before it was - taken. Connection factories can pose as pools where taking always creates a - new connection, and giving back always disposes. - - - - - Provides a connection ready for use. The connection state may or may not be closed depending on how the pool is being used. - - An . - - - - Gives the connection to the pool. - Depending on implementation, - the pool could be full, - and the connection disposed of immediately. - - The connection to be received by the pool. - - - - The actual connection type. - - - - An connection of type . - - - - Extensions for getting generic versions on non-generic connection pools.. - - - - - Converts a non-generic connection factory to a generic one. - - The source connection factory. - The generic version of the source factory. - - - - Common interface used for expressive commands. - - - - - Executes a reader on a command with a handler function. - - The handler function for each IDataRecord. - - - - Executes a reader on a command with a transform function. - - The return type of the transform function. - The transform function for each IDataRecord. - The result of the transform. - - - - Asynchronously executes a reader on a command with a handler function. - - The handler function for each IDataRecord. - - - - Asynchronously executes a reader on a command with a transform function. - - The return type of the transform function. - The transform function for each IDataRecord. - The result of the transform. - - - - Common interface used for expressive commands. - - - - - Executes a reader on a command with a handler function. - - The handler function for each IDataRecord. - - - - Executes a reader on a command with a transform function. - - The return type of the transform function. - The transform function for each IDataRecord. - The result of the transform. - - - - Asynchronously executes a reader on a command with a handler function. - - The handler function for each IDataRecord. - - - - Asynchronously executes a reader on a command with a transform function. - - The return type of the transform function. - The transform function for each IDataRecord. - The result of the transform. - - - - Common interface used for expressive commands when dealing with a data reader. - - - - - The cancellation token to use with supported methods. - - - - - Executes a reader on a command with a handler function. - - The handler function for the data reader. - The command behavior for once the command the reader is complete. - - - - Executes a reader on a command with a transform function. - - The return type of the transform function. - The transform function for each IDataRecord. - The command behavior for once the command the reader is complete. - The result of the transform. - - - - Executes a reader on a command with a handler function. - - The handler function for the data reader. - The command behavior for once the command the reader is complete. - - - - Executes a reader on a command with a handler function. - - The transform function for each IDataRecord. - The command behavior for once the command the reader is complete. - - - - Common interface used for expressive commands when dealing with a data reader. - - The type of the data reader. - - - - Executes a reader on a command with a handler function. - - The handler function for the data reader. - The command behavior for once the command the reader is complete. - - - - Executes a reader on a command with a transform function. - - The return type of the transform function. - The transform function for each IDataRecord. - The command behavior for once the command the reader is complete. - The result of the transform. - - - - Executes a reader on a command with a handler function. - - The handler function for the data reader. - The command behavior for once the command the reader is complete. - - - - Executes a reader on a command with a handler function. - - The handler function for the data reader. - The command behavior for once the command the reader is complete. - - - - - - - Indicates if reader.ReadAsync() will be used in favor of reader.Read(). - - - - - -
-
diff --git a/Source/Core/ExpressiveCommand.cs b/Source/Core/ExpressiveCommand.cs index af38d92..a684ddd 100644 --- a/Source/Core/ExpressiveCommand.cs +++ b/Source/Core/ExpressiveCommand.cs @@ -1,76 +1,84 @@ -using System.Collections.Generic; -using System.Data; - -namespace Open.Database.Extensions; +namespace Open.Database.Extensions; /// /// An abstraction for executing commands on a database using best practices and simplified expressive syntax. /// public class ExpressiveCommand : ExpressiveCommandBase { - /// Constructs a . - /// - public ExpressiveCommand( - IDbConnectionPool connectionPool, - CommandType type, - string command, - IEnumerable? @params = null) - : base(connectionPool.AsGeneric(), type, command, @params) - { - } + /// Constructs a . + /// + public ExpressiveCommand( + IDbConnectionPool connectionPool, + CommandType type, + string command, + IEnumerable? @params = null) + : base(connectionPool.AsGeneric(), type, command, @params) + { + } + + /// Constructs a . + /// + public ExpressiveCommand( + IDbConnectionFactory connFactory, + CommandType type, + string command, + IEnumerable? @params = null) + : base(connFactory.AsGeneric(), type, command, @params) + { + } - /// Constructs a . - /// - public ExpressiveCommand( - IDbConnectionFactory connFactory, - CommandType type, - string command, - IEnumerable? @params = null) - : base(connFactory.AsGeneric(), type, command, @params) - { - } + /// Constructs a . + /// + public ExpressiveCommand( + Func connFactory, + CommandType type, + string command, + IEnumerable? @params = null) + : base(connFactory, type, command, @params) + { + } - /// Constructs a . - /// - public ExpressiveCommand( - IDbConnection connection, - IDbTransaction? transaction, - CommandType type, - string command, - IEnumerable? @params = null) - : base(connection, transaction, type, command, @params) - { - } + /// Constructs a . + /// + public ExpressiveCommand( + IDbConnection connection, + IDbTransaction? transaction, + CommandType type, + string command, + IEnumerable? @params = null) + : base(connection, transaction, type, command, @params) + { + } - /// Constructs a . - /// - public ExpressiveCommand( - IDbTransaction transaction, - CommandType type, - string command, - IEnumerable? @params = null) - : base(transaction, type, command, @params) - { - } + /// Constructs a . + /// + public ExpressiveCommand( + IDbTransaction transaction, + CommandType type, + string command, + IEnumerable? @params = null) + : base(transaction, type, command, @params) + { + } - /// Constructs a . - /// - public ExpressiveCommand( - IDbConnection connection, - CommandType type, - string command, - IEnumerable? @params = null) - : base(connection, type, command, @params) - { - } + /// Constructs a . + /// + public ExpressiveCommand( + IDbConnection connection, + CommandType type, + string command, + IEnumerable? @params = null) + : base(connection, type, command, @params) + { + } - /// - protected override void AddParams(IDbCommand command) - { - foreach (var p in Params) - { - var np = command.AddParameter(p.Name, p.Value); - if (p.Type.HasValue) np.DbType = p.Type.Value; - } - } + /// + protected override void AddParams(IDbCommand command) + { + foreach (Param p in Params) + { + IDbDataParameter np = command.AddParameter(p.Name, p.Value); + if (p.Type.HasValue) np.DbType = p.Type.Value; + } + } } diff --git a/Source/Core/ExpressiveCommandBase.Param.cs b/Source/Core/ExpressiveCommandBase.Param.cs index c74a118..8af2484 100644 --- a/Source/Core/ExpressiveCommandBase.Param.cs +++ b/Source/Core/ExpressiveCommandBase.Param.cs @@ -1,71 +1,59 @@ -using System; -using System.Collections.Generic; -using System.Data; - -namespace Open.Database.Extensions; +namespace Open.Database.Extensions; public abstract partial class ExpressiveCommandBase - where TConnection : class, IDbConnection - where TCommand : class, IDbCommand - where TReader : class, IDataReader - where TDbType : struct - where TThis : ExpressiveCommandBase + where TConnection : class, IDbConnection + where TCommand : class, IDbCommand + where TReader : class, IDataReader + where TDbType : struct + where TThis : ExpressiveCommandBase { /// /// A struct that represents the param to be created when the command is executed. /// TDbType facilitates the difference between DbType and SqlDbType. /// - public struct Param : IEquatable - - { - /// - /// The name of the param. - /// - public string Name { get; set; } - - /// - /// The value of the param. - /// - public object Value { get; set; } - - /// - /// The DbType of the param. - /// - public TDbType? Type { get; set; } - - /// - public bool Equals(Param other) - => Name == other.Name - && EqualityComparer.Default.Equals(Value, other.Value) - && EqualityComparer.Default.Equals(Type, other.Type); - - /// - public override bool Equals(object obj) - => obj is Param o && Equals(o); - - /// -#if NETSTANDARD2_1 - public override int GetHashCode() - => HashCode.Combine(Name, Value, Type); -#else - public override int GetHashCode() - { - var hashCode = 1477810893; - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Name); - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Value); - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Type); - return hashCode; - } -#endif - - /// - /// Equality operator. - /// - public static bool operator ==(Param left, Param right) => left.Equals(right); - - /// - /// Inequality operator. - /// - public static bool operator !=(Param left, Param right) => !left.Equals(right); - } + public readonly record struct Param + + { + /// + /// Constructs a . + /// + public Param( + string name, + object? value, + TDbType? type = null, + ParameterDirection direction = ParameterDirection.Input) + { + if (name is null) throw new ArgumentNullException(nameof(name)); + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentException("Parameter names cannot be empty or white space.", nameof(name)); + Contract.EndContractBlock(); + + Name = name; + Type = type; + Direction = direction; + + bool isInput = direction is ParameterDirection.Input or ParameterDirection.InputOutput; + Value = isInput ? (value ?? DBNull.Value) : value; + } + + /// + /// The name of the param. + /// + public string Name { get; } + + /// + /// The value of the param. + /// + public object? Value { get; } + + /// + /// The DbType of the param. + /// + public TDbType? Type { get; } + + /// + /// The direction of the param. + /// + public ParameterDirection Direction { get; } + } } diff --git a/Source/Core/ExpressiveCommandBase.cs b/Source/Core/ExpressiveCommandBase.cs index 757cfd4..6171a9e 100644 --- a/Source/Core/ExpressiveCommandBase.cs +++ b/Source/Core/ExpressiveCommandBase.cs @@ -1,14 +1,4 @@ -using Open.Database.Extensions.Core; -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; -using System.Diagnostics.Contracts; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Open.Database.Extensions; +namespace Open.Database.Extensions; /// /// Base class for developing expressive commands. @@ -20,211 +10,184 @@ namespace Open.Database.Extensions; /// The DB type enum to use for parameters. /// The type of this class in order to facilitate proper expressive notation. public abstract partial class ExpressiveCommandBase - : IExecuteCommand, IExecuteReader - where TConnection : class, IDbConnection - where TCommand : class, IDbCommand - where TReader : class, IDataReader - where TDbType : struct - where TThis : ExpressiveCommandBase + : IExecuteCommand, IExecuteReader + where TConnection : class, IDbConnection + where TCommand : class, IDbCommand + where TReader : class, IDataReader + where TDbType : struct + where TThis : ExpressiveCommandBase { - /// - /// Utility for simplifying param concatenation. - /// - /// The type of the enumerable. - /// The first value. - /// The remaining values. - protected static IEnumerable Concat(T first, ICollection remaining) - => CoreExtensions.Concat(first, remaining); - - /// - /// The connection provider to used to acquire connections. - /// - protected IDbConnectionPool ConnectionProvider { get; } - - /// - /// The transaction to execute commands on if not using a connection factory. - /// - protected IDbTransaction? Transaction { get; } - - /// Constructs a . - /// The pool to acquire connections from. - /// The command type. - /// The SQL command. - /// The list of params - protected ExpressiveCommandBase( - IDbConnectionPool connectionPool, - CommandType type, - string command, - IEnumerable? @params) - { - ConnectionProvider = connectionPool ?? throw new ArgumentNullException(nameof(connectionPool)); - Command = command ?? throw new ArgumentNullException(nameof(command)); - if (string.IsNullOrWhiteSpace(command)) throw new ArgumentException("Cannot be null or whitespace.", nameof(command)); - Contract.EndContractBlock(); - - Type = type; - Params = @params?.ToList() ?? new List(); - Timeout = CommandTimeout.DEFAULT_SECONDS; - } - - /// Constructs a . - /// The factory to generate connections from. - /// The command type. - /// The SQL command. - /// The list of params - protected ExpressiveCommandBase( - IDbConnectionFactory connFactory, - CommandType type, - string command, - IEnumerable? @params) - : this((connFactory ?? throw new ArgumentNullException(nameof(connFactory))).AsPool(), type, command, @params) - { - } - - /// Constructs a . - /// The connection to execute the command on. - /// The optional transaction to execute the command on. - /// The command type. - /// The SQL command. - /// The list of params - protected ExpressiveCommandBase( - TConnection connection, - IDbTransaction? transaction, - CommandType type, - string command, - IEnumerable? @params) - : this(DbConnectionProvider.Create(connection), type, command, @params) - { - Transaction = transaction; - } - - /// Constructs a . - /// The connection to execute the command on. - /// The command type. - /// The SQL command. - /// The list of params - protected ExpressiveCommandBase( - TConnection connection, - CommandType type, - string command, - IEnumerable? @params) - : this(connection, null, type, command, @params) - { - } - - /// Constructs a . - /// The optional transaction to execute the command on. - /// The command type. - /// The SQL command. - /// The list of params - protected ExpressiveCommandBase( - IDbTransaction transaction, - CommandType type, - string command, - IEnumerable? @params) - : this( - (TConnection)(transaction ?? throw new ArgumentNullException(nameof(transaction))).Connection, - transaction, type, command, @params) - { - } - - /// - /// The command text or procedure name to use. - /// - public string Command { get; set; } - - /// - /// The command type. - /// - public CommandType Type { get; set; } - - /// - /// The list of params to apply to the command before execution. - /// - public List Params { get; } - - /// - /// The command timeout value. - /// - public ushort Timeout { get; set; } - - /// - /// Creates the expected command type from the connection provided. - /// - /// The connection to create the command from. - /// The new command to use. - protected TCommand PrepareCommand(TConnection connection) - { - var cmd = connection.CreateCommand(Type, Command, Timeout); - if (cmd is not TCommand c) - throw new InvalidCastException($"Actual command type ({cmd.GetType()}) is not compatible with expected command type ({typeof(TCommand)})."); - if (Transaction != null) - c.Transaction = Transaction; - AddParams(c); - return c; - } - - /// - /// The optional cancellation token to use with supported methods. - /// - public CancellationToken CancellationToken { get; set; } = CancellationToken.None; - - CancellationToken IExecuteReader.CancellationToken => CancellationToken; - - /// - /// Sets the cancellation token. - /// - public TThis UseCancellationToken(CancellationToken token) - { - CancellationToken = token; - return (TThis)this; - } - - #region AddParam - /// - /// Adds a parameter to the params list. - /// - /// The name of the parameter. - /// The value of the parameter. - /// The database type of the parameter. - /// This instance for use in method chaining. - public TThis AddParam(string name, object value, TDbType type) - { - if (name is null) throw new ArgumentNullException(nameof(name)); - else if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentException("Parameter names cannot be empty or white space.", nameof(name)); - Contract.EndContractBlock(); - - Params.Add(new Param - { - Name = name, - Value = value, - Type = type - }); - - return (TThis)this; - } - - /// - /// Adds a parameter to the params list. - /// - /// The name of the parameter. - /// The value of the parameter. - /// This instance for use in method chaining. - public TThis AddParam(string name, object value) - { - if (name is null) throw new ArgumentNullException(nameof(name)); - else if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentException("Parameter names cannot be empty or white space.", nameof(name)); - Contract.EndContractBlock(); - - Params.Add(new Param - { - Name = name, - Value = value ?? DBNull.Value - }); - return (TThis)this; - } + /// + /// Utility for simplifying param concatenation. + /// + /// The type of the enumerable. + /// The first value. + /// The remaining values. + protected static IEnumerable Concat(T first, ICollection remaining) + => CoreExtensions.Concat(first, remaining); + + /// + /// The connection provider to used to acquire connections. + /// + protected IDbConnectionPool ConnectionProvider { get; } + + /// + /// The transaction to execute commands on if not using a connection factory. + /// + protected IDbTransaction? Transaction { get; } + + /// Constructs a . + /// The pool to acquire connections from. + /// The command type. + /// The SQL command. + /// The list of params + protected ExpressiveCommandBase( + IDbConnectionPool connectionPool, + CommandType type, + string command, + IEnumerable? @params) + { + ConnectionProvider = connectionPool ?? throw new ArgumentNullException(nameof(connectionPool)); + Command = command ?? throw new ArgumentNullException(nameof(command)); + if (string.IsNullOrWhiteSpace(command)) throw new ArgumentException("Cannot be null or whitespace.", nameof(command)); + Contract.EndContractBlock(); + + Type = type; + Params = @params?.ToList() ?? []; + Timeout = CommandTimeout.DEFAULT_SECONDS; + } + + /// Constructs a . + /// The factory to generate connections from. + /// The command type. + /// The SQL command. + /// The list of params + protected ExpressiveCommandBase( + IDbConnectionFactory connFactory, + CommandType type, + string command, + IEnumerable? @params) + : this((connFactory ?? throw new ArgumentNullException(nameof(connFactory))).AsPool(), type, command, @params) + { + } + + /// + protected ExpressiveCommandBase( + Func connFactory, + CommandType type, + string command, + IEnumerable? @params) + : this((connFactory ?? throw new ArgumentNullException(nameof(connFactory))).AsPool(), type, command, @params) + { + } + + /// Constructs a . + /// The connection to execute the command on. + /// The optional transaction to execute the command on. + /// The command type. + /// The SQL command. + /// The list of params + protected ExpressiveCommandBase( + TConnection connection, + IDbTransaction? transaction, + CommandType type, + string command, + IEnumerable? @params) + : this(DbConnectionProvider.Create(connection), type, command, @params) + => Transaction = transaction; + + /// Constructs a . + /// The connection to execute the command on. + /// The command type. + /// The SQL command. + /// The list of params + protected ExpressiveCommandBase( + TConnection connection, + CommandType type, + string command, + IEnumerable? @params) + : this(connection, null, type, command, @params) + { + } + + /// Constructs a . + /// The optional transaction to execute the command on. + /// The command type. + /// The SQL command. + /// The list of params + protected ExpressiveCommandBase( + IDbTransaction transaction, + CommandType type, + string command, + IEnumerable? @params) + : this( + (TConnection)(transaction ?? throw new ArgumentNullException(nameof(transaction))).Connection!, + transaction, type, command, @params) + { + } + + /// + /// The command text or procedure name to use. + /// + public string Command { get; set; } + + /// + /// The command type. + /// + public CommandType Type { get; set; } + + /// + /// The list of params to apply to the command before execution. + /// + public List Params { get; } + + /// + /// The command timeout value. + /// + public ushort Timeout { get; set; } + + /// + /// Creates the expected command type from the connection provided. + /// + /// The connection to create the command from. + /// The new command to use. + protected TCommand PrepareCommand(TConnection connection) + { + IDbCommand cmd = connection.CreateCommand(Type, Command, Timeout); + if (cmd is not TCommand c) + throw new InvalidCastException($"Actual command type ({cmd.GetType()}) is not compatible with expected command type ({typeof(TCommand)})."); + if (Transaction != null) + c.Transaction = Transaction; + AddParams(c); + return c; + } + + /// + /// The optional cancellation token to use with supported methods. + /// + public CancellationToken CancellationToken { get; set; } = CancellationToken.None; + + CancellationToken IExecuteReader.CancellationToken => CancellationToken; + + /// + /// Sets the cancellation token. + /// + public TThis UseCancellationToken(CancellationToken token) + { + CancellationToken = token; + return (TThis)this; + } + + #region AddParam + + /// + /// Shortcut to add a parameter to the params list. + /// + protected TThis AddParam(Param param) + { + Params.Add(param); + return (TThis)this; + } /// /// Adds a parameter to the params list. @@ -232,392 +195,357 @@ public TThis AddParam(string name, object value) /// The name of the parameter. /// The value of the parameter. /// The database type of the parameter. + /// The direction of the parameter. + /// This instance for use in method chaining. + /// is null. + /// is blank. + public TThis AddParam(string name, object value, TDbType type, ParameterDirection direction = ParameterDirection.Input) + => AddParam(new(name, value, type, direction)); + + /// + public TThis AddParam(string name, object? value, ParameterDirection direction = ParameterDirection.Input) + => AddParam(new(name, value, null, direction)); + + /// + public TThis AddParam(string name, T? value, TDbType? type = null, ParameterDirection direction = ParameterDirection.Input) + where T : struct + => AddParam(new(name, value, type, direction)); + + /// + public TThis AddParam(string name, string? value, TDbType? type = null, ParameterDirection direction = ParameterDirection.Input) + => AddParam(new(name, value, type, direction)); + + /// + public TThis AddParam(string name, TDbType type, ParameterDirection direction = ParameterDirection.Input) + => AddParam(new(name, null, type, direction)); + + /// + public TThis AddParam(string name, ParameterDirection direction = ParameterDirection.Input) + => AddParam(new(name, null, null, direction)); + + /// + /// Adds a return parameter to the params list. + /// + /// + public TThis AddReturnParam(string name, TDbType type) + => AddParam(new(name, null, type, ParameterDirection.ReturnValue)); + + /// + public TThis AddReturnParam(string name) + => AddParam(new(name, null, null, ParameterDirection.ReturnValue)); + + /// + /// Conditionally adds a parameter to the params list. + /// + /// The condition to add the param by. Only adds if true. + /// The name of the parameter. + /// The value of the parameter. + /// The database type of the parameter. + /// The direction of the parameter. + /// + public TThis AddParamIf(bool condition, string name, object value, TDbType type, ParameterDirection direction = ParameterDirection.Input) + => condition ? AddParam(name, value, type, direction) : (TThis)this; + + /// + public TThis AddParamIf(bool condition, string name, T? value, ParameterDirection direction = ParameterDirection.Input) + where T : struct + => condition ? AddParam(name, value, null, direction) : (TThis)this; + + /// + public TThis AddParamIf(bool condition, string name, object? value, ParameterDirection direction = ParameterDirection.Input) + => condition ? AddParam(name, value, direction) : (TThis)this; + + /// + public TThis AddParamIf(bool condition, string name, T? value, TDbType? type, ParameterDirection direction = ParameterDirection.Input) + where T : struct + => condition ? AddParam(name, value, type, direction) : (TThis)this; + + /// + public TThis AddParamIf(bool condition, string name, TDbType type, ParameterDirection direction = ParameterDirection.Input) + => condition ? AddParam(name, type, direction) : (TThis)this; + + /// + public TThis AddParamIf(bool condition, string name, ParameterDirection direction = ParameterDirection.Input) + => condition ? AddParam(name, direction) : (TThis)this; + + /// + /// Handles adding the list of parameters to a new command. + /// + /// The command to add parameters to. + protected abstract void AddParams(TCommand command); + #endregion + + /// + /// Sets the timeout value. + /// + /// The number of seconds to wait before the connection times out. /// This instance for use in method chaining. - public TThis AddParam(string name, T? value, TDbType type) - where T : struct - { - if (name is null) throw new ArgumentNullException(nameof(name)); - else if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentException("Parameter names cannot be empty or white space.", nameof(name)); - Contract.EndContractBlock(); - - var p = new Param { Name = name, Type = type }; - p.Value = value ?? (object)DBNull.Value; - - Params.Add(p); - return (TThis)this; - } - - /// - /// Adds a parameter to the params list. - /// - /// The name of the parameter. - /// The value of the parameter. - /// This instance for use in method chaining. - public TThis AddParam(string name, T? value) - where T : struct - { - if (name is null) throw new ArgumentNullException(nameof(name)); - Contract.EndContractBlock(); - - var p = new Param { Name = name }; - p.Value = value ?? (object)DBNull.Value; - - Params.Add(p); - return (TThis)this; - } - - /// - /// Adds a parameter to the params list. - /// - /// The name of the parameter. - /// This instance for use in method chaining. - public TThis AddParam(string name) - { - if (name is null) throw new ArgumentNullException(nameof(name)); - else if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentException("Parameter names cannot be empty or white space.", nameof(name)); - Contract.EndContractBlock(); - - Params.Add(new Param - { - Name = name - }); - - return (TThis)this; - } - - /// - /// Conditionally adds a parameter to the params list. - /// - /// The condition to add the param by. Only adds if true. - /// The name of the parameter. - /// The value of the parameter. - /// This instance for use in method chaining. - public TThis AddParamIf(bool condition, string name, T? value) - where T : struct - => condition ? AddParam(name, value) : (TThis)this; - - /// - /// Conditionally adds a parameter to the params list. - /// - /// The condition to add the param by. Only adds if true. - /// The name of the parameter. - /// The value of the parameter. - /// This instance for use in method chaining. - public TThis AddParamIf(bool condition, string name, object value) - => condition ? AddParam(name, value) : (TThis)this; - - /// - /// Conditionally adds a parameter to the params list. - /// - /// The condition to add the param by. Only adds if true. - /// The name of the parameter. - /// The value of the parameter. - /// The database type of the parameter. - /// This instance for use in method chaining. - public TThis AddParamIf(bool condition, string name, object value, TDbType type) - => condition ? AddParam(name, value, type) : (TThis)this; - - /// - /// Conditionally adds a parameter to the params list. - /// - /// The condition to add the param by. Only adds if true. - /// The name of the parameter. - /// The value of the parameter. - /// The database type of the parameter. - /// This instance for use in method chaining. - public TThis AddParamIf(bool condition, string name, T? value, TDbType type) - where T : struct - => condition ? AddParam(name, value, type) : (TThis)this; - - /// - /// Conditionally adds a parameter to the params list. - /// - /// The condition to add the param by. Only adds if true. - /// The name of the parameter. - /// This instance for use in method chaining. - public TThis AddParamIf(bool condition, string name) - => condition ? AddParam(name) : (TThis)this; - - /// - /// Handles adding the list of parameters to a new command. - /// - /// The command to add parameters to. - protected abstract void AddParams(TCommand command); - #endregion - - /// - /// Sets the timeout value. - /// - /// The number of seconds to wait before the connection times out. - /// This instance for use in method chaining. - public TThis SetTimeout(ushort seconds) - { - Timeout = seconds; - return (TThis)this; - } - - /// - public void Execute(Action action) - { - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); - - // Open MUST occur before command creation as some DbCommands require it. - ConnectionProvider.Open((conn, _) => - { - using var cmd = PrepareCommand(conn); - action(cmd); - }); - } - - /// - public T Execute(Func transform) - { - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - // Open MUST occur before command creation as some DbCommands require it. - return ConnectionProvider.Open((conn, _) => - { - using var cmd = PrepareCommand(conn); - return transform(cmd); - }); - } - - /// - public virtual ValueTask ExecuteAsync(Func handler) - { - if (handler is null) throw new ArgumentNullException(nameof(handler)); - Contract.EndContractBlock(); - - CancellationToken.ThrowIfCancellationRequested(); // Since cancelled awaited tasks throw, we will follow the same pattern here. - - // Open MUST occur before command creation as some DbCommands require it. - return ConnectionProvider.OpenAsync(async (conn, _) => - { - using var cmd = PrepareCommand(conn); - await handler(cmd).ConfigureAwait(false); - }); - } - - /// - public virtual ValueTask ExecuteAsync(Func> transform) - { - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - CancellationToken.ThrowIfCancellationRequested(); // Since cancelled awaited tasks throw, we will follow the same pattern here. - - // Open MUST occur before command creation as some DbCommands require it. - return ConnectionProvider.OpenAsync(async (conn, _) => - { - using var cmd = PrepareCommand(conn); - return await transform(cmd).ConfigureAwait(false); - }); - } - - void IExecuteCommand.Execute(Action action) - => Execute(command => action(command)); - - T IExecuteCommand.Execute(Func transform) - => Execute(command => transform(command)); - - ValueTask IExecuteCommand.ExecuteAsync(Func handler) - => ExecuteAsync(command => handler(command)); - - ValueTask IExecuteCommand.ExecuteAsync(Func> transform) - => ExecuteAsync(command => transform(command)); - - /// - /// Validates and properly acquires the expected type of the reader. - /// - /// The actual type of the reader. - /// The reader to cast. - /// The expected reader. - protected static TReader EnsureReaderType(TActual reader) - where TActual : IDataReader - => reader is TReader r ? r : throw new InvalidCastException($"Expected reader type of ({typeof(TReader)}). Actual: ({reader.GetType()})"); - - /// - public void ExecuteReader(Action handler, CommandBehavior behavior = CommandBehavior.Default) - { - if (handler is null) throw new ArgumentNullException(nameof(handler)); - Contract.EndContractBlock(); - - // Open MUST occur before command creation as some DbCommands require it. - ConnectionProvider.Open((conn, state) => - { - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var cmd = PrepareCommand(conn); - cmd.ExecuteReader(reader => handler(EnsureReaderType(reader)), behavior); - }); - } - - /// - public T ExecuteReader(Func transform, CommandBehavior behavior = CommandBehavior.Default) - { - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - return ConnectionProvider.Open((conn, state) => - { - // Open MUST occur before command creation as some DbCommands require it. - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var cmd = PrepareCommand(conn); - return cmd.ExecuteReader(reader => transform(EnsureReaderType(reader)), behavior); - }); - } - - /// - public ValueTask ExecuteReaderAsync(Action handler, CommandBehavior behavior = CommandBehavior.Default) - { - return ConnectionProvider.OpenAsync(async (conn, state) => - { - // Open MUST occur before command creation as some DbCommands require it. - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var cmd = PrepareCommand(conn); - await cmd.ExecuteReaderAsync(ExecuteReaderAsyncCore, behavior, CancellationToken).ConfigureAwait(true); - }); - - ValueTask ExecuteReaderAsyncCore(IDataReader reader) - { - handler(EnsureReaderType(reader)); - return new ValueTask(); - } - } - - /// - public ValueTask ExecuteReaderAsync(Func handler, CommandBehavior behavior = CommandBehavior.Default) - { - return ConnectionProvider.OpenAsync(async (conn, state) => - { - // Open MUST occur before command creation as some DbCommands require it. - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var cmd = PrepareCommand(conn); - return await cmd.ExecuteReaderAsync(ExecuteReaderAsyncCore, behavior, CancellationToken).ConfigureAwait(true); - }); - - ValueTask ExecuteReaderAsyncCore(IDataReader reader) - => new(handler(EnsureReaderType(reader))); - } - - /// - public ValueTask ExecuteReaderAsync(Func handler, CommandBehavior behavior = CommandBehavior.Default) - => ConnectionProvider.OpenAsync(async (conn, state) => - { - // Open MUST occur before command creation as some DbCommands require it. - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var cmd = PrepareCommand(conn); - await cmd.ExecuteReaderAsync(reader => handler(EnsureReaderType(reader)), behavior, CancellationToken).ConfigureAwait(true); - }); - - /// - public ValueTask ExecuteReaderAsync(Func> handler, CommandBehavior behavior = CommandBehavior.Default) - => ConnectionProvider.OpenAsync(async (conn, state) => - { - // Open MUST occur before command creation as some DbCommands require it. - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var cmd = PrepareCommand(conn); - return await cmd.ExecuteReaderAsync(reader => handler(EnsureReaderType(reader)), behavior, CancellationToken).ConfigureAwait(true); - }); - - void IExecuteReader.ExecuteReader(Action handler, CommandBehavior behavior) - => ExecuteReader(reader => handler(reader), behavior); - - T IExecuteReader.ExecuteReader(Func transform, CommandBehavior behavior) - => ExecuteReader(reader => transform(reader), behavior); - - ValueTask IExecuteReader.ExecuteReaderAsync(Func handler, CommandBehavior behavior) - => ExecuteReaderAsync(reader => handler(reader), behavior); - - ValueTask IExecuteReader.ExecuteReaderAsync(Func> transform, CommandBehavior behavior) - => ExecuteReaderAsync(reader => transform(reader), behavior); - - /// - /// Calls ExecuteNonQuery on the underlying command but sets up a return parameter and returns that value. - /// - /// The value from the return parameter. - public object ExecuteReturn() - // Open MUST occur before command creation as some DbCommands require it. - => ConnectionProvider.Open((conn, _) => - { - using var cmd = PrepareCommand(conn); - var returnParameter = cmd.AddReturnParameter(); - cmd.ExecuteNonQuery(); - return returnParameter.Value; - }); - - /// - /// Calls ExecuteNonQuery on the underlying command but sets up a return parameter and returns that value. - /// - /// The value from the return parameter. - public T ExecuteReturn() - => (T)ExecuteReturn(); - - /// - /// Calls ExecuteNonQueryAsync on the underlying command but sets up a return parameter and returns that value. - /// - /// The value from the return parameter. - public ValueTask ExecuteReturnAsync() - { - CancellationToken.ThrowIfCancellationRequested(); - - // Open MUST occur before command creation as some DbCommands require it. - return ConnectionProvider.OpenAsync(async (conn, _) => - { - using var cmd = PrepareCommand(conn); - var returnParameter = cmd.AddReturnParameter(); - - if (cmd is DbCommand dbCommand) - await dbCommand.ExecuteNonQueryAsync(CancellationToken).ConfigureAwait(false); - else - cmd.ExecuteNonQuery(); - - return returnParameter.Value; - }); - } - - /// - /// Calls ExecuteNonQueryAsync on the underlying command but sets up a return parameter and returns that value. - /// - /// The value from the return parameter. - public async ValueTask ExecuteReturnAsync() - => (T)await ExecuteReturnAsync().ConfigureAwait(false); - - /// - /// Calls ExecuteNonQuery on the underlying command. - /// - /// The integer response from the method. (Records updated.) - public int ExecuteNonQuery() - => Execute(command => command.ExecuteNonQuery()); - - /// - /// Calls ExecuteScalar on the underlying command. - /// - /// The value returned from the method. - public object ExecuteScalar() - => Execute(command => command.ExecuteScalar()); - - /// - /// Calls ExecuteScalar on the underlying command. - /// - /// The type expected. - /// The value returned from the method. - public T ExecuteScalar() - => (T)ExecuteScalar(); - - /// - /// Calls ExecuteScalar on the underlying command. - /// - /// The type expected. - /// The value returned from the method. - public T ExecuteScalar(Func transform) - { - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - return transform(ExecuteScalar()); - } + public TThis SetTimeout(ushort seconds) + { + Timeout = seconds; + return (TThis)this; + } + + /// + public void Execute(Action action) + { + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); + + // Open MUST occur before command creation as some DbCommands require it. + ConnectionProvider.Open((conn, _) => + { + using TCommand cmd = PrepareCommand(conn); + action(cmd); + }); + } + + /// + public T Execute(Func transform) + { + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + // Open MUST occur before command creation as some DbCommands require it. + return ConnectionProvider.Open((conn, _) => + { + using TCommand cmd = PrepareCommand(conn); + return transform(cmd); + }); + } + + /// + public virtual ValueTask ExecuteAsync(Func handler) + { + if (handler is null) throw new ArgumentNullException(nameof(handler)); + Contract.EndContractBlock(); + + CancellationToken.ThrowIfCancellationRequested(); // Since cancelled awaited tasks throw, we will follow the same pattern here. + + // Open MUST occur before command creation as some DbCommands require it. + return ConnectionProvider.OpenAsync(async (conn, _) => + { + using TCommand cmd = PrepareCommand(conn); + await handler(cmd).ConfigureAwait(false); + }); + } + + /// + public virtual ValueTask ExecuteAsync(Func> transform) + { + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + CancellationToken.ThrowIfCancellationRequested(); // Since cancelled awaited tasks throw, we will follow the same pattern here. + + // Open MUST occur before command creation as some DbCommands require it. + return ConnectionProvider.OpenAsync(async (conn, _) => + { + using TCommand cmd = PrepareCommand(conn); + return await transform(cmd).ConfigureAwait(false); + }); + } + + void IExecuteCommand.Execute(Action action) + => Execute(command => action(command)); + + T IExecuteCommand.Execute(Func transform) + => Execute(command => transform(command)); + + ValueTask IExecuteCommand.ExecuteAsync(Func handler) + => ExecuteAsync(command => handler(command)); + + ValueTask IExecuteCommand.ExecuteAsync(Func> transform) + => ExecuteAsync(command => transform(command)); + + /// + /// Validates and properly acquires the expected type of the reader. + /// + /// The actual type of the reader. + /// The reader to cast. + /// The expected reader. + protected static TReader EnsureReaderType(TActual reader) + where TActual : IDataReader + => reader is TReader r ? r : throw new InvalidCastException($"Expected reader type of ({typeof(TReader)}). Actual: ({reader.GetType()})"); + + /// + public void ExecuteReader(Action handler, CommandBehavior behavior = CommandBehavior.Default) + { + if (handler is null) throw new ArgumentNullException(nameof(handler)); + Contract.EndContractBlock(); + + // Open MUST occur before command creation as some DbCommands require it. + ConnectionProvider.Open((conn, state) => + { + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; + using TCommand cmd = PrepareCommand(conn); + cmd.ExecuteReader(reader => handler(EnsureReaderType(reader)), behavior); + }); + } + + /// + public T ExecuteReader(Func transform, CommandBehavior behavior = CommandBehavior.Default) + { + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + return ConnectionProvider.Open((conn, state) => + { + // Open MUST occur before command creation as some DbCommands require it. + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; + using TCommand cmd = PrepareCommand(conn); + return cmd.ExecuteReader(reader => transform(EnsureReaderType(reader)), behavior); + }); + } + + /// + public ValueTask ExecuteReaderAsync(Action handler, CommandBehavior behavior = CommandBehavior.Default) + { + return ConnectionProvider.OpenAsync(async (conn, state) => + { + // Open MUST occur before command creation as some DbCommands require it. + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; + using TCommand cmd = PrepareCommand(conn); + await cmd.ExecuteReaderAsync(ExecuteReaderAsyncCore, behavior, CancellationToken).ConfigureAwait(false); + }); + + ValueTask ExecuteReaderAsyncCore(IDataReader reader) + { + handler(EnsureReaderType(reader)); + return new ValueTask(); + } + } + + /// + public ValueTask ExecuteReaderAsync(Func handler, CommandBehavior behavior = CommandBehavior.Default) + { + return ConnectionProvider.OpenAsync(async (conn, state) => + { + // Open MUST occur before command creation as some DbCommands require it. + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; + using TCommand cmd = PrepareCommand(conn); + return await cmd.ExecuteReaderAsync(ExecuteReaderAsyncCore, behavior, CancellationToken).ConfigureAwait(false); + }); + + ValueTask ExecuteReaderAsyncCore(IDataReader reader) + => new(handler(EnsureReaderType(reader))); + } + + /// + public ValueTask ExecuteReaderAsync(Func handler, CommandBehavior behavior = CommandBehavior.Default) + => ConnectionProvider.OpenAsync(async (conn, state) => + { + // Open MUST occur before command creation as some DbCommands require it. + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; + using TCommand cmd = PrepareCommand(conn); + await cmd.ExecuteReaderAsync(reader => handler(EnsureReaderType(reader)), behavior, CancellationToken).ConfigureAwait(false); + }); + + /// + public ValueTask ExecuteReaderAsync(Func> handler, CommandBehavior behavior = CommandBehavior.Default) + => ConnectionProvider.OpenAsync(async (conn, state) => + { + // Open MUST occur before command creation as some DbCommands require it. + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; + using TCommand cmd = PrepareCommand(conn); + return await cmd.ExecuteReaderAsync(reader => handler(EnsureReaderType(reader)), behavior, CancellationToken).ConfigureAwait(false); + }); + + void IExecuteReader.ExecuteReader(Action handler, CommandBehavior behavior) + => ExecuteReader(reader => handler(reader), behavior); + + T IExecuteReader.ExecuteReader(Func transform, CommandBehavior behavior) + => ExecuteReader(reader => transform(reader), behavior); + + ValueTask IExecuteReader.ExecuteReaderAsync(Func handler, CommandBehavior behavior) + => ExecuteReaderAsync(reader => handler(reader), behavior); + + ValueTask IExecuteReader.ExecuteReaderAsync(Func> transform, CommandBehavior behavior) + => ExecuteReaderAsync(reader => transform(reader), behavior); + + /// + /// Calls ExecuteNonQuery on the underlying command but sets up a return parameter and returns that value. + /// + /// The value from the return parameter. + public object? ExecuteReturn() + // Open MUST occur before command creation as some DbCommands require it. + => ConnectionProvider.Open((conn, _) => + { + using TCommand cmd = PrepareCommand(conn); + IDbDataParameter returnParameter = cmd.AddReturnParameter(); + cmd.ExecuteNonQuery(); + return returnParameter.Value; + }); + + /// + /// Calls ExecuteNonQuery on the underlying command but sets up a return parameter and returns that value. + /// + /// The value from the return parameter. + public T ExecuteReturn() + => (T)ExecuteReturn()!; + + /// + /// Calls ExecuteNonQueryAsync on the underlying command but sets up a return parameter and returns that value. + /// + /// The value from the return parameter. + public ValueTask ExecuteReturnAsync() + { + CancellationToken.ThrowIfCancellationRequested(); + + // Open MUST occur before command creation as some DbCommands require it. + return ConnectionProvider.OpenAsync(async (conn, _) => + { + using TCommand cmd = PrepareCommand(conn); + IDbDataParameter returnParameter = cmd.AddReturnParameter(); + + if (cmd is DbCommand dbCommand) + await dbCommand.ExecuteNonQueryAsync(CancellationToken).ConfigureAwait(false); + else + cmd.ExecuteNonQuery(); + + return returnParameter.Value; + })!; + } + + /// + /// Calls ExecuteNonQueryAsync on the underlying command but sets up a return parameter and returns that value. + /// + /// The value from the return parameter. + public async ValueTask ExecuteReturnAsync() + => (T)(await ExecuteReturnAsync().ConfigureAwait(false))!; + + /// + /// Calls ExecuteNonQuery on the underlying command. + /// + /// The integer response from the method. (Records updated.) + public int ExecuteNonQuery() + => Execute(command => command.ExecuteNonQuery()); + + /// + /// Calls ExecuteScalar on the underlying command. + /// + /// The value returned from the method. + public object? ExecuteScalar() + => Execute(command => command.ExecuteScalar()); + + /// + /// Calls ExecuteScalar on the underlying command. + /// + /// The type expected. + /// The value returned from the method. + public T ExecuteScalar() + => (T)ExecuteScalar()!; + + /// + /// Calls ExecuteScalar on the underlying command. + /// + /// The type expected. + /// The value returned from the method. + public T ExecuteScalar(Func transform) + { + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + return transform(ExecuteScalar()); + } } diff --git a/Source/Core/ExpressiveDbCommand.cs b/Source/Core/ExpressiveDbCommand.cs index 22d3984..4836e4f 100644 --- a/Source/Core/ExpressiveDbCommand.cs +++ b/Source/Core/ExpressiveDbCommand.cs @@ -1,77 +1,83 @@ -using System.Collections.Generic; -using System.Data; -using System.Data.Common; - -namespace Open.Database.Extensions; +namespace Open.Database.Extensions; /// /// An abstraction for executing commands on a database using best practices and simplified expressive syntax. /// public class ExpressiveDbCommand : ExpressiveDbCommandBase { - /// Constructs a . - /// - public ExpressiveDbCommand( - IDbConnectionPool connectionPool, - CommandType type, - string command, - IEnumerable? @params = null) - : base(connectionPool, type, command, @params) - { - } + /// Constructs a . + /// + public ExpressiveDbCommand( + IDbConnectionPool connectionPool, + CommandType type, + string command, + IEnumerable? @params = null) + : base(connectionPool, type, command, @params) + { + } + + /// Constructs a . + /// + public ExpressiveDbCommand( + IDbConnectionFactory connFactory, + CommandType type, + string command, + IEnumerable? @params = null) + : base(connFactory, type, command, @params) + { + } - /// Constructs a . - /// - public ExpressiveDbCommand( - IDbConnectionFactory connFactory, - CommandType type, - string command, - IEnumerable? @params = null) - : base(connFactory, type, command, @params) - { - } + /// + public ExpressiveDbCommand( + Func connFactory, + CommandType type, + string command, + IEnumerable? @params = null) + : base(connFactory.AsPool(), type, command, @params) + { + } - /// Constructs a . - /// - public ExpressiveDbCommand( - DbConnection connection, - IDbTransaction? transaction, - CommandType type, - string command, - IEnumerable? @params = null) - : base(connection, transaction, type, command, @params) - { - } + /// Constructs a . + /// + public ExpressiveDbCommand( + DbConnection connection, + IDbTransaction? transaction, + CommandType type, + string command, + IEnumerable? @params = null) + : base(connection, transaction, type, command, @params) + { + } - /// Constructs a . - /// - public ExpressiveDbCommand( - DbConnection connection, - CommandType type, - string command, - IEnumerable? @params = null) - : base(connection, type, command, @params) - { - } + /// Constructs a . + /// + public ExpressiveDbCommand( + DbConnection connection, + CommandType type, + string command, + IEnumerable? @params = null) + : base(connection, type, command, @params) + { + } - /// Constructs a . - /// - public ExpressiveDbCommand( - IDbTransaction transaction, - CommandType type, - string command, - IEnumerable? @params = null) - : base(transaction, type, command, @params) - { - } + /// Constructs a . + /// + public ExpressiveDbCommand( + IDbTransaction transaction, + CommandType type, + string command, + IEnumerable? @params = null) + : base(transaction, type, command, @params) + { + } - /// - protected override void AddParams(DbCommand command) - { - foreach (var p in Params) - { - var np = command.AddParameter(p.Name, p.Value); - if (p.Type.HasValue) np.DbType = p.Type.Value; - } - } + /// + protected override void AddParams(DbCommand command) + { + foreach (Param p in Params) + { + IDbDataParameter np = command.AddParameter(p.Name, p.Value); + if (p.Type.HasValue) np.DbType = p.Type.Value; + } + } } diff --git a/Source/Core/ExpressiveDbCommandBase.cs b/Source/Core/ExpressiveDbCommandBase.cs index 6d6b669..9f715aa 100644 --- a/Source/Core/ExpressiveDbCommandBase.cs +++ b/Source/Core/ExpressiveDbCommandBase.cs @@ -1,13 +1,4 @@ -using Open.Database.Extensions.Core; -using System; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; -using System.Diagnostics.Contracts; -using System.Linq; -using System.Threading.Tasks; - -namespace Open.Database.Extensions; +namespace Open.Database.Extensions; /// /// An base class for executing commands on a database using best practices and simplified expressive syntax. @@ -19,277 +10,297 @@ namespace Open.Database.Extensions; /// The DB type enum to use for parameters. /// The type of this class in order to facilitate proper expressive notation. public abstract class ExpressiveDbCommandBase - : ExpressiveCommandBase, IExecuteReaderAsync - where TConnection : DbConnection - where TCommand : DbCommand - where TReader : DbDataReader - where TDbType : struct - where TThis : ExpressiveDbCommandBase + : ExpressiveCommandBase, IExecuteReaderAsync + where TConnection : DbConnection + where TCommand : DbCommand + where TReader : DbDataReader + where TDbType : struct + where TThis : ExpressiveDbCommandBase { - /// Constructs a an expressive command. - /// The pool to acquire connections from. - /// The command type. - /// The SQL command. - /// The list of params - protected ExpressiveDbCommandBase( - IDbConnectionPool connectionPool, - CommandType type, - string command, - IEnumerable? @params = null) - : base(connectionPool, type, command, @params) - { - } + /// Constructs a an expressive command. + /// The pool to acquire connections from. + /// The command type. + /// The SQL command. + /// The list of params + protected ExpressiveDbCommandBase( + IDbConnectionPool connectionPool, + CommandType type, + string command, + IEnumerable? @params = null) + : base(connectionPool, type, command, @params) + { + } + + /// Constructs a an expressive command. + /// The factory to generate connections from. + /// The command type. + /// The SQL command. + /// The list of params + protected ExpressiveDbCommandBase( + IDbConnectionFactory connFactory, + CommandType type, + string command, + IEnumerable? @params = null) + : base(connFactory, type, command, @params) + { + } - /// Constructs a an expressive command. - /// The factory to generate connections from. - /// The command type. - /// The SQL command. - /// The list of params - protected ExpressiveDbCommandBase( - IDbConnectionFactory connFactory, - CommandType type, - string command, - IEnumerable? @params = null) - : base(connFactory, type, command, @params) - { - } + /// + protected ExpressiveDbCommandBase( + Func connFactory, + CommandType type, + string command, + IEnumerable? @params = null) + : base(connFactory, type, command, @params) + { + } - /// Constructs a an expressive command. - /// The connection to execute the command on. - /// The optional transaction to execute the command on. - /// The command type. - /// The SQL command. - /// The list of params - protected ExpressiveDbCommandBase( - TConnection connection, - IDbTransaction? transaction, - CommandType type, - string command, - IEnumerable? @params = null) - : base(connection, transaction, type, command, @params) - { - } + /// Constructs a an expressive command. + /// The connection to execute the command on. + /// The optional transaction to execute the command on. + /// The command type. + /// The SQL command. + /// The list of params + protected ExpressiveDbCommandBase( + TConnection connection, + IDbTransaction? transaction, + CommandType type, + string command, + IEnumerable? @params = null) + : base(connection, transaction, type, command, @params) + { + } - /// Constructs a an expressive command. - /// The connection to execute the command on. - /// The command type. - /// The SQL command. - /// The list of params - protected ExpressiveDbCommandBase( - TConnection connection, - CommandType type, - string command, - IEnumerable? @params = null) - : base(connection, type, command, @params) - { - } + /// Constructs a an expressive command. + /// The connection to execute the command on. + /// The command type. + /// The SQL command. + /// The list of params + protected ExpressiveDbCommandBase( + TConnection connection, + CommandType type, + string command, + IEnumerable? @params = null) + : base(connection, type, command, @params) + { + } - /// Constructs a an expressive command. - /// The transaction to execute the command on. - /// The command type. - /// The SQL command. - /// The list of params - protected ExpressiveDbCommandBase( - IDbTransaction transaction, - CommandType type, - string command, - IEnumerable? @params = null) - : base(transaction, type, command, @params) - { - } + /// Constructs a an expressive command. + /// The transaction to execute the command on. + /// The command type. + /// The SQL command. + /// The list of params + protected ExpressiveDbCommandBase( + IDbTransaction transaction, + CommandType type, + string command, + IEnumerable? @params = null) + : base(transaction, type, command, @params) + { + } - /// - /// By default (false), for async methods, the underlying iteration operation for a reader will be .Read() whenever possible. If set to true, .ReadAsync() will be used. - /// Using .ReadAsync() can introduce unexpected latency and additional CPU overhead. - /// This should only be set to true if there is a clear reason why and should be profiled before and after. - /// - public bool UseAsyncRead { get; set; } + /// + /// By default (false), for async methods, the underlying iteration operation for a reader will be .Read() whenever possible. If set to true, .ReadAsync() will be used. + /// Using .ReadAsync() can introduce unexpected latency and additional CPU overhead. + /// This should only be set to true if there is a clear reason why and should be profiled before and after. + /// + public bool UseAsyncRead { get; set; } - /// - /// Sets the UseAsyncRead value. - /// - public TThis EnableAsyncRead(bool value = true) - { - UseAsyncRead = value; - return (TThis)this; - } + /// + /// Sets the UseAsyncRead value. + /// + public TThis EnableAsyncRead(bool value = true) + { + UseAsyncRead = value; + return (TThis)this; + } - /// - /// Calls ExecuteNonQueryAsync on the underlying command. - /// - /// The integer response from the method. - public ValueTask ExecuteNonQueryAsync() - => ExecuteAsync(command => new ValueTask(command.ExecuteNonQueryAsync(CancellationToken))); + /// + /// Calls ExecuteNonQueryAsync on the underlying command. + /// + /// The integer response from the method. + public ValueTask ExecuteNonQueryAsync() + => ExecuteAsync(command => new ValueTask(command.ExecuteNonQueryAsync(CancellationToken))); - /// - /// Calls ExecuteScalarAsync on the underlying command. - /// - /// The value returned from the method. - public ValueTask ExecuteScalarAsync() - => ExecuteAsync(command => new ValueTask(command.ExecuteScalarAsync(CancellationToken))); + /// + /// Calls ExecuteScalarAsync on the underlying command. + /// + /// The value returned from the method. + public ValueTask ExecuteScalarAsync() + => ExecuteAsync(command => new ValueTask(command.ExecuteScalarAsync(CancellationToken))); - /// - /// Asynchronously executes scalar on the underlying command. - /// - /// The type expected. - /// The transform function for the result. - /// The value returned from the method. - public async ValueTask ExecuteScalarAsync(Func transform) - { - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); + /// + /// Asynchronously executes scalar on the underlying command. + /// + /// The type expected. + /// The transform function for the result. + /// The value returned from the method. + public async ValueTask ExecuteScalarAsync(Func transform) + { + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); - return transform(await ExecuteScalarAsync().ConfigureAwait(false)); - } + return transform(await ExecuteScalarAsync().ConfigureAwait(false)); + } - /// - /// Asynchronously executes scalar on the underlying command and casts to the expected type. - /// - /// The type expected. - /// The value returned from the method. - public async ValueTask ExecuteScalarAsync() - => (T)await ExecuteScalarAsync().ConfigureAwait(false); + /// + /// Asynchronously executes scalar on the underlying command and casts to the expected type. + /// + /// The type expected. + /// The value returned from the method. + public async ValueTask ExecuteScalarAsync() + => (T)(await ExecuteScalarAsync().ConfigureAwait(false))!; - /// - /// Asynchronously executes scalar on the underlying command. - /// - /// The type expected. - /// The transform function (task) for the result. - /// The value returned from the method. - public async ValueTask ExecuteScalarAsync(Func> transform) - { - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); + /// + /// Asynchronously executes scalar on the underlying command. + /// + /// The type expected. + /// The transform function (task) for the result. + /// The value returned from the method. + public async ValueTask ExecuteScalarAsync(Func> transform) + { + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); - return await transform(await ExecuteScalarAsync().ConfigureAwait(false)).ConfigureAwait(false); - } + return await transform(await ExecuteScalarAsync().ConfigureAwait(false)).ConfigureAwait(false); + } - /// - /// Asynchronously iterates a IDataReader and returns the each result until the count is met. - /// - /// The return type of the transform function. - /// The transform function to process each IDataRecord. - /// The maximum number of records before complete. - /// The behavior to use with the data reader. - /// The value from the transform. - public ValueTask> TakeAsync(Func transform, int count, CommandBehavior behavior = CommandBehavior.Default) - { - if (transform is null) throw new ArgumentNullException(nameof(transform)); - if (count < 0) throw new ArgumentOutOfRangeException(nameof(count), count, "Cannot be negative."); - Contract.EndContractBlock(); + /// + /// Asynchronously iterates a IDataReader and returns the each result until the count is met. + /// + /// The return type of the transform function. + /// The transform function to process each IDataRecord. + /// The maximum number of records before complete. + /// The behavior to use with the data reader. + /// The value from the transform. + public ValueTask> TakeAsync(Func transform, int count, CommandBehavior behavior = CommandBehavior.Default) + { + if (transform is null) throw new ArgumentNullException(nameof(transform)); + if (count < 0) throw new ArgumentOutOfRangeException(nameof(count), count, "Cannot be negative."); + Contract.EndContractBlock(); - return count == 0 - ? new ValueTask>(Array.Empty()) - : TakeAsyncCore(); + return count == 0 + ? new ValueTask>(Array.Empty()) + : TakeAsyncCore(); - async ValueTask> TakeAsyncCore() - { - var results = new List(); - await this.IterateReaderWhileAsync(record => - { - results.Add(transform(record)); - return results.Count < count; - }, behavior).ConfigureAwait(false); - return results; - } - } + async ValueTask> TakeAsyncCore() + { + var results = new List(); + await this.IterateReaderWhileAsync(record => + { + results.Add(transform(record)); + return results.Count < count; + }, behavior).ConfigureAwait(false); + return results; + } + } - /// - /// Reads the first column from every record and returns the results as a list.. - /// values are converted to null. - /// - /// The list of transformed records. - public ValueTask> FirstOrdinalResultsAsync() - => ExecuteReaderAsync(reader => reader.FirstOrdinalResultsAsync(UseAsyncRead, CancellationToken), CommandBehavior.SequentialAccess | CommandBehavior.SingleResult); + /// + /// Reads the first column from every record and returns the results as a list.. + /// values are converted to null. + /// + /// The list of transformed records. + public ValueTask> FirstOrdinalResultsAsync() + => ExecuteReaderAsync(reader => reader.FirstOrdinalResultsAsync(UseAsyncRead, CancellationToken), CommandBehavior.SequentialAccess | CommandBehavior.SingleResult); - /// - /// Reads the first column from every record.. - /// values are converted to null. - /// - /// The enumerable of casted values. - public ValueTask> FirstOrdinalResultsAsync() - => ExecuteReaderAsync(reader => reader.FirstOrdinalResultsAsync(UseAsyncRead, CancellationToken), CommandBehavior.SequentialAccess | CommandBehavior.SingleResult); + /// + /// Reads the first column from every record.. + /// values are converted to null. + /// + /// The enumerable of casted values. + public ValueTask> FirstOrdinalResultsAsync() + => ExecuteReaderAsync(reader => reader.FirstOrdinalResultsAsync(UseAsyncRead, CancellationToken), CommandBehavior.SequentialAccess | CommandBehavior.SingleResult); - /// - /// Asynchronously iterates all records within the current result set using an IDataReader and returns the desired results. - /// - /// The first ordinal to include in the request to the reader for each record. - /// The remaining ordinals to request from the reader for each record. - /// The QueryResult that contains all the results and the column mappings. - public ValueTask> RetrieveAsync(int n, params int[] others) - => RetrieveAsync(Concat(n, others)); + /// + /// Asynchronously iterates all records within the current result set using an IDataReader and returns the desired results. + /// + /// The first ordinal to include in the request to the reader for each record. + /// The remaining ordinals to request from the reader for each record. + /// The QueryResult that contains all the results and the column mappings. +#if NET8_0_OR_GREATER + public ValueTask> RetrieveAsync(int n, params IEnumerable others) + => RetrieveAsync(others.Prepend(n)); +#else + public ValueTask> RetrieveAsync(int n, params int[] others) + => RetrieveAsync(Concat(n, others)); +#endif - /// - /// Iterates all records within the current result set using an IDataReader and returns the desired results. - /// - /// The first column name to include in the request to the reader for each record. - /// The remaining column names to request from the reader for each record. - /// The QueryResult that contains all the results and the column mappings. - public ValueTask> RetrieveAsync(string c, params string[] others) - => RetrieveAsync(Concat(c, others)); + /// + /// Iterates all records within the current result set using an IDataReader and returns the desired results. + /// + /// The first column name to include in the request to the reader for each record. + /// The remaining column names to request from the reader for each record. + /// The QueryResult that contains all the results and the column mappings. +#if NET8_0_OR_GREATER + public ValueTask> RetrieveAsync(string c, params IEnumerable others) + => RetrieveAsync(others.Prepend(c)); +#else + public ValueTask> RetrieveAsync(string c, params string[] others) + => RetrieveAsync(Concat(c, others)); +#endif - /// - /// Asynchronously returns all records via a transform function. - /// - /// The desired column names. - /// The behavior to use with the data reader. - /// A task containing the list of results. - public async ValueTask> ToListAsync(Func transform, CommandBehavior behavior = CommandBehavior.Default) - { - var results = new List(); - await this.IterateReaderAsync(record => results.Add(transform(record)), behavior).ConfigureAwait(false); - return results; - } + /// + /// Asynchronously returns all records via a transform function. + /// + /// The desired column names. + /// The behavior to use with the data reader. + /// A task containing the list of results. + public async ValueTask> ToListAsync(Func transform, CommandBehavior behavior = CommandBehavior.Default) + { + var results = new List(); + await this.IterateReaderAsync(record => results.Add(transform(record)), behavior).ConfigureAwait(false); + return results; + } - /// - /// Asynchronously returns all records and iteratively attempts to map the fields to type T. - /// - /// The model type to map the values to (using reflection). - /// An override map of field names to column names where the keys are the property names, and values are the column names. - /// A task containing the list of results. - public ValueTask> ResultsAsync(IEnumerable>? fieldMappingOverrides) where T : new() - => ResultsAsync(fieldMappingOverrides?.Select(kvp => (kvp.Key, kvp.Value))); + /// + /// Asynchronously returns all records and iteratively attempts to map the fields to type T. + /// + /// The model type to map the values to (using reflection). + /// An override map of field names to column names where the keys are the property names, and values are the column names. + /// A task containing the list of results. + public ValueTask> ResultsAsync(IEnumerable>? fieldMappingOverrides) where T : new() + => ResultsAsync(fieldMappingOverrides?.Select(kvp => (kvp.Key, kvp.Value))); - /// - /// Asynchronously returns all records and iteratively attempts to map the fields to type T. - /// - /// The model type to map the values to (using reflection). - /// An override map of field names to column names where the keys are the property names, and values are the column names. - /// A task containing the list of results. - public ValueTask> ResultsAsync(params (string Field, string? Column)[] fieldMappingOverrides) where T : new() - => ResultsAsync(fieldMappingOverrides as IEnumerable<(string Field, string? Column)>); + /// + /// Asynchronously returns all records and iteratively attempts to map the fields to type T. + /// + /// The model type to map the values to (using reflection). + /// An override map of field names to column names where the keys are the property names, and values are the column names. + /// A task containing the list of results. + public ValueTask> ResultsAsync(params (string Field, string? Column)[] fieldMappingOverrides) where T : new() + => ResultsAsync(fieldMappingOverrides as IEnumerable<(string Field, string? Column)>); - /// - /// Asynchronously iterates all records within the first result set using an IDataReader and returns the results. - /// - /// The QueryResult that contains all the results and the column mappings. - public ValueTask> RetrieveAsync() - => ExecuteReaderAsync(reader => reader.RetrieveAsync(useReadAsync: UseAsyncRead), CommandBehavior.SingleResult); + /// + /// Asynchronously iterates all records within the first result set using an IDataReader and returns the results. + /// + /// The QueryResult that contains all the results and the column mappings. + public ValueTask> RetrieveAsync() + => ExecuteReaderAsync(reader => reader.RetrieveAsync(useReadAsync: UseAsyncRead), CommandBehavior.SingleResult); - /// - /// Asynchronously iterates all records within the current result set using an IDataReader and returns the desired results. - /// - /// The ordinals to request from the reader for each record. - /// The QueryResult that contains all the results and the column mappings. - public ValueTask> RetrieveAsync(IEnumerable ordinals) - => ExecuteReaderAsync(reader => reader.RetrieveAsync(ordinals, useReadAsync: UseAsyncRead), CommandBehavior.SingleResult); + /// + /// Asynchronously iterates all records within the current result set using an IDataReader and returns the desired results. + /// + /// The ordinals to request from the reader for each record. + /// The QueryResult that contains all the results and the column mappings. + public ValueTask> RetrieveAsync(IEnumerable ordinals) + => ExecuteReaderAsync(reader => reader.RetrieveAsync(ordinals, useReadAsync: UseAsyncRead), CommandBehavior.SingleResult); - /// - /// Iterates all records within the first result set using an IDataReader and returns the desired results as a list of Dictionaries containing only the specified column values. - /// - /// The column names to select. - /// Orders the results arrays by ordinal. - /// The QueryResult that contains all the results and the column mappings. - public ValueTask> RetrieveAsync(IEnumerable columnNames, bool normalizeColumnOrder = false) - => ExecuteReaderAsync(reader => reader.RetrieveAsync(columnNames, normalizeColumnOrder, useReadAsync: UseAsyncRead)); + /// + /// Iterates all records within the first result set using an IDataReader and returns the desired results as a list of Dictionaries containing only the specified column values. + /// + /// The column names to select. + /// Orders the results arrays by ordinal. + /// The QueryResult that contains all the results and the column mappings. + public ValueTask> RetrieveAsync(IEnumerable columnNames, bool normalizeColumnOrder = false) + => ExecuteReaderAsync(reader => reader.RetrieveAsync(columnNames, normalizeColumnOrder, useReadAsync: UseAsyncRead)); - /// - /// Asynchronously returns all records and iteratively attempts to map the fields to type T. - /// - /// The model type to map the values to (using reflection). - /// An override map of field names to column names where the keys are the property names, and values are the column names. - /// A task containing the list of results. - public ValueTask> ResultsAsync(IEnumerable<(string Field, string? Column)>? fieldMappingOverrides) - where T : new() - => ExecuteReaderAsync(reader => reader.ResultsBufferedAsync(fieldMappingOverrides, useReadAsync: UseAsyncRead)); + /// + /// Asynchronously returns all records and iteratively attempts to map the fields to type T. + /// + /// The model type to map the values to (using reflection). + /// An override map of field names to column names where the keys are the property names, and values are the column names. + /// A task containing the list of results. + public ValueTask> ResultsAsync(IEnumerable<(string Field, string? Column)>? fieldMappingOverrides) + where T : new() + => ExecuteReaderAsync(reader => reader.ResultsBufferedAsync(fieldMappingOverrides, useReadAsync: UseAsyncRead)); } diff --git a/Source/Core/Extensions/Command.AddParameter.cs b/Source/Core/Extensions/Command.AddParameter.cs index d3e1918..b1057e2 100644 --- a/Source/Core/Extensions/Command.AddParameter.cs +++ b/Source/Core/Extensions/Command.AddParameter.cs @@ -1,35 +1,84 @@ -using System; -using System.Data; -using System.Diagnostics.Contracts; - -namespace Open.Database.Extensions; +namespace Open.Database.Extensions; public static partial class CommandExtensions { - private const string ParameterNamesNotEmptyMessage = "Parameter names cannot be empty or white space."; - private const string ParameterNamesOnlyNullForReturn = "Parameter names can only be null for a return parameter."; + private const string ParameterNamesNotEmptyMessage = "Parameter names cannot be empty or white space."; + private const string ParameterNamesOnlyNullForReturn = "Parameter names can only be null for a return parameter."; - /// - public static IDbDataParameter AddParameter( + /// + public static IDbDataParameter AddParameter( this IDbCommand target, string name, object? value = null) - { - if (target is null) - throw new ArgumentNullException(nameof(target)); - if (name is null) - throw new ArgumentNullException(nameof(name)); - else if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentException(ParameterNamesNotEmptyMessage, nameof(name)); - Contract.EndContractBlock(); - - var c = target.CreateParameter(); - c.ParameterName = name; - if (value != null) // DBNull.Value is allowed. - c.Value = value; - target.Parameters.Add(c); - return c; - } + { + if (target is null) + throw new ArgumentNullException(nameof(target)); + if (name is null) + throw new ArgumentNullException(nameof(name)); + else if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentException(ParameterNamesNotEmptyMessage, nameof(name)); + Contract.EndContractBlock(); + + IDbDataParameter c = target.CreateParameter(); + c.ParameterName = name; + c.Value = value; + target.Parameters.Add(c); + return c; + } + + /// + public static IDbDataParameter AddParameter( + this IDbCommand target, + string name, + string? value) => AddParameter(target, name, value, DbType.String); + + /// + public static IDbDataParameter AddParameter( + this IDbCommand target, + string name, + int value) => AddParameter(target, name, value, DbType.Int32); + + /// + public static IDbDataParameter AddParameter( + this IDbCommand target, + string name, + long value) => AddParameter(target, name, value, DbType.Int64); + + /// + public static IDbDataParameter AddParameter( + this IDbCommand target, + string name, + short value) => AddParameter(target, name, value, DbType.Int16); + + /// + public static IDbDataParameter AddParameter( + this IDbCommand target, + string name, + DateTime value) => AddParameter(target, name, value, DbType.DateTime); + + /// + public static IDbDataParameter AddParameter( + this IDbCommand target, + string name, + double value) => AddParameter(target, name, value, DbType.Double); + + /// + public static IDbDataParameter AddParameter( + this IDbCommand target, + string name, + decimal value) => AddParameter(target, name, value, DbType.Decimal); + + /// + public static IDbDataParameter AddParameter( + this IDbCommand target, + string name, + Guid value) => AddParameter(target, name, value, DbType.Guid); + + /// + public static IDbDataParameter AddParameter( + this IDbCommand target, + string name, + DateTimeOffset value) => AddParameter(target, name, value, DbType.DateTimeOffset); /// /// Shortcut for adding a command parameter. @@ -43,15 +92,15 @@ public static IDbDataParameter AddParameter( public static IDbDataParameter AddParameter( this IDbCommand target, string name, - object value, + object? value, DbType type, ParameterDirection direction = ParameterDirection.Input) - { - var p = target.AddParameterType(name, type); - p.Value = value; - p.Direction = direction; - return p; - } + { + IDbDataParameter p = target.AddParameterType(name, type); + p.Value = value; + p.Direction = direction; + return p; + } /// /// Shortcut for adding a typed (non-input) command parameter. @@ -62,22 +111,22 @@ public static IDbDataParameter AddParameterType( string? name, DbType type, ParameterDirection direction = ParameterDirection.Input) - { - if (target is null) - throw new ArgumentNullException(nameof(target)); - if (direction != ParameterDirection.ReturnValue && name == null) - throw new ArgumentNullException(nameof(name), ParameterNamesOnlyNullForReturn); - else if (name != null && string.IsNullOrWhiteSpace(name)) - throw new ArgumentException(ParameterNamesNotEmptyMessage, nameof(name)); - Contract.EndContractBlock(); - - var c = target.CreateParameter(); - if (name != null) c.ParameterName = name; - c.DbType = type; - c.Direction = direction; - target.Parameters.Add(c); - return c; - } + { + if (target is null) + throw new ArgumentNullException(nameof(target)); + if (direction != ParameterDirection.ReturnValue && name == null) + throw new ArgumentNullException(nameof(name), ParameterNamesOnlyNullForReturn); + else if (name != null && string.IsNullOrWhiteSpace(name)) + throw new ArgumentException(ParameterNamesNotEmptyMessage, nameof(name)); + Contract.EndContractBlock(); + + IDbDataParameter c = target.CreateParameter(); + if (name != null) c.ParameterName = name; + c.DbType = type; + c.Direction = direction; + target.Parameters.Add(c); + return c; + } /// /// Shortcut for adding command a typed return parameter. @@ -87,23 +136,23 @@ public static IDbDataParameter AddReturnParameter( this IDbCommand target, DbType type, string? name = null) - => target.AddParameterType(name, type, ParameterDirection.ReturnValue); + => target.AddParameterType(name, type, ParameterDirection.ReturnValue); /// /// Shortcut for adding command a return parameter. /// /// public static IDbDataParameter AddReturnParameter(this IDbCommand target, - string? name = null) - { - if (target is null) - throw new ArgumentNullException(nameof(target)); - Contract.EndContractBlock(); - - var c = target.CreateParameter(); - if (!string.IsNullOrWhiteSpace(name)) c.ParameterName = name; - c.Direction = ParameterDirection.ReturnValue; - target.Parameters.Add(c); - return c; - } + string? name = null) + { + if (target is null) + throw new ArgumentNullException(nameof(target)); + Contract.EndContractBlock(); + + IDbDataParameter c = target.CreateParameter(); + if (!string.IsNullOrWhiteSpace(name)) c.ParameterName = name; + c.Direction = ParameterDirection.ReturnValue; + target.Parameters.Add(c); + return c; + } } diff --git a/Source/Core/Extensions/Command._.cs b/Source/Core/Extensions/Command._.cs index 929be26..d1cc1bb 100644 --- a/Source/Core/Extensions/Command._.cs +++ b/Source/Core/Extensions/Command._.cs @@ -1,13 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Data; -using System.Data.Common; -using System.Diagnostics.Contracts; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - + namespace Open.Database.Extensions; /// @@ -15,223 +6,264 @@ namespace Open.Database.Extensions; /// public static partial class CommandExtensions { - /// - /// Iterates all records using an and returns the desired results as a list. - /// - /// The return type of the transform function. - /// The to generate a reader from. - /// The behavior to use with the data reader. - /// The transform function to process each . - public static List ToList( + #region Connection.EnsureOpen Shortcuts. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ConnectionState EnsureOpen(this IDbCommand command) + { +#if DEBUG + if (command.Connection is null) throw new InvalidOperationException("Cannot execute a command with a null connection."); +#endif + return command.Connection!.EnsureOpen(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ValueTask EnsureOpenAsync(this IDbCommand command, CancellationToken cancellationToken) + { +#if DEBUG + if (command.Connection is null) throw new ArgumentException("Cannot execute a command with a null connection."); +#endif + return command.Connection!.EnsureOpenAsync(cancellationToken); + } + #endregion + + /// + /// Iterates all records using an and returns the desired results as a list. + /// + /// The return type of the transform function. + /// The to generate a reader from. + /// The behavior to use with the data reader. + /// The transform function to process each . + public static List ToList( this IDbCommand command, - CommandBehavior behavior, - Func transform) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - var state = command.Connection.EnsureOpen(); - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var reader = command.ExecuteReader(behavior); - return reader.Select(transform).ToList(); - } - - /// - public static List ToList(this IDbCommand command, - Func transform, - CommandBehavior behavior = CommandBehavior.Default) - => ToList(command, behavior, transform); + CommandBehavior behavior, + Func transform) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + ConnectionState state = command.EnsureOpen(); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; + using IDataReader reader = command.ExecuteReader(behavior); + return reader.Select(transform).ToList(); + } + + /// + public static List ToList(this IDbCommand command, + Func transform, + CommandBehavior behavior = CommandBehavior.Default) + => ToList(command, behavior, transform); /// public static async ValueTask> ToListAsync(this DbCommand command, - CommandBehavior behavior, - Func> transform, - CancellationToken cancellationToken = default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - var state = await command.Connection.EnsureOpenAsync(cancellationToken).ConfigureAwait(true); - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; -#if NETSTANDARD2_1 - await using var reader = await command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(true); + CommandBehavior behavior, + Func> transform, + CancellationToken cancellationToken = default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + ConnectionState state = await command.EnsureOpenAsync(cancellationToken).ConfigureAwait(false); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; +#if NETSTANDARD2_0 #else - using var reader = await command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(true); + await #endif - return await reader.ToListAsync(transform, cancellationToken).ConfigureAwait(false); - } - - /// - public static ValueTask> ToListAsync(this DbCommand command, - Func> transform, - CommandBehavior behavior = CommandBehavior.Default, - CancellationToken cancellationToken = default) - => ToListAsync(command, behavior, transform, cancellationToken); - - /// - /// Asynchronously iterates all records using an and returns the desired results as a list. - /// - /// The return type of the transform function. - /// The to generate a reader from. - /// The behavior to use with the data reader. - /// The transform function to process each . - /// If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. - /// The cancellation token. - /// A task containing a list of all results. - public static async ValueTask> ToListAsync(this DbCommand command, - CommandBehavior behavior, - Func transform, - bool useReadAsync = true, - CancellationToken cancellationToken = default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - var state = await command.Connection.EnsureOpenAsync(cancellationToken).ConfigureAwait(true); - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - -#if NETSTANDARD2_1 - await using var reader = await command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(true); + using DbDataReader reader = await command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(false); + + return await reader.ToListAsync(transform, cancellationToken).ConfigureAwait(false); + } + + /// + public static ValueTask> ToListAsync(this DbCommand command, + Func> transform, + CommandBehavior behavior = CommandBehavior.Default, + CancellationToken cancellationToken = default) + => ToListAsync(command, behavior, transform, cancellationToken); + + /// + /// Asynchronously iterates all records using an and returns the desired results as a list. + /// + /// The return type of the transform function. + /// The to generate a reader from. + /// The behavior to use with the data reader. + /// The transform function to process each . + /// If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. + /// The cancellation token. + /// A task containing a list of all results. + public static async ValueTask> ToListAsync(this DbCommand command, + CommandBehavior behavior, + Func transform, + bool useReadAsync = true, + CancellationToken cancellationToken = default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + ConnectionState state = await command.EnsureOpenAsync(cancellationToken).ConfigureAwait(false); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; + +#if NETSTANDARD2_0 #else - using var reader = await command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(true); + await #endif - if (useReadAsync) return await reader.ToListAsync(transform, cancellationToken).ConfigureAwait(false); + using DbDataReader reader = await command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(false); + + if (useReadAsync) return await reader.ToListAsync(transform, cancellationToken).ConfigureAwait(false); - var r = reader.ToList(transform, cancellationToken); - return r; - } + var r = reader.ToList(transform, cancellationToken); + return r; + } /// public static ValueTask> ToListAsync(this DbCommand command, - CommandBehavior behavior, - Func transform, - CancellationToken cancellationToken = default) - => ToListAsync(command, behavior, transform, true, cancellationToken); + CommandBehavior behavior, + Func transform, + CancellationToken cancellationToken = default) + => ToListAsync(command, behavior, transform, true, cancellationToken); /// public static ValueTask> ToListAsync(this DbCommand command, - Func transform, - CommandBehavior behavior = CommandBehavior.Default, - bool useReadAsync = true, - CancellationToken cancellationToken = default) - => ToListAsync(command, behavior, transform, useReadAsync, cancellationToken); - - /// - /// Iterates all records using an and returns the desired results as an array. - /// - /// The return type of the transform function. - /// The to generate a reader from. - /// The behavior to use with the data reader. - /// The transform function to process each . - public static T[] ToArray(this IDbCommand command, CommandBehavior behavior, Func transform) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - var state = command.Connection.EnsureOpen(); - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var reader = command.ExecuteReader(behavior); - return reader.Select(transform).ToArray(); - } + Func transform, + CommandBehavior behavior = CommandBehavior.Default, + bool useReadAsync = true, + CancellationToken cancellationToken = default) + => ToListAsync(command, behavior, transform, useReadAsync, cancellationToken); + + /// + /// Iterates all records using an and returns the desired results as an array. + /// + /// The return type of the transform function. + /// The to generate a reader from. + /// The behavior to use with the data reader. + /// The transform function to process each . + public static T[] ToArray(this IDbCommand command, CommandBehavior behavior, Func transform) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + ConnectionState state = command.EnsureOpen(); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; + using IDataReader reader = command.ExecuteReader(behavior); + return reader.Select(transform).ToArray(); + } /// - public static T[] ToArray(this IDbCommand command, Func transform, CommandBehavior behavior = CommandBehavior.Default) - => ToArray(command, behavior, transform); + public static T[] ToArray(this IDbCommand command, Func transform, CommandBehavior behavior = CommandBehavior.Default) + => ToArray(command, behavior, transform); /// public static ImmutableArray ToImmutableArray(this IDbCommand command, CommandBehavior behavior, Func transform) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - var state = command.Connection.EnsureOpen(); - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var reader = command.ExecuteReader(behavior); - return reader.Select(transform).ToImmutableArray(); - } + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + ConnectionState state = command.EnsureOpen(); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; + using IDataReader reader = command.ExecuteReader(behavior); + return reader.Select(transform).ToImmutableArray(); + } /// public static ImmutableArray ToImmutableArray(this IDbCommand command, Func transform, CommandBehavior behavior = CommandBehavior.Default) - => ToImmutableArray(command, behavior, transform); - - /// - /// Loads all data from a command through an into a DataTable. - /// - /// The to generate a reader from. - /// The behavior to use with the data reader. - /// The resultant DataTable. - public static DataTable ToDataTable(this IDbCommand command, CommandBehavior behavior = CommandBehavior.SequentialAccess) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - Contract.EndContractBlock(); - - var state = command.Connection.EnsureOpen(); - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var reader = command.ExecuteReader(behavior); - return reader.ToDataTable(); - } - - /// - /// Loads all data from a command through an into a DataTables. - /// Calls .NextResult() to check for more results. - /// - /// The to generate a reader from. - /// The behavior to use with the data reader. - /// The resultant list of DataTables. - public static List ToDataTables(this IDbCommand command, CommandBehavior behavior = CommandBehavior.SequentialAccess) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - Contract.EndContractBlock(); - - var state = command.Connection.EnsureOpen(); - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var reader = command.ExecuteReader(behavior); - return reader.ToDataTables(); - } - - /// - /// Executes a reader on a command with a handler function. - /// - /// The to generate a reader from. - /// The handler function for each . - /// The behavior to use with the data reader. - public static void ExecuteReader(this IDbCommand command, Action handler, CommandBehavior behavior = CommandBehavior.Default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (handler is null) throw new ArgumentNullException(nameof(handler)); - Contract.EndContractBlock(); - - var state = command.Connection.EnsureOpen(); - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var reader = command.ExecuteReader(behavior); - handler(reader); - } - - /// - /// Executes a reader on a command with a transform function. - /// + => ToImmutableArray(command, behavior, transform); + + /// + /// Loads all data from a command through an into a DataTable. + /// + /// The to generate a reader from. + /// The behavior to use with the data reader. + /// The resultant DataTable. + public static DataTable ToDataTable(this IDbCommand command, CommandBehavior behavior = CommandBehavior.SequentialAccess) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + Contract.EndContractBlock(); + + ConnectionState state = command.EnsureOpen(); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; + using IDataReader reader = command.ExecuteReader(behavior); + return reader.ToDataTable(); + } + + /// + /// Loads all data from a command through an into a DataTables. + /// Calls .NextResult() to check for more results. + /// + /// The to generate a reader from. + /// The behavior to use with the data reader. + /// The resultant list of DataTables. + public static List ToDataTables(this IDbCommand command, CommandBehavior behavior = CommandBehavior.SequentialAccess) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + Contract.EndContractBlock(); + + ConnectionState state = command.EnsureOpen(); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; + using IDataReader reader = command.ExecuteReader(behavior); + return reader.ToDataTables(); + } + + /// + /// Executes a reader on a command with a handler function. + /// + /// The to generate a reader from. + /// The handler function for each . + /// The behavior to use with the data reader. + public static void ExecuteReader(this IDbCommand command, Action handler, CommandBehavior behavior = CommandBehavior.Default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (handler is null) throw new ArgumentNullException(nameof(handler)); + Contract.EndContractBlock(); + + ConnectionState state = command.EnsureOpen(); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; + using IDataReader reader = command.ExecuteReader(behavior); + handler(reader); + } + + /// + /// Executes a reader on a command with a transform function. + /// /// The default behavior will open a connection, execute the reader and close the connection it if was not already open. - /// The return type of the transform function. - /// The to generate a reader from. - /// The transform function for each . - /// The behavior to use with the data reader. - /// The result of the transform. - public static T ExecuteReader(this IDbCommand command, Func transform, CommandBehavior behavior = CommandBehavior.Default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - var state = command.Connection.EnsureOpen(); - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var reader = command.ExecuteReader(behavior); - return transform(reader); - } + /// The return type of the transform function. + /// The to generate a reader from. + /// The transform function for each . + /// The behavior to use with the data reader. + /// The result of the transform. + public static T ExecuteReader(this IDbCommand command, Func transform, CommandBehavior behavior = CommandBehavior.Default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + ConnectionState state = command.EnsureOpen(); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; + using IDataReader reader = command.ExecuteReader(behavior); + return transform(reader); + } + + /// + /// If the is derived from , this will call ; + /// otherwise it will call . + /// + /// The to generate a reader from. + /// The behavior to use with the data reader. + /// The cancellation token. + public static async ValueTask ExecuteReaderAsync(this IDbCommand command, + CommandBehavior behavior = CommandBehavior.Default, + CancellationToken cancellationToken = default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + Contract.EndContractBlock(); + + return command is DbCommand c + ? await c.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(false) + : command.ExecuteReader(behavior); + } /// /// Asynchronously executes a reader on a command with a handler function. @@ -241,19 +273,23 @@ public static T ExecuteReader(this IDbCommand command, Func t /// The behavior to use with the data reader. /// The cancellation token. public static async ValueTask ExecuteReaderAsync(this DbCommand command, - Action handler, - CommandBehavior behavior = CommandBehavior.Default, - CancellationToken cancellationToken = default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (handler is null) throw new ArgumentNullException(nameof(handler)); - Contract.EndContractBlock(); - - var state = await command.Connection.EnsureOpenAsync(cancellationToken).ConfigureAwait(true); - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var reader = await command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(true); - handler(reader); - } + Action handler, + CommandBehavior behavior = CommandBehavior.Default, + CancellationToken cancellationToken = default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (handler is null) throw new ArgumentNullException(nameof(handler)); + Contract.EndContractBlock(); + + ConnectionState state = await command.EnsureOpenAsync(cancellationToken).ConfigureAwait(false); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; +#if NETSTANDARD2_0 +#else + await +#endif + using DbDataReader reader = await command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(false); + handler(reader); + } /// /// Asynchronously executes a reader on a command with a handler function. @@ -264,45 +300,49 @@ public static async ValueTask ExecuteReaderAsync(this DbCommand command, /// The cancellation token. /// public static async ValueTask ExecuteReaderAsync(this IDbCommand command, - Func handler, - CommandBehavior behavior = CommandBehavior.Default, - CancellationToken cancellationToken = default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (handler is null) throw new ArgumentNullException(nameof(handler)); - Contract.EndContractBlock(); - - if (command is DbCommand c) - { - await c.ExecuteReaderAsync(reader => handler(reader), behavior, cancellationToken).ConfigureAwait(true); - return; - } - - var state = command.Connection.EnsureOpen(); - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var reader = command.ExecuteReader(behavior); - await handler(reader).ConfigureAwait(false); - } + Func handler, + CommandBehavior behavior = CommandBehavior.Default, + CancellationToken cancellationToken = default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (handler is null) throw new ArgumentNullException(nameof(handler)); + Contract.EndContractBlock(); + + if (command is DbCommand c) + { + await c.ExecuteReaderAsync(reader => handler(reader), behavior, cancellationToken).ConfigureAwait(false); + return; + } + + ConnectionState state = command.EnsureOpen(); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; + using IDataReader reader = command.ExecuteReader(behavior); + await handler(reader).ConfigureAwait(false); + } /// The to generate a reader from. - /// The handler function for each . + /// The handler function for the . /// The behavior to use with the data reader. /// The cancellation token. /// public static async ValueTask ExecuteReaderAsync(this DbCommand command, - Func handler, - CommandBehavior behavior = CommandBehavior.Default, - CancellationToken cancellationToken = default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (handler is null) throw new ArgumentNullException(nameof(handler)); - Contract.EndContractBlock(); - - var state = await command.Connection.EnsureOpenAsync(cancellationToken).ConfigureAwait(true); - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var reader = await command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(true); - await handler(reader).ConfigureAwait(false); - } + Func handler, + CommandBehavior behavior = CommandBehavior.Default, + CancellationToken cancellationToken = default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (handler is null) throw new ArgumentNullException(nameof(handler)); + Contract.EndContractBlock(); + + ConnectionState state = await command.EnsureOpenAsync(cancellationToken).ConfigureAwait(false); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; +#if NETSTANDARD2_0 +#else + await +#endif + using DbDataReader reader = await command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(false); + await handler(reader).ConfigureAwait(false); + } /// /// Asynchronously executes a reader on a command with a transform function. @@ -314,70 +354,78 @@ public static async ValueTask ExecuteReaderAsync(this DbCommand command, /// The cancellation token. /// The result of the transform. public static async ValueTask ExecuteReaderAsync(this DbCommand command, - Func transform, - CommandBehavior behavior = CommandBehavior.Default, - CancellationToken cancellationToken = default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - var state = await command.Connection.EnsureOpenAsync(cancellationToken).ConfigureAwait(true); - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var reader = await command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(true); - return transform(reader); - } - - /// The to generate a reader from. - /// The transform function for each . - /// The behavior to use with the data reader. - /// The cancellation token. + Func transform, + CommandBehavior behavior = CommandBehavior.Default, + CancellationToken cancellationToken = default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + ConnectionState state = await command.EnsureOpenAsync(cancellationToken).ConfigureAwait(false); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; +#if NETSTANDARD2_0 +#else + await +#endif + using DbDataReader reader = await command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(false); + return transform(reader); + } + + /// The to generate a reader from. + /// The transform function for each . + /// The behavior to use with the data reader. + /// The cancellation token. /// public static ValueTask ExecuteReaderAsync(this IDbCommand command, - Func> transform, - CommandBehavior behavior = CommandBehavior.Default, - CancellationToken cancellationToken = default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - return command is DbCommand c - ? ExecuteReaderAsync(c, reader => transform(reader), behavior, cancellationToken) - : ExecuteReaderAsyncCore(command, transform, behavior); + Func> transform, + CommandBehavior behavior = CommandBehavior.Default, + CancellationToken cancellationToken = default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + return command is DbCommand c + ? ExecuteReaderAsync(c, reader => transform(reader), behavior, cancellationToken) + : ExecuteReaderAsyncCore(command, transform, behavior); static async ValueTask ExecuteReaderAsyncCore(IDbCommand command, Func> transform, CommandBehavior behavior) - { - var state = command.Connection.EnsureOpen(); - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var reader = command.ExecuteReader(behavior); - return await transform(reader).ConfigureAwait(false); - } - } + { + ConnectionState state = command.EnsureOpen(); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; + using IDataReader reader = command.ExecuteReader(behavior); + return await transform(reader).ConfigureAwait(false); + } + } /// public static async ValueTask ExecuteReaderAsync(this DbCommand command, - Func> transform, - CommandBehavior behavior = CommandBehavior.Default, - CancellationToken cancellationToken = default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - var state = await command.Connection.EnsureOpenAsync(cancellationToken).ConfigureAwait(true); - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var reader = await command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(true); - return await transform(reader).ConfigureAwait(false); - } + Func> transform, + CommandBehavior behavior = CommandBehavior.Default, + CancellationToken cancellationToken = default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + ConnectionState state = await command.EnsureOpenAsync(cancellationToken).ConfigureAwait(false); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; +#if NETSTANDARD2_0 +#else + await +#endif + using DbDataReader reader = await command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(false); + return await transform(reader).ConfigureAwait(false); + } /// - public static TResult IterateReader( - this IDbCommand command, - Func transform, - Func, TResult> selector, - CommandBehavior behavior = CommandBehavior.Default) - => IterateReader(command, behavior, transform, selector); + public static TResult IterateReader( + this IDbCommand command, + Func transform, + Func, TResult> selector, + CommandBehavior behavior = CommandBehavior.Default) + => IterateReader(command, behavior, transform, selector); /// /// Executes a reader on a command with a transform function. @@ -390,21 +438,21 @@ public static TResult IterateReader( /// Provides an IEnumerable<TEntity> to select individual results by. /// public static TResult IterateReader( - this IDbCommand command, - CommandBehavior behavior, - Func transform, - Func, TResult> selector) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - if (selector is null) throw new ArgumentNullException(nameof(selector)); - Contract.EndContractBlock(); - - var state = command.Connection.EnsureOpen(); - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var reader = command.ExecuteReader(behavior); - return selector(reader.Select(transform)); - } + this IDbCommand command, + CommandBehavior behavior, + Func transform, + Func, TResult> selector) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + if (selector is null) throw new ArgumentNullException(nameof(selector)); + Contract.EndContractBlock(); + + ConnectionState state = command.EnsureOpen(); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; + using IDataReader reader = command.ExecuteReader(behavior); + return selector(reader.Select(transform)); + } /// /// Iterates an on a command with a handler function. @@ -414,28 +462,28 @@ public static TResult IterateReader( /// The handler function for each . public static void IterateReader( this IDbCommand command, - CommandBehavior behavior, - Action handler) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (handler is null) throw new ArgumentNullException(nameof(handler)); - Contract.EndContractBlock(); - - var state = command.Connection.EnsureOpen(); - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var reader = command.ExecuteReader(behavior); - reader.ForEach(handler); - } - - /// - public static void IterateReader( + CommandBehavior behavior, + Action handler) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (handler is null) throw new ArgumentNullException(nameof(handler)); + Contract.EndContractBlock(); + + ConnectionState state = command.EnsureOpen(); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; + using IDataReader reader = command.ExecuteReader(behavior); + reader.ForEach(handler); + } + + /// + public static void IterateReader( this IDbCommand command, Action handler, CommandBehavior behavior = CommandBehavior.Default) - => IterateReader(command, behavior, handler); + => IterateReader(command, behavior, handler); - internal static IEnumerable IterateReaderInternal(IDbCommand command, CommandBehavior behavior, Func transform) - { + internal static IEnumerable IterateReaderInternal(IDbCommand command, CommandBehavior behavior, Func transform) + { return command is null ? throw new ArgumentNullException(nameof(command)) : transform is null @@ -444,111 +492,123 @@ internal static IEnumerable IterateReaderInternal(IDbCommand command, Comm static IEnumerable IterateReaderInternalCore(IDbCommand command, CommandBehavior behavior, Func transform) { - var state = command.Connection.EnsureOpen(); + ConnectionState state = command.EnsureOpen(); if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var reader = command.ExecuteReader(behavior); + using IDataReader reader = command.ExecuteReader(behavior); while (reader.Read()) yield return transform(reader); } } - /// - /// Iterates an on a command while the predicate returns true. - /// - /// The to generate a reader from. - /// The handler function that processes each and decides if iteration should continue. - /// The behavior to use with the data reader. - public static void IterateReaderWhile(this IDbCommand command, - Func predicate, - CommandBehavior behavior = CommandBehavior.Default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (predicate is null) throw new ArgumentNullException(nameof(predicate)); - Contract.EndContractBlock(); - - var state = command.Connection.EnsureOpen(); - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var reader = command.ExecuteReader(behavior); - reader.IterateWhile(predicate); - } - - /// - /// Asynchronously iterates all records from an . - /// - /// The to generate a reader from. - /// The handler function for each . - /// The behavior to use with the data reader. - /// If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. - /// The cancellation token. - public static async ValueTask ForEachAsync(this DbCommand command, - Action handler, - CommandBehavior behavior, - bool useReadAsync = true, - CancellationToken cancellationToken = default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (handler is null) throw new ArgumentNullException(nameof(handler)); - Contract.EndContractBlock(); - - cancellationToken.ThrowIfCancellationRequested(); - - var state = await command.Connection.EnsureOpenAsync(cancellationToken).ConfigureAwait(true); - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var reader = await command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(true); - await reader.ForEachAsync(handler, useReadAsync, cancellationToken).ConfigureAwait(false); - } - - /// - public static ValueTask ForEachAsync(this DbCommand command, - Action handler, - bool useReadAsync = true, - CancellationToken cancellationToken = default) - => ForEachAsync(command, handler, CommandBehavior.Default, useReadAsync, cancellationToken); + /// + /// Iterates an on a command while the predicate returns true. + /// + /// The to generate a reader from. + /// The handler function that processes each and decides if iteration should continue. + /// The behavior to use with the data reader. + public static void IterateReaderWhile(this IDbCommand command, + Func predicate, + CommandBehavior behavior = CommandBehavior.Default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (predicate is null) throw new ArgumentNullException(nameof(predicate)); + Contract.EndContractBlock(); + + ConnectionState state = command.EnsureOpen(); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; + using IDataReader reader = command.ExecuteReader(behavior); + reader.IterateWhile(predicate); + } + + /// + /// Asynchronously iterates all records from an . + /// + /// The to generate a reader from. + /// The handler function for each . + /// The behavior to use with the data reader. + /// If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. + /// The cancellation token. + public static async ValueTask ForEachAsync(this DbCommand command, + Action handler, + CommandBehavior behavior, + bool useReadAsync = true, + CancellationToken cancellationToken = default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (handler is null) throw new ArgumentNullException(nameof(handler)); + Contract.EndContractBlock(); + + cancellationToken.ThrowIfCancellationRequested(); + + ConnectionState state = await command.EnsureOpenAsync(cancellationToken).ConfigureAwait(false); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; +#if NETSTANDARD2_0 +#else + await +#endif + using DbDataReader reader = await command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(false); + await reader.ForEachAsync(handler, useReadAsync, cancellationToken).ConfigureAwait(false); + } + + /// + public static ValueTask ForEachAsync(this DbCommand command, + Action handler, + bool useReadAsync = true, + CancellationToken cancellationToken = default) + => ForEachAsync(command, handler, CommandBehavior.Default, useReadAsync, cancellationToken); /// public static async ValueTask ForEachAsync(this DbCommand command, - Func handler, CommandBehavior behavior, - bool useReadAsync = true, - CancellationToken cancellationToken = default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (handler is null) throw new ArgumentNullException(nameof(handler)); - Contract.EndContractBlock(); - - cancellationToken.ThrowIfCancellationRequested(); - - var state = await command.Connection.EnsureOpenAsync(cancellationToken).ConfigureAwait(true); - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var reader = await command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(true); - await reader.ForEachAsync(handler, useReadAsync, cancellationToken).ConfigureAwait(false); - } + Func handler, CommandBehavior behavior, + bool useReadAsync = true, + CancellationToken cancellationToken = default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (handler is null) throw new ArgumentNullException(nameof(handler)); + Contract.EndContractBlock(); + + cancellationToken.ThrowIfCancellationRequested(); + + ConnectionState state = await command.EnsureOpenAsync(cancellationToken).ConfigureAwait(false); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; +#if NETSTANDARD2_0 +#else + await +#endif + using DbDataReader reader = await command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(false); + await reader.ForEachAsync(handler, useReadAsync, cancellationToken).ConfigureAwait(false); + } /// public static ValueTask ForEachAsync(this DbCommand command, - Func handler, - bool useReadAsync = true, - CancellationToken cancellationToken = default) - => ForEachAsync(command, handler, CommandBehavior.Default, useReadAsync, cancellationToken); - - /// - /// Asynchronously iterates an on a command while the predicate returns true. - /// - /// The to generate a reader from. - /// The handler function that processes each and decides if iteration should continue. - /// The behavior to use with the data reader. - /// If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. - /// The cancellation token. - public static async ValueTask IterateReaderWhileAsync(this DbCommand command, Func> predicate, CommandBehavior behavior = CommandBehavior.Default, bool useReadAsync = true, CancellationToken cancellationToken = default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (predicate is null) throw new ArgumentNullException(nameof(predicate)); - Contract.EndContractBlock(); - - var state = await command.Connection.EnsureOpenAsync(cancellationToken).ConfigureAwait(true); - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var reader = await command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(true); - await reader.IterateWhileAsync(predicate, useReadAsync, cancellationToken).ConfigureAwait(false); - } + Func handler, + bool useReadAsync = true, + CancellationToken cancellationToken = default) + => ForEachAsync(command, handler, CommandBehavior.Default, useReadAsync, cancellationToken); + + /// + /// Asynchronously iterates an on a command while the predicate returns true. + /// + /// The to generate a reader from. + /// The handler function that processes each and decides if iteration should continue. + /// The behavior to use with the data reader. + /// If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. + /// The cancellation token. + public static async ValueTask IterateReaderWhileAsync(this DbCommand command, Func> predicate, CommandBehavior behavior = CommandBehavior.Default, bool useReadAsync = true, CancellationToken cancellationToken = default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (predicate is null) throw new ArgumentNullException(nameof(predicate)); + Contract.EndContractBlock(); + + ConnectionState state = await command.EnsureOpenAsync(cancellationToken).ConfigureAwait(false); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; +#if NETSTANDARD2_0 +#else + await +#endif + using DbDataReader reader = await command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(false); + await reader.IterateWhileAsync(predicate, useReadAsync, cancellationToken).ConfigureAwait(false); + } /// /// Iterates an and returns the first result through a transform function. @@ -560,159 +620,159 @@ public static async ValueTask IterateReaderWhileAsync(this DbCommand command, Fu /// The behavior to use with the data reader. /// The value from the transform. public static T First(this IDbCommand command, Func transform, CommandBehavior behavior = CommandBehavior.Default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - var state = command.Connection.EnsureOpen(); - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var reader = command.ExecuteReader(behavior | CommandBehavior.SingleRow); - return reader.Select(transform).First(); - } + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + ConnectionState state = command.EnsureOpen(); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; + using IDataReader reader = command.ExecuteReader(behavior | CommandBehavior.SingleRow); + return reader.Select(transform).First(); + } /// Returns default(T) if thre are no results. /// - public static T FirstOrDefault(this IDbCommand command, Func transform, CommandBehavior behavior = CommandBehavior.Default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - var state = command.Connection.EnsureOpen(); - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var reader = command.ExecuteReader(behavior | CommandBehavior.SingleRow); - return reader.Select(transform).FirstOrDefault(); - } + public static T? FirstOrDefault(this IDbCommand command, Func transform, CommandBehavior behavior = CommandBehavior.Default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + ConnectionState state = command.EnsureOpen(); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; + using IDataReader reader = command.ExecuteReader(behavior | CommandBehavior.SingleRow); + return reader.Select(transform).FirstOrDefault(); + } /// Throws an if there is anything other than a single result. /// public static T Single(this IDbCommand command, Func transform, CommandBehavior behavior = CommandBehavior.Default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - var state = command.Connection.EnsureOpen(); - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var reader = command.ExecuteReader(behavior); - return reader.Select(transform).Single(); - } + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + ConnectionState state = command.EnsureOpen(); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; + using IDataReader reader = command.ExecuteReader(behavior); + return reader.Select(transform).Single(); + } /// /// Returns default(T) if thre are no results. /// Throws an if there is more than one result. /// /// - public static T SingleOrDefault(this IDbCommand command, Func transform, CommandBehavior behavior = CommandBehavior.Default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - var state = command.Connection.EnsureOpen(); - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var reader = command.ExecuteReader(behavior); - return reader.Select(transform).SingleOrDefault(); - } - - /// - /// Iterates an and returns the first number of results defined by the count. - /// - /// The return type of the transform function. - /// The to generate a reader from. - /// The maximum number of records to return. - /// The transform function to process each . - /// The behavior to use with the data reader. - /// The results from the transform limited by the take count. - public static List Take(this IDbCommand command, int count, Func transform, CommandBehavior behavior = CommandBehavior.Default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - var state = command.Connection.EnsureOpen(); - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var reader = command.ExecuteReader(behavior); - return reader.Select(transform).Take(count).ToList(); - } - - /// - /// Iterates an and skips the first number of results defined by the count. - /// - /// The return type of the transform function. - /// The to generate a reader from. - /// The number of records to skip. - /// The transform function to process each . - /// The behavior to use with the data reader. - /// The results from the transform after the skip count. - public static List Skip(this IDbCommand command, int count, Func transform, CommandBehavior behavior = CommandBehavior.Default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - var state = command.Connection.EnsureOpen(); - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var reader = command.ExecuteReader(behavior); - while (0 < count--) reader.Read(); - return reader.Select(transform).ToList(); - } - - /// - /// Iterates an and skips by the skip parameter returns the maximum remaining defined by the take parameter. - /// - /// The return type of the transform function. - /// The to generate a reader from. - /// The number of entries to skip before starting to take results. - /// The maximum number of records to return. - /// The transform function to process each . - /// The behavior to use with the data reader. - /// The results from the skip, transform and take operation. - public static List SkipThenTake(this IDbCommand command, int skip, int take, Func transform, CommandBehavior behavior = CommandBehavior.Default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - var state = command.Connection.EnsureOpen(); - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var reader = command.ExecuteReader(behavior); - while (0 < skip--) reader.Read(); - return reader.Select(transform).Take(take).ToList(); - } + public static T? SingleOrDefault(this IDbCommand command, Func transform, CommandBehavior behavior = CommandBehavior.Default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + ConnectionState state = command.EnsureOpen(); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; + using IDataReader reader = command.ExecuteReader(behavior); + return reader.Select(transform).SingleOrDefault(); + } + + /// + /// Iterates an and returns the first number of results defined by the count. + /// + /// The return type of the transform function. + /// The to generate a reader from. + /// The maximum number of records to return. + /// The transform function to process each . + /// The behavior to use with the data reader. + /// The results from the transform limited by the take count. + public static List Take(this IDbCommand command, int count, Func transform, CommandBehavior behavior = CommandBehavior.Default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + ConnectionState state = command.EnsureOpen(); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; + using IDataReader reader = command.ExecuteReader(behavior); + return reader.Select(transform).Take(count).ToList(); + } + + /// + /// Iterates an and skips the first number of results defined by the count. + /// + /// The return type of the transform function. + /// The to generate a reader from. + /// The number of records to skip. + /// The transform function to process each . + /// The behavior to use with the data reader. + /// The results from the transform after the skip count. + public static List Skip(this IDbCommand command, int count, Func transform, CommandBehavior behavior = CommandBehavior.Default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + ConnectionState state = command.EnsureOpen(); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; + using IDataReader reader = command.ExecuteReader(behavior); + while (0 < count--) reader.Read(); + return reader.Select(transform).ToList(); + } + + /// + /// Iterates an and skips by the skip parameter returns the maximum remaining defined by the take parameter. + /// + /// The return type of the transform function. + /// The to generate a reader from. + /// The number of entries to skip before starting to take results. + /// The maximum number of records to return. + /// The transform function to process each . + /// The behavior to use with the data reader. + /// The results from the skip, transform and take operation. + public static List SkipThenTake(this IDbCommand command, int skip, int take, Func transform, CommandBehavior behavior = CommandBehavior.Default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + ConnectionState state = command.EnsureOpen(); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; + using IDataReader reader = command.ExecuteReader(behavior); + while (0 < skip--) reader.Read(); + return reader.Select(transform).Take(take).ToList(); + } /// values are converted to null. /// public static IEnumerable FirstOrdinalResults(this IDbCommand command, CommandBehavior behavior = CommandBehavior.SequentialAccess) - { - var results = new Queue(IterateReaderInternal(command, behavior | CommandBehavior.SingleResult, r => r.GetValue(0))); - return results.DequeueEach().DBNullToNull(); - } + { + var results = new Queue(IterateReaderInternal(command, behavior | CommandBehavior.SingleResult, r => r.GetValue(0))); + return results.DequeueEach().DBNullToNull(); + } /// public static IEnumerable FirstOrdinalResults(this IDbCommand command, CommandBehavior behavior = CommandBehavior.SequentialAccess) - => command is DbCommand dbc - ? dbc.FirstOrdinalResults() - : command.FirstOrdinalResults(behavior | CommandBehavior.SingleResult).Cast(); + => command is DbCommand dbc + ? dbc.FirstOrdinalResults() + : command.FirstOrdinalResults(behavior | CommandBehavior.SingleResult).Cast(); /// The expected type of the first ordinal. /// public static IEnumerable FirstOrdinalResults(this DbCommand command, CommandBehavior behavior = CommandBehavior.SequentialAccess) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - Contract.EndContractBlock(); - - var state = command.Connection.EnsureOpen(); - if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var reader = command.ExecuteReader(behavior | CommandBehavior.SingleResult); - return reader.FirstOrdinalResults(); - } + { + if (command is null) throw new ArgumentNullException(nameof(command)); + Contract.EndContractBlock(); + + ConnectionState state = command.EnsureOpen(); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; + using DbDataReader reader = command.ExecuteReader(behavior | CommandBehavior.SingleResult); + return reader.FirstOrdinalResults(); + } /// values are converted to null. /// public static ValueTask> FirstOrdinalResultsAsync(this DbCommand command, CommandBehavior behavior = CommandBehavior.SequentialAccess, bool useReadAsync = true, CancellationToken cancellationToken = default) - => command.ExecuteReaderAsync(reader => reader.FirstOrdinalResultsAsync(useReadAsync, cancellationToken), behavior | CommandBehavior.SingleResult, cancellationToken); + => command.ExecuteReaderAsync(reader => reader.FirstOrdinalResultsAsync(useReadAsync, cancellationToken), behavior | CommandBehavior.SingleResult, cancellationToken); /// /// Reads the first column from every record. @@ -725,5 +785,5 @@ public static IEnumerable FirstOrdinalResults(this DbCommand command, Co /// The cancellation token. /// The enumerable of casted values. public static ValueTask> FirstOrdinalResultsAsync(this DbCommand command, CommandBehavior behavior = CommandBehavior.SequentialAccess, bool useReadAsync = true, CancellationToken cancellationToken = default) - => command.ExecuteReaderAsync(reader => reader.FirstOrdinalResultsAsync(useReadAsync, cancellationToken), behavior | CommandBehavior.SingleResult, cancellationToken); + => command.ExecuteReaderAsync(reader => reader.FirstOrdinalResultsAsync(useReadAsync, cancellationToken), behavior | CommandBehavior.SingleResult, cancellationToken); } diff --git a/Source/Core/Extensions/Connection.CreateCommand.cs b/Source/Core/Extensions/Connection.CreateCommand.cs index 919066a..17fc79b 100644 --- a/Source/Core/Extensions/Connection.CreateCommand.cs +++ b/Source/Core/Extensions/Connection.CreateCommand.cs @@ -1,13 +1,8 @@ -using System; -using System.Data; -using System.Data.Common; -using System.Diagnostics.Contracts; - -namespace Open.Database.Extensions; +namespace Open.Database.Extensions; public static partial class ConnectionExtensions { - const string EmptyOrWhiteSpace = "Command is empty or whitespace."; + const string EmptyOrWhiteSpace = "Command is empty or whitespace."; /// /// Shortcut for creating an from any . @@ -17,69 +12,69 @@ public static partial class ConnectionExtensions /// The command text or stored procedure name to use. /// The number of seconds to wait before the command times out. public static IDbCommand CreateCommand(this IDbConnection connection, - CommandType type, string commandText, int secondsTimeout = CommandTimeout.DEFAULT_SECONDS) - { - if (connection is null) throw new ArgumentNullException(nameof(connection)); - if (commandText is null) throw new ArgumentNullException(nameof(commandText)); - if (string.IsNullOrWhiteSpace(commandText)) throw new ArgumentException(EmptyOrWhiteSpace, nameof(commandText)); - Contract.EndContractBlock(); + CommandType type, string commandText, int secondsTimeout = CommandTimeout.DEFAULT_SECONDS) + { + if (connection is null) throw new ArgumentNullException(nameof(connection)); + if (commandText is null) throw new ArgumentNullException(nameof(commandText)); + if (string.IsNullOrWhiteSpace(commandText)) throw new ArgumentException(EmptyOrWhiteSpace, nameof(commandText)); + Contract.EndContractBlock(); - var command = connection.CreateCommand(); - command.CommandType = type; - command.CommandText = commandText; - command.CommandTimeout = secondsTimeout; - return command; - } + IDbCommand command = connection.CreateCommand(); + command.CommandType = type; + command.CommandText = commandText; + command.CommandTimeout = secondsTimeout; + return command; + } /// /// Shortcut for creating a text from any . /// /// public static IDbCommand CreateTextCommand(this IDbConnection connection, - string commandText, int secondsTimeout = CommandTimeout.DEFAULT_SECONDS) - => connection.CreateCommand(CommandType.Text, commandText, secondsTimeout); + string commandText, int secondsTimeout = CommandTimeout.DEFAULT_SECONDS) + => connection.CreateCommand(CommandType.Text, commandText, secondsTimeout); /// /// Shortcut for creating a stored procedure from any . /// /// public static IDbCommand CreateStoredProcedureCommand(this IDbConnection connection, - string commandText, int secondsTimeout = CommandTimeout.DEFAULT_SECONDS) - => connection.CreateCommand(CommandType.StoredProcedure, commandText, secondsTimeout); + string commandText, int secondsTimeout = CommandTimeout.DEFAULT_SECONDS) + => connection.CreateCommand(CommandType.StoredProcedure, commandText, secondsTimeout); /// /// Shortcut for creating an from any . /// /// public static DbCommand CreateCommand(this DbConnection connection, - CommandType type, string commandText, int secondsTimeout = CommandTimeout.DEFAULT_SECONDS) - { - if (connection is null) throw new ArgumentNullException(nameof(connection)); - if (commandText is null) throw new ArgumentNullException(nameof(commandText)); - if (string.IsNullOrWhiteSpace(commandText)) throw new ArgumentException(EmptyOrWhiteSpace, nameof(commandText)); - Contract.EndContractBlock(); + CommandType type, string commandText, int secondsTimeout = CommandTimeout.DEFAULT_SECONDS) + { + if (connection is null) throw new ArgumentNullException(nameof(connection)); + if (commandText is null) throw new ArgumentNullException(nameof(commandText)); + if (string.IsNullOrWhiteSpace(commandText)) throw new ArgumentException(EmptyOrWhiteSpace, nameof(commandText)); + Contract.EndContractBlock(); - var command = connection.CreateCommand(); - command.CommandType = type; - command.CommandText = commandText; - command.CommandTimeout = secondsTimeout; + DbCommand command = connection.CreateCommand(); + command.CommandType = type; + command.CommandText = commandText; + command.CommandTimeout = secondsTimeout; - return command; - } + return command; + } /// /// Shortcut for creating a text from any . /// /// public static DbCommand CreateTextCommand(this DbConnection connection, - string commandText, int secondsTimeout = CommandTimeout.DEFAULT_SECONDS) - => connection.CreateCommand(CommandType.Text, commandText, secondsTimeout); + string commandText, int secondsTimeout = CommandTimeout.DEFAULT_SECONDS) + => connection.CreateCommand(CommandType.Text, commandText, secondsTimeout); /// /// Shortcut for creating a stored procedure from any .s /// /// public static DbCommand CreateStoredProcedureCommand(this DbConnection connection, - string procedureName, int secondsTimeout = CommandTimeout.DEFAULT_SECONDS) - => connection.CreateCommand(CommandType.StoredProcedure, procedureName, secondsTimeout); + string procedureName, int secondsTimeout = CommandTimeout.DEFAULT_SECONDS) + => connection.CreateCommand(CommandType.StoredProcedure, procedureName, secondsTimeout); } diff --git a/Source/Core/Extensions/Connection.EnsureOpen.cs b/Source/Core/Extensions/Connection.EnsureOpen.cs index 061830e..1812d74 100644 --- a/Source/Core/Extensions/Connection.EnsureOpen.cs +++ b/Source/Core/Extensions/Connection.EnsureOpen.cs @@ -1,11 +1,4 @@ -using System; -using System.Data; -using System.Data.Common; -using System.Diagnostics.Contracts; -using System.Threading; -using System.Threading.Tasks; - -namespace Open.Database.Extensions; +namespace Open.Database.Extensions; /// /// Core non-DB-specific extensions for database connections. @@ -15,72 +8,95 @@ public static partial class ConnectionExtensions /// The prior connection state. /// public static ConnectionState EnsureOpen(this IDbConnection connection) - { - if (connection is null) throw new ArgumentNullException(nameof(connection)); - Contract.EndContractBlock(); - - var state = connection.State; - - if (state.HasFlag(ConnectionState.Broken)) - connection.Close(); - - if (!connection.State.HasFlag(ConnectionState.Open)) - connection.Open(); - - return state; - } - - /// - /// If the connection isn't open, opens the connection.
- /// If the connection is in neither open or close, first closes the connection, then opens it. - ///
- /// The connection to transact with. - /// If true (default) will retain the context after opening. - /// An cancellation token to cancel opening. - /// A task containing the prior connection state. - public static async ValueTask EnsureOpenAsync( + { + if (connection is null) throw new ArgumentNullException(nameof(connection)); + Contract.EndContractBlock(); + + ConnectionState state = connection.State; + + if (state.HasFlag(ConnectionState.Broken)) + connection.Close(); + + if (!connection.State.HasFlag(ConnectionState.Open)) + connection.Open(); + + return state; + } + + /// + /// If the connection isn't open, opens the connection.
+ /// If the connection is in neither open or close, first closes the connection, then opens it. + ///
+ /// The connection to transact with. + /// If true (default) will retain the context after opening. + /// An cancellation token to cancel opening. + /// A task containing the prior connection state. + public static async ValueTask EnsureOpenAsync( this DbConnection connection, bool configureAwait = true, CancellationToken cancellationToken = default) - { - if (connection is null) throw new ArgumentNullException(nameof(connection)); - Contract.EndContractBlock(); + { + if (connection is null) throw new ArgumentNullException(nameof(connection)); + Contract.EndContractBlock(); - cancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); - var state = connection.State; - if (state.HasFlag(ConnectionState.Broken)) - connection.Close(); + ConnectionState state = connection.State; + if (state.HasFlag(ConnectionState.Broken)) + connection.Close(); - if (connection.State.HasFlag(ConnectionState.Open)) - return state; + if (connection.State.HasFlag(ConnectionState.Open)) + return state; - await connection.OpenAsync(cancellationToken).ConfigureAwait(configureAwait); + await connection.OpenAsync(cancellationToken).ConfigureAwait(configureAwait); - if (cancellationToken.IsCancellationRequested && !state.HasFlag(ConnectionState.Closed)) - { - connection.Close(); // Fake finally... - cancellationToken.ThrowIfCancellationRequested(); - } + if (cancellationToken.IsCancellationRequested && !state.HasFlag(ConnectionState.Closed)) + { + connection.Close(); // Fake finally... + cancellationToken.ThrowIfCancellationRequested(); + } - return state; - } + return state; + } /// public static ValueTask EnsureOpenAsync( this DbConnection connection, CancellationToken cancellationToken) - => connection.EnsureOpenAsync(true, cancellationToken); + => connection.EnsureOpenAsync(true, cancellationToken); /// - internal static async ValueTask EnsureOpenAsync( + public static async ValueTask EnsureOpenAsync( + this IDbConnection connection, + bool configureAwait = true, + CancellationToken cancellationToken = default) + { + if (connection is DbConnection c) + return await c.EnsureOpenAsync(configureAwait, cancellationToken).ConfigureAwait(configureAwait); + + cancellationToken.ThrowIfCancellationRequested(); + return connection.EnsureOpen(); + } + + /// + public static ValueTask EnsureOpenAsync( this IDbConnection connection, CancellationToken cancellationToken) - { - if (connection is DbConnection c) - return await c.EnsureOpenAsync(true, cancellationToken).ConfigureAwait(true); + => EnsureOpenAsync(connection, true, cancellationToken); + + /// + /// If the derives from , this will call ; + /// otherwise it will call . + /// + /// + public static async ValueTask OpenAsync( + this IDbConnection connection, + bool configureAwait = true, + CancellationToken cancellationToken = default) + { + if (connection is DbConnection c) + await c.OpenAsync(cancellationToken).ConfigureAwait(configureAwait); - cancellationToken.ThrowIfCancellationRequested(); - return connection.EnsureOpen(); - } + connection.Open(); + } } diff --git a/Source/Core/Extensions/ConnectionProvider.Command.cs b/Source/Core/Extensions/ConnectionProvider.Command.cs index d062ec0..fefb960 100644 --- a/Source/Core/Extensions/ConnectionProvider.Command.cs +++ b/Source/Core/Extensions/ConnectionProvider.Command.cs @@ -1,24 +1,20 @@ -using System; -using System.Data; -using System.Data.Common; - -namespace Open.Database.Extensions; +namespace Open.Database.Extensions; /// /// Core non-DB-specific extensions for acquiring and operating on different connection factories. /// public static partial class ConnectionExtensions { - /// - /// Creates an for subsequent configuration and execution. - /// - /// The connection to execute the command on. - /// The command text or stored procedure name to use. - /// The command type. The default is . - public static ExpressiveCommand Command( - this IDbConnection connection, - string command, CommandType type = CommandType.Text) - => new(connection, type, command); + /// + /// Creates an for subsequent configuration and execution. + /// + /// The connection to execute the command on. + /// The command text or stored procedure name to use. + /// The command type. The default is . + public static ExpressiveCommand Command( + this IDbConnection connection, + string command, CommandType type = CommandType.Text) + => new(connection, type, command); /// /// Creates an for subsequent configuration and execution. @@ -27,210 +23,210 @@ public static ExpressiveCommand Command( /// The command text or stored procedure name to use. /// The command type. The default is . public static ExpressiveCommand Command( - this IDbTransaction transaction, - string command, CommandType type = CommandType.Text) - => new(transaction, type, command); + this IDbTransaction transaction, + string command, CommandType type = CommandType.Text) + => new(transaction, type, command); - /// - /// Creates an for subsequent configuration and execution. - /// + /// + /// Creates an for subsequent configuration and execution. + /// /// - public static ExpressiveDbCommand Command( - this DbConnection connection, - string command, CommandType type = CommandType.Text) - => new(connection, type, command); - - /// - /// Creates an for subsequent configuration and execution. - /// - /// The transaction to execute the command on. - /// The command text or stored procedure name to use. - /// The command type. Default = CommandType.Text. - public static ExpressiveDbCommand Command( - this DbTransaction transaction, - string command, CommandType type = CommandType.Text) - => new(transaction, type, command); - - /// - /// Creates an with command type set to for subsequent configuration and execution. - /// - /// The connection to execute the command on. - /// The stored procedure name to use. - public static ExpressiveCommand StoredProcedure( - this IDbConnection connection, - string procedureName) - => new(connection, CommandType.StoredProcedure, procedureName); - - /// - /// Creates an with command type set to for subsequent configuration and execution. - /// - /// The transaction to execute the command on. - /// The stored procedure name to use. - public static ExpressiveCommand StoredProcedure( - this IDbTransaction transaction, - string procedureName) - => new(transaction, CommandType.StoredProcedure, procedureName); - - /// - /// Creates an with command type set to for subsequent configuration and execution. - /// - /// - public static ExpressiveDbCommand StoredProcedure( - this DbConnection connection, - string procedureName) - => new(connection, CommandType.StoredProcedure, procedureName); + public static ExpressiveDbCommand Command( + this DbConnection connection, + string command, CommandType type = CommandType.Text) + => new(connection, type, command); + + /// + /// Creates an for subsequent configuration and execution. + /// + /// The transaction to execute the command on. + /// The command text or stored procedure name to use. + /// The command type. Default = CommandType.Text. + public static ExpressiveDbCommand Command( + this DbTransaction transaction, + string command, CommandType type = CommandType.Text) + => new(transaction, type, command); + + /// + /// Creates an with command type set to for subsequent configuration and execution. + /// + /// The connection to execute the command on. + /// The stored procedure name to use. + public static ExpressiveCommand StoredProcedure( + this IDbConnection connection, + string procedureName) + => new(connection, CommandType.StoredProcedure, procedureName); + + /// + /// Creates an with command type set to for subsequent configuration and execution. + /// + /// The transaction to execute the command on. + /// The stored procedure name to use. + public static ExpressiveCommand StoredProcedure( + this IDbTransaction transaction, + string procedureName) + => new(transaction, CommandType.StoredProcedure, procedureName); + + /// + /// Creates an with command type set to for subsequent configuration and execution. + /// + /// + public static ExpressiveDbCommand StoredProcedure( + this DbConnection connection, + string procedureName) + => new(connection, CommandType.StoredProcedure, procedureName); /// /// Creates an with command type set to for subsequent configuration and execution. /// /// public static ExpressiveDbCommand StoredProcedure( - this DbTransaction transaction, - string procedureName) - => new(transaction, CommandType.StoredProcedure, procedureName); - - /// - /// Creates an for subsequent configuration and execution. - /// - /// The connection factory to generate connections and subsequently commands from. - /// The command text or stored procedure name to use. - /// The command type. Default = CommandType.Text. - public static ExpressiveCommand Command( - this IDbConnectionFactory connectionSource, - string command, - CommandType type = CommandType.Text) - => new(connectionSource, type, command); - - /// - /// Creates an with command type set to for subsequent configuration and execution. - /// - /// The connection factory to generate connections and subsequently commands from. - /// The stored procedure name to use. - public static ExpressiveCommand StoredProcedure( - this IDbConnectionFactory connectionSource, - string procedureName) - => new(connectionSource, CommandType.StoredProcedure, procedureName); - - /// - /// Creates an for subsequent configuration and execution. - /// - /// The connection pool to take connections from. - /// The command text or stored procedure name to use. - /// The command type. Default = CommandType.Text. - public static ExpressiveCommand Command( - this IDbConnectionPool connectionSource, - string command, - CommandType type = CommandType.Text) - => new(connectionSource, type, command); - - /// - /// Creates an with command type set to for subsequent configuration and execution. - /// - /// The connection pool to take connections from. - /// The stored procedure name to use. - public static ExpressiveCommand StoredProcedure( - this IDbConnectionPool connectionSource, - string procedureName) - => new(connectionSource, CommandType.StoredProcedure, procedureName); - - /// - /// Creates an for subsequent configuration and execution. - /// - /// The connection type. - /// The connection factory to generate connections and subsequently commands from. - /// The command text or stored procedure name to use. - /// The command type. Default = CommandType.Text. - public static ExpressiveDbCommand Command( - this IDbConnectionFactory connectionSource, - string command, - CommandType type = CommandType.Text) - where TConnection : DbConnection - => new(connectionSource, type, command); - - /// - /// Creates an with command type set to for subsequent configuration and execution. - /// - /// The connection type. - /// The connection factory to generate connections and subsequently commands from. - /// The stored procedure name to use. - /// The resultant ExpressiveDbCommand. - public static ExpressiveDbCommand StoredProcedure( - this IDbConnectionFactory connectionSource, - string procedureName) - where TConnection : DbConnection - => new(connectionSource, CommandType.StoredProcedure, procedureName); - - /// - /// Creates an for subsequent configuration and execution. - /// - /// The connection type. - /// The connection pool to take connections from. - /// The command text or stored procedure name to use. - /// The command type. Default = CommandType.Text. - public static ExpressiveDbCommand Command( - this IDbConnectionPool connectionSource, - string command, - CommandType type = CommandType.Text) - where TConnection : DbConnection - => new(connectionSource, type, command); - - /// - /// Creates an with command type set to for subsequent configuration and execution. - /// - /// The connection type. - /// The connection pool to take connections from. - /// The stored procedure name to use. - public static ExpressiveDbCommand StoredProcedure( - this IDbConnectionPool connectionSource, - string procedureName) - where TConnection : DbConnection - => new(connectionSource, CommandType.StoredProcedure, procedureName); - - /// - /// Creates an for subsequent configuration and execution. - /// - /// The connection factory to generate a commands from. - /// The command text or stored procedure name to use. - /// The command type. Default = CommandType.Text. - public static ExpressiveCommand Command( - this Func connectionSource, - string command, - CommandType type = CommandType.Text) - => Command(DbConnectionFactory.Create(connectionSource), command, type); - - /// - /// Creates an with command type set to for subsequent configuration and execution. - /// - /// The connection factory to generate a commands from. - /// The stored procedure name to use. - public static ExpressiveCommand StoredProcedure( - this Func connectionSource, - string procedureName) - => StoredProcedure(DbConnectionFactory.Create(connectionSource), procedureName); - - /// - /// Creates an for subsequent configuration and execution. - /// - /// The connection type. - /// The connection factory to generate a commands from. - /// The command text or stored procedure name to use. - /// The command type. Default = CommandType.Text. - public static ExpressiveDbCommand Command( - this Func connectionSource, - string command, - CommandType type = CommandType.Text) - where TConnection : DbConnection - => Command(DbConnectionFactory.Create(connectionSource), command, type); - - /// - /// Creates an with command type set to for subsequent configuration and execution. - /// - /// The connection type. - /// The connection factory to generate a commands from. - /// The stored procedure name to use. - public static ExpressiveDbCommand StoredProcedure( - this Func connectionSource, - string procedureName) - where TConnection : DbConnection - => StoredProcedure(DbConnectionFactory.Create(connectionSource), procedureName); + this DbTransaction transaction, + string procedureName) + => new(transaction, CommandType.StoredProcedure, procedureName); + + /// + /// Creates an for subsequent configuration and execution. + /// + /// The connection factory to generate connections and subsequently commands from. + /// The command text or stored procedure name to use. + /// The command type. Default = CommandType.Text. + public static ExpressiveCommand Command( + this IDbConnectionFactory connectionSource, + string command, + CommandType type = CommandType.Text) + => new(connectionSource, type, command); + + /// + /// Creates an with command type set to for subsequent configuration and execution. + /// + /// The connection factory to generate connections and subsequently commands from. + /// The stored procedure name to use. + public static ExpressiveCommand StoredProcedure( + this IDbConnectionFactory connectionSource, + string procedureName) + => new(connectionSource, CommandType.StoredProcedure, procedureName); + + /// + /// Creates an for subsequent configuration and execution. + /// + /// The connection pool to take connections from. + /// The command text or stored procedure name to use. + /// The command type. Default = CommandType.Text. + public static ExpressiveCommand Command( + this IDbConnectionPool connectionSource, + string command, + CommandType type = CommandType.Text) + => new(connectionSource, type, command); + + /// + /// Creates an with command type set to for subsequent configuration and execution. + /// + /// The connection pool to take connections from. + /// The stored procedure name to use. + public static ExpressiveCommand StoredProcedure( + this IDbConnectionPool connectionSource, + string procedureName) + => new(connectionSource, CommandType.StoredProcedure, procedureName); + + /// + /// Creates an for subsequent configuration and execution. + /// + /// The connection type. + /// The connection factory to generate connections and subsequently commands from. + /// The command text or stored procedure name to use. + /// The command type. Default = CommandType.Text. + public static ExpressiveDbCommand Command( + this IDbConnectionFactory connectionSource, + string command, + CommandType type = CommandType.Text) + where TConnection : DbConnection + => new(connectionSource, type, command); + + /// + /// Creates an with command type set to for subsequent configuration and execution. + /// + /// The connection type. + /// The connection factory to generate connections and subsequently commands from. + /// The stored procedure name to use. + /// The resultant ExpressiveDbCommand. + public static ExpressiveDbCommand StoredProcedure( + this IDbConnectionFactory connectionSource, + string procedureName) + where TConnection : DbConnection + => new(connectionSource, CommandType.StoredProcedure, procedureName); + + /// + /// Creates an for subsequent configuration and execution. + /// + /// The connection type. + /// The connection pool to take connections from. + /// The command text or stored procedure name to use. + /// The command type. Default = CommandType.Text. + public static ExpressiveDbCommand Command( + this IDbConnectionPool connectionSource, + string command, + CommandType type = CommandType.Text) + where TConnection : DbConnection + => new(connectionSource, type, command); + + /// + /// Creates an with command type set to for subsequent configuration and execution. + /// + /// The connection type. + /// The connection pool to take connections from. + /// The stored procedure name to use. + public static ExpressiveDbCommand StoredProcedure( + this IDbConnectionPool connectionSource, + string procedureName) + where TConnection : DbConnection + => new(connectionSource, CommandType.StoredProcedure, procedureName); + + /// + /// Creates an for subsequent configuration and execution. + /// + /// The connection factory to generate a commands from. + /// The command text or stored procedure name to use. + /// The command type. Default = CommandType.Text. + public static ExpressiveCommand Command( + this Func connectionSource, + string command, + CommandType type = CommandType.Text) + => Command(connectionSource.AsPool(), command, type); + + /// + /// Creates an with command type set to for subsequent configuration and execution. + /// + /// The connection factory to generate a commands from. + /// The stored procedure name to use. + public static ExpressiveCommand StoredProcedure( + this Func connectionSource, + string procedureName) + => StoredProcedure(DbConnectionFactory.Create(connectionSource), procedureName); + + /// + /// Creates an for subsequent configuration and execution. + /// + /// The connection type. + /// The connection factory to generate a commands from. + /// The command text or stored procedure name to use. + /// The command type. Default = CommandType.Text. + public static ExpressiveDbCommand Command( + this Func connectionSource, + string command, + CommandType type = CommandType.Text) + where TConnection : DbConnection + => Command(connectionSource.AsPool(), command, type); + + /// + /// Creates an with command type set to for subsequent configuration and execution. + /// + /// The connection type. + /// The connection factory to generate a commands from. + /// The stored procedure name to use. + public static ExpressiveDbCommand StoredProcedure( + this Func connectionSource, + string procedureName) + where TConnection : DbConnection + => StoredProcedure(connectionSource.AsPool(), procedureName); } diff --git a/Source/Core/Extensions/ConnectionProvider.Open.cs b/Source/Core/Extensions/ConnectionProvider.Open.cs index 091c3ef..cd2e9a8 100644 --- a/Source/Core/Extensions/ConnectionProvider.Open.cs +++ b/Source/Core/Extensions/ConnectionProvider.Open.cs @@ -1,10 +1,4 @@ -using System; -using System.Data; -using System.Diagnostics.Contracts; -using System.Threading; -using System.Threading.Tasks; - -namespace Open.Database.Extensions; +namespace Open.Database.Extensions; /// /// Core non-DB-specific extensions for acquiring and operating on different connection factories. @@ -15,15 +9,15 @@ public static partial class ConnectionExtensions /// The value from the action. /// public static T Open(this IDbConnectionFactory connectionFactory, Func action) - { - if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); + { + if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); - using var conn = connectionFactory.Create(); - conn.EnsureOpen(); // Use EnsureOpen in case the connection factory implementation has it's own pooling. - return action(conn); - } + using IDbConnection conn = connectionFactory.Create(); + conn.EnsureOpen(); // Use EnsureOpen in case the connection factory implementation has it's own pooling. + return action(conn); + } /// /// Generates a connection. Ensures it's open. Invokes the action.
@@ -33,15 +27,15 @@ public static T Open(this IDbConnectionFactory connectionFactory, FuncThe connection factory to generate connections from. /// The action to execute. public static void Open(this IDbConnectionFactory connectionFactory, Action action) - { - if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); + { + if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); - using var conn = connectionFactory.Create(); - conn.EnsureOpen(); // Use EnsureOpen in case the connection factory implementation has it's own pooling. - action(conn); - } + using IDbConnection conn = connectionFactory.Create(); + conn.EnsureOpen(); // Use EnsureOpen in case the connection factory implementation has it's own pooling. + action(conn); + } /// The connection type. /// The type returned from the action. @@ -50,52 +44,52 @@ public static void Open(this IDbConnectionFactory connectionFactory, ActionThe value from the action. /// public static T Open(this IDbConnectionFactory connectionFactory, Func action) - where TConnection : IDbConnection - { - if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); + where TConnection : IDbConnection + { + if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); - using var conn = connectionFactory.Create(); - conn.EnsureOpen(); // Use EnsureOpen in case the connection factory implementation has it's own pooling. - return action(conn); - } + using TConnection conn = connectionFactory.Create(); + conn.EnsureOpen(); // Use EnsureOpen in case the connection factory implementation has it's own pooling. + return action(conn); + } /// The connection type. /// The connection factory to generate connections from. /// The action to execute. /// public static void Open(this IDbConnectionFactory connectionFactory, Action action) - where TConnection : IDbConnection - { - if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); - - using var conn = connectionFactory.Create(); - conn.EnsureOpen(); // Use EnsureOpen in case the connection factory implementation has it's own pooling. - action(conn); - } - - /// The type returned from the action. - /// The value from the action. + where TConnection : IDbConnection + { + if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); + + using TConnection conn = connectionFactory.Create(); + conn.EnsureOpen(); // Use EnsureOpen in case the connection factory implementation has it's own pooling. + action(conn); + } + + /// The type returned from the action. + /// The value from the action. /// - public static T Open(this IDbConnectionPool connectionPool, Func action) - { - if (connectionPool is null) throw new ArgumentNullException(nameof(connectionPool)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); - - var conn = connectionPool.Take(); - try - { - return action(conn, conn.EnsureOpen()); - } - finally - { - connectionPool.Give(conn); - } - } + public static T Open(this IDbConnectionPool connectionPool, Func action) + { + if (connectionPool is null) throw new ArgumentNullException(nameof(connectionPool)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); + + IDbConnection conn = connectionPool.Take(); + try + { + return action(conn, conn.EnsureOpen()); + } + finally + { + connectionPool.Give(conn); + } + } /// /// Acquires a connection from the pool. Ensures it's open. Invokes the action.
@@ -105,106 +99,106 @@ public static T Open(this IDbConnectionPool connectionPool, FuncThe action to execute. /// public static void Open(this IDbConnectionPool connectionPool, Action action) - { - if (connectionPool is null) throw new ArgumentNullException(nameof(connectionPool)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); - - var conn = connectionPool.Take(); - try - { - action(conn, conn.EnsureOpen()); - } - finally - { - connectionPool.Give(conn); - } - } - - /// The connection type. - /// The type returned from the action. - /// The value from the action. + { + if (connectionPool is null) throw new ArgumentNullException(nameof(connectionPool)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); + + IDbConnection conn = connectionPool.Take(); + try + { + action(conn, conn.EnsureOpen()); + } + finally + { + connectionPool.Give(conn); + } + } + + /// The connection type. + /// The type returned from the action. + /// The value from the action. /// - public static T Open(this IDbConnectionPool connectionPool, Func action) - where TConnection : IDbConnection - { - if (connectionPool is null) throw new ArgumentNullException(nameof(connectionPool)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); - - var conn = connectionPool.Take(); - try - { - return action(conn, conn.EnsureOpen()); - } - finally - { - connectionPool.Give(conn); - } - } + public static T Open(this IDbConnectionPool connectionPool, Func action) + where TConnection : IDbConnection + { + if (connectionPool is null) throw new ArgumentNullException(nameof(connectionPool)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); + + TConnection conn = connectionPool.Take(); + try + { + return action(conn, conn.EnsureOpen()); + } + finally + { + connectionPool.Give(conn); + } + } /// The connection type. /// public static void Open(this IDbConnectionPool connectionPool, Action action) - where TConnection : IDbConnection - { - if (connectionPool is null) throw new ArgumentNullException(nameof(connectionPool)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); - - var conn = connectionPool.Take(); - try - { - action(conn, conn.EnsureOpen()); - } - finally - { - connectionPool.Give(conn); - } - } + where TConnection : IDbConnection + { + if (connectionPool is null) throw new ArgumentNullException(nameof(connectionPool)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); + + TConnection conn = connectionPool.Take(); + try + { + action(conn, conn.EnsureOpen()); + } + finally + { + connectionPool.Give(conn); + } + } /// The value from the action. /// public static T Open(this Func connectionFactory, Func action) - where TConnection : IDbConnection - { - if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); + where TConnection : IDbConnection + { + if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); - using var conn = connectionFactory(); - conn.EnsureOpen(); // Use EnsureOpen in case the connection factory implementation has it's own pooling. - return action(conn); - } + using TConnection conn = connectionFactory(); + conn.EnsureOpen(); // Use EnsureOpen in case the connection factory implementation has it's own pooling. + return action(conn); + } /// public static void Open( this Func connectionFactory, Action action) - where TConnection : IDbConnection - { - if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); + where TConnection : IDbConnection + { + if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); - using var conn = connectionFactory(); - conn.EnsureOpen(); // Use EnsureOpen in case the connection factory implementation has it's own pooling. - action(conn); - } + using TConnection conn = connectionFactory(); + conn.EnsureOpen(); // Use EnsureOpen in case the connection factory implementation has it's own pooling. + action(conn); + } /// The value from the action. /// public static async ValueTask OpenAsync(this IDbConnectionFactory connectionFactory, Func> action, CancellationToken cancellationToken = default) - { - if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); + { + if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); - using var conn = connectionFactory.Create(); - // Use EnsureOpen in case the connection factory implementation has it's own pooling. - await conn.EnsureOpenAsync(cancellationToken).ConfigureAwait(true); - return await action(conn).ConfigureAwait(false); - } + using IDbConnection conn = connectionFactory.Create(); + // Use EnsureOpen in case the connection factory implementation has it's own pooling. + await conn.EnsureOpenAsync(cancellationToken).ConfigureAwait(false); + return await action(conn).ConfigureAwait(false); + } /// /// Generates a connection. Ensures it's open. Invokes the action.
@@ -218,16 +212,16 @@ public static async ValueTask OpenAsync( this IDbConnectionFactory connectionFactory, Func action, CancellationToken cancellationToken = default) - { - if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); + { + if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); - using var conn = connectionFactory.Create(); - // Use EnsureOpen in case the connection factory implementation has it's own pooling. - await conn.EnsureOpenAsync(cancellationToken).ConfigureAwait(true); - await action(conn).ConfigureAwait(false); - } + using IDbConnection conn = connectionFactory.Create(); + // Use EnsureOpen in case the connection factory implementation has it's own pooling. + await conn.EnsureOpenAsync(cancellationToken).ConfigureAwait(false); + await action(conn).ConfigureAwait(false); + } /// The connection type. /// The type returned from the action. @@ -237,55 +231,55 @@ public static async ValueTask OpenAsync( this IDbConnectionFactory connectionFactory, Func> action, CancellationToken cancellationToken = default) - where TConnection : IDbConnection - { - if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); - - using var conn = connectionFactory.Create(); - // Use EnsureOpen in case the connection factory implementation has it's own pooling. - await conn.EnsureOpenAsync(cancellationToken).ConfigureAwait(true); - return await action(conn).ConfigureAwait(false); - } + where TConnection : IDbConnection + { + if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); + + using TConnection conn = connectionFactory.Create(); + // Use EnsureOpen in case the connection factory implementation has it's own pooling. + await conn.EnsureOpenAsync(cancellationToken).ConfigureAwait(false); + return await action(conn).ConfigureAwait(false); + } /// public static async ValueTask OpenAsync( this IDbConnectionFactory connectionFactory, Func action, CancellationToken cancellationToken = default) - where TConnection : IDbConnection - { - if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); - - using var conn = connectionFactory.Create(); - // Use EnsureOpen in case the connection factory implementation has it's own pooling. - await conn.EnsureOpenAsync(cancellationToken).ConfigureAwait(true); - await action(conn).ConfigureAwait(false); - } - - /// The value from the action. + where TConnection : IDbConnection + { + if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); + + using TConnection conn = connectionFactory.Create(); + // Use EnsureOpen in case the connection factory implementation has it's own pooling. + await conn.EnsureOpenAsync(cancellationToken).ConfigureAwait(false); + await action(conn).ConfigureAwait(false); + } + + /// The value from the action. /// - public static async ValueTask OpenAsync(this IDbConnectionPool connectionPool, Func> action, CancellationToken cancellationToken = default) - { - if (connectionPool is null) throw new ArgumentNullException(nameof(connectionPool)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); - - var conn = connectionPool.Take(); - try - { - return await action(conn, - await conn.EnsureOpenAsync(cancellationToken).ConfigureAwait(true)) - .ConfigureAwait(false); - } - finally - { - connectionPool.Give(conn); - } - } + public static async ValueTask OpenAsync(this IDbConnectionPool connectionPool, Func> action, CancellationToken cancellationToken = default) + { + if (connectionPool is null) throw new ArgumentNullException(nameof(connectionPool)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); + + IDbConnection conn = connectionPool.Take(); + try + { + return await action(conn, + await conn.EnsureOpenAsync(cancellationToken).ConfigureAwait(false)) + .ConfigureAwait(false); + } + finally + { + connectionPool.Give(conn); + } + } /// /// Acquires a connection from the pool. Ensures it's open. Invokes the action.
@@ -296,114 +290,114 @@ await conn.EnsureOpenAsync(cancellationToken).ConfigureAwait(true)) /// The cancellation token. /// public static async ValueTask OpenAsync(this IDbConnectionPool connectionPool, Func action, CancellationToken cancellationToken = default) - { - if (connectionPool is null) throw new ArgumentNullException(nameof(connectionPool)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); - - var conn = connectionPool.Take(); - try - { - await action(conn, - await conn.EnsureOpenAsync(cancellationToken).ConfigureAwait(true)) - .ConfigureAwait(false); - } - finally - { - connectionPool.Give(conn); - } - } - - /// The connection type. - /// The type returned from the action. - /// The value from the action. + { + if (connectionPool is null) throw new ArgumentNullException(nameof(connectionPool)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); + + IDbConnection conn = connectionPool.Take(); + try + { + await action(conn, + await conn.EnsureOpenAsync(cancellationToken).ConfigureAwait(false)) + .ConfigureAwait(false); + } + finally + { + connectionPool.Give(conn); + } + } + + /// The connection type. + /// The type returned from the action. + /// The value from the action. /// - public static async ValueTask OpenAsync( + public static async ValueTask OpenAsync( this IDbConnectionPool connectionPool, Func> action, CancellationToken cancellationToken = default) - where TConnection : IDbConnection - { - if (connectionPool is null) throw new ArgumentNullException(nameof(connectionPool)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); - - var conn = connectionPool.Take(); - try - { - return await action(conn, - await conn.EnsureOpenAsync(cancellationToken).ConfigureAwait(true)) - .ConfigureAwait(false); - } - finally - { - connectionPool.Give(conn); - } - } + where TConnection : IDbConnection + { + if (connectionPool is null) throw new ArgumentNullException(nameof(connectionPool)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); + + TConnection conn = connectionPool.Take(); + try + { + return await action(conn, + await conn.EnsureOpenAsync(cancellationToken).ConfigureAwait(false)) + .ConfigureAwait(false); + } + finally + { + connectionPool.Give(conn); + } + } /// The connection type. /// public static async ValueTask OpenAsync(this IDbConnectionPool connectionPool, Func action, CancellationToken cancellationToken = default) - where TConnection : IDbConnection - { - if (connectionPool is null) throw new ArgumentNullException(nameof(connectionPool)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); - - var conn = connectionPool.Take(); - try - { - await action(conn, - await conn.EnsureOpenAsync(cancellationToken).ConfigureAwait(true)) - .ConfigureAwait(false); - } - finally - { - connectionPool.Give(conn); - } - } - - /// - /// Generates a connection. Ensures it's open. Invokes the action. - /// Ensures the connection is disposed of when the action is complete. - /// Useful for single-line operations. - /// - /// The connection type. - /// The type returned from the action. - /// The connection factory to generate connections from. - /// The action to execute. - /// An optional cancellation token. - /// The value from the action. - public static async ValueTask OpenAsync( + where TConnection : IDbConnection + { + if (connectionPool is null) throw new ArgumentNullException(nameof(connectionPool)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); + + TConnection conn = connectionPool.Take(); + try + { + await action(conn, + await conn.EnsureOpenAsync(cancellationToken).ConfigureAwait(false)) + .ConfigureAwait(false); + } + finally + { + connectionPool.Give(conn); + } + } + + /// + /// Generates a connection. Ensures it's open. Invokes the action. + /// Ensures the connection is disposed of when the action is complete. + /// Useful for single-line operations. + /// + /// The connection type. + /// The type returned from the action. + /// The connection factory to generate connections from. + /// The action to execute. + /// An optional cancellation token. + /// The value from the action. + public static async ValueTask OpenAsync( this Func connectionFactory, Func> action, CancellationToken cancellationToken = default) - where TConnection : IDbConnection - { - if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); - - using var conn = connectionFactory(); - // Use EnsureOpen in case the connection factory implementation has it's own pooling. - await conn.EnsureOpenAsync(cancellationToken).ConfigureAwait(true); - return await action(conn).ConfigureAwait(false); - } - - /// - public static async ValueTask OpenAsync( + where TConnection : IDbConnection + { + if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); + + using TConnection conn = connectionFactory(); + // Use EnsureOpen in case the connection factory implementation has it's own pooling. + await conn.EnsureOpenAsync(cancellationToken).ConfigureAwait(false); + return await action(conn).ConfigureAwait(false); + } + + /// + public static async ValueTask OpenAsync( this Func connectionFactory, Func action, CancellationToken cancellationToken = default) - where TConnection : IDbConnection - { - if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); - - using var conn = connectionFactory(); - // Use EnsureOpen in case the connection factory implementation has it's own pooling. - await conn.EnsureOpenAsync(cancellationToken).ConfigureAwait(true); - await action(conn).ConfigureAwait(false); - } + where TConnection : IDbConnection + { + if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); + + using TConnection conn = connectionFactory(); + // Use EnsureOpen in case the connection factory implementation has it's own pooling. + await conn.EnsureOpenAsync(cancellationToken).ConfigureAwait(false); + await action(conn).ConfigureAwait(false); + } } diff --git a/Source/Core/Extensions/ConnectionProvider.Using.cs b/Source/Core/Extensions/ConnectionProvider.Using.cs index f06b479..39bf464 100644 --- a/Source/Core/Extensions/ConnectionProvider.Using.cs +++ b/Source/Core/Extensions/ConnectionProvider.Using.cs @@ -1,9 +1,4 @@ -using System; -using System.Data; -using System.Diagnostics.Contracts; -using System.Threading.Tasks; - -namespace Open.Database.Extensions; +namespace Open.Database.Extensions; /// /// Core non-DB-specific extensions for acquiring and operating on different connection factories. @@ -14,14 +9,14 @@ public static partial class ConnectionExtensions /// The value from the action. /// public static T Using(this IDbConnectionFactory connectionFactory, Func action) - { - if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); + { + if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); - using var conn = connectionFactory.Create(); - return action(conn); - } + using IDbConnection conn = connectionFactory.Create(); + return action(conn); + } /// /// Generates a connection and executes the action within a using statement. @@ -30,62 +25,62 @@ public static T Using(this IDbConnectionFactory connectionFactory, FuncThe connection factory to generate connections from. /// The action to execute. public static void Using(this IDbConnectionFactory connectionFactory, Action action) - { - if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); + { + if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); - using var conn = connectionFactory.Create(); - action(conn); - } + using IDbConnection conn = connectionFactory.Create(); + action(conn); + } /// The connection type. /// The type returned from the action. /// The value from the action. /// public static T Using(this IDbConnectionFactory connectionFactory, Func action) - where TConn : IDbConnection - { - if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); + where TConn : IDbConnection + { + if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); - using var conn = connectionFactory.Create(); - return action(conn); - } + using TConn conn = connectionFactory.Create(); + return action(conn); + } /// The connection type. /// public static void Using(this IDbConnectionFactory connectionFactory, Action action) - where TConn : IDbConnection - { - if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); - - using var conn = connectionFactory.Create(); - action(conn); - } - - /// The type returned from the action. - /// The value from the action. + where TConn : IDbConnection + { + if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); + + using TConn conn = connectionFactory.Create(); + action(conn); + } + + /// The type returned from the action. + /// The value from the action. /// - public static T Using(this IDbConnectionPool connectionPool, Func action) - { - if (connectionPool is null) throw new ArgumentNullException(nameof(connectionPool)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); - - var conn = connectionPool.Take(); - try - { - return action(conn); - } - finally - { - connectionPool.Give(conn); - } - } + public static T Using(this IDbConnectionPool connectionPool, Func action) + { + if (connectionPool is null) throw new ArgumentNullException(nameof(connectionPool)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); + + IDbConnection conn = connectionPool.Take(); + try + { + return action(conn); + } + finally + { + connectionPool.Give(conn); + } + } /// /// Acquires a connection from the pool, returning it after the action is complete. @@ -94,245 +89,245 @@ public static T Using(this IDbConnectionPool connectionPool, FuncThe connection pool to acquire connections from. /// The action to execute. public static void Using(this IDbConnectionPool connectionPool, Action action) - { - if (connectionPool is null) throw new ArgumentNullException(nameof(connectionPool)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); - - var conn = connectionPool.Take(); - try - { - action(conn); - } - finally - { - connectionPool.Give(conn); - } - } + { + if (connectionPool is null) throw new ArgumentNullException(nameof(connectionPool)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); + + IDbConnection conn = connectionPool.Take(); + try + { + action(conn); + } + finally + { + connectionPool.Give(conn); + } + } /// The connection type. /// The type returned from the action. /// The value from the action. /// public static T Using(this IDbConnectionPool connectionPool, Func action) - where TConn : IDbConnection - { - if (connectionPool is null) throw new ArgumentNullException(nameof(connectionPool)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); - - var conn = connectionPool.Take(); - try - { - return action(conn); - } - finally - { - connectionPool.Give(conn); - } - } + where TConn : IDbConnection + { + if (connectionPool is null) throw new ArgumentNullException(nameof(connectionPool)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); + + TConn conn = connectionPool.Take(); + try + { + return action(conn); + } + finally + { + connectionPool.Give(conn); + } + } /// The connection type. /// public static void Using(this IDbConnectionPool connectionPool, Action action) - where TConn : IDbConnection - { - if (connectionPool is null) throw new ArgumentNullException(nameof(connectionPool)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); - - var conn = connectionPool.Take(); - try - { - action(conn); - } - finally - { - connectionPool.Give(conn); - } - } + where TConn : IDbConnection + { + if (connectionPool is null) throw new ArgumentNullException(nameof(connectionPool)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); + + TConn conn = connectionPool.Take(); + try + { + action(conn); + } + finally + { + connectionPool.Give(conn); + } + } /// The connection type. /// The type returned from the action. /// The value from the action. /// public static T Using(this Func connectionFactory, Func action) - where TConn : IDbConnection - { - if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); + where TConn : IDbConnection + { + if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); - using var conn = connectionFactory(); - return action(conn); - } + using TConn conn = connectionFactory(); + return action(conn); + } /// The connection type. /// public static void Using(this Func connectionFactory, Action action) - where TConn : IDbConnection - { - if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); + where TConn : IDbConnection + { + if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); - using var conn = connectionFactory(); - action(conn); - } + using TConn conn = connectionFactory(); + action(conn); + } /// The type returned from the action. /// The value from the action. /// public static async ValueTask UsingAsync(this IDbConnectionFactory connectionFactory, Func> action) - { - if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); + { + if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); - using var conn = connectionFactory.Create(); - return await action(conn).ConfigureAwait(false); - } + using IDbConnection conn = connectionFactory.Create(); + return await action(conn).ConfigureAwait(false); + } /// public static async ValueTask UsingAsync(this IDbConnectionFactory connectionFactory, Func action) - { - if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); + { + if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); - using var conn = connectionFactory.Create(); - await action(conn).ConfigureAwait(false); - } + using IDbConnection conn = connectionFactory.Create(); + await action(conn).ConfigureAwait(false); + } /// public static async ValueTask UsingAsync(this IDbConnectionFactory connectionFactory, Func> action) - where TConn : IDbConnection - { - if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); + where TConn : IDbConnection + { + if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); - using var conn = connectionFactory.Create(); - return await action(conn).ConfigureAwait(false); - } + using TConn conn = connectionFactory.Create(); + return await action(conn).ConfigureAwait(false); + } /// The connection type. /// public static async ValueTask UsingAsync(this IDbConnectionFactory connectionFactory, Func action) - where TConn : IDbConnection - { - if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); + where TConn : IDbConnection + { + if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); - using var conn = connectionFactory.Create(); - await action(conn).ConfigureAwait(false); - } + using TConn conn = connectionFactory.Create(); + await action(conn).ConfigureAwait(false); + } /// The type returned from the action. /// The value from the action. /// public static async ValueTask UsingAsync(this IDbConnectionPool connectionPool, Func> action) - { - if (connectionPool is null) throw new ArgumentNullException(nameof(connectionPool)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); - - var conn = connectionPool.Take(); - try - { - return await action(conn).ConfigureAwait(false); - } - finally - { - connectionPool.Give(conn); - } - } + { + if (connectionPool is null) throw new ArgumentNullException(nameof(connectionPool)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); + + IDbConnection conn = connectionPool.Take(); + try + { + return await action(conn).ConfigureAwait(false); + } + finally + { + connectionPool.Give(conn); + } + } /// public static async ValueTask UsingAsync(this IDbConnectionPool connectionPool, Func action) - { - if (connectionPool is null) throw new ArgumentNullException(nameof(connectionPool)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); - - var conn = connectionPool.Take(); - try - { - await action(conn).ConfigureAwait(false); - } - finally - { - connectionPool.Give(conn); - } - } + { + if (connectionPool is null) throw new ArgumentNullException(nameof(connectionPool)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); + + IDbConnection conn = connectionPool.Take(); + try + { + await action(conn).ConfigureAwait(false); + } + finally + { + connectionPool.Give(conn); + } + } /// The connection type. /// The type returned from the action. /// The value from the action. /// public static async ValueTask UsingAsync(this IDbConnectionPool connectionPool, Func> action) - where TConn : IDbConnection - { - if (connectionPool is null) throw new ArgumentNullException(nameof(connectionPool)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); - - var conn = connectionPool.Take(); - try - { - return await action(conn).ConfigureAwait(false); - } - finally - { - connectionPool.Give(conn); - } - } + where TConn : IDbConnection + { + if (connectionPool is null) throw new ArgumentNullException(nameof(connectionPool)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); + + TConn conn = connectionPool.Take(); + try + { + return await action(conn).ConfigureAwait(false); + } + finally + { + connectionPool.Give(conn); + } + } /// The connection type. /// public static async ValueTask UsingAsync(this IDbConnectionPool connectionPool, Func action) - where TConn : IDbConnection - { - if (connectionPool is null) throw new ArgumentNullException(nameof(connectionPool)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); - - var conn = connectionPool.Take(); - try - { - await action(conn).ConfigureAwait(false); - } - finally - { - connectionPool.Give(conn); - } - } + where TConn : IDbConnection + { + if (connectionPool is null) throw new ArgumentNullException(nameof(connectionPool)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); + + TConn conn = connectionPool.Take(); + try + { + await action(conn).ConfigureAwait(false); + } + finally + { + connectionPool.Give(conn); + } + } /// The connection type. /// The type returned from the action. /// public static async ValueTask UsingAsync(this Func connectionFactory, Func> action) - where TConn : IDbConnection - { - if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); + where TConn : IDbConnection + { + if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); - using var conn = connectionFactory(); - return await action(conn).ConfigureAwait(false); - } + using TConn conn = connectionFactory(); + return await action(conn).ConfigureAwait(false); + } /// The connection type. /// public static async ValueTask UsingAsync(this Func connectionFactory, Func action) - where TConn : IDbConnection - { - if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); - if (action is null) throw new ArgumentNullException(nameof(action)); - Contract.EndContractBlock(); - - using var conn = connectionFactory(); - await action(conn).ConfigureAwait(false); - } + where TConn : IDbConnection + { + if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); + if (action is null) throw new ArgumentNullException(nameof(action)); + Contract.EndContractBlock(); + + using TConn conn = connectionFactory(); + await action(conn).ConfigureAwait(false); + } } diff --git a/Source/Core/Extensions/DataReader.cs b/Source/Core/Extensions/DataReader.cs index 21329f1..5b941e7 100644 --- a/Source/Core/Extensions/DataReader.cs +++ b/Source/Core/Extensions/DataReader.cs @@ -1,81 +1,72 @@ -using System; -using System.Buffers; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Data; -using System.Data.Common; -using System.Diagnostics.Contracts; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -#if NETSTANDARD2_1 -using System.Runtime.CompilerServices; -#endif +using System.Diagnostics; namespace Open.Database.Extensions; /// /// Extension methods for Data Readers. /// +#pragma warning disable IDE0079 // Remove unnecessary suppression +[SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload provided for convienience.")] +[SuppressMessage("Reliability", "CA2016:Forward the 'CancellationToken' parameter to methods that take one", Justification = "Intentional to prevent cancellation exception.")] +#pragma warning restore IDE0079 // Remove unnecessary suppression public static class DataReaderExtensions { - /// - /// Iterates all records from an . - /// - /// The IDataReader to iterate. - /// The handler function for each . - /// If true, when cancelled, may exit the iteration via an exception. Otherwise when cancelled will simply stop iterating and return without exception. - /// An optional cancellation token for stopping the iteration. - public static void ForEach( + /// + /// Iterates all records from an . + /// + /// The IDataReader to iterate. + /// The handler function for each . + /// If true, when canceled, may exit the iteration via an exception. Otherwise when canceled will simply stop iterating and return without exception. + /// An optional cancellation token for stopping the iteration. + public static void ForEach( this IDataReader reader, Action handler, bool throwOnCancellation, CancellationToken cancellationToken = default) - { - if (reader is null) throw new ArgumentNullException(nameof(reader)); - if (handler is null) throw new ArgumentNullException(nameof(handler)); - Contract.EndContractBlock(); - - if (cancellationToken.CanBeCanceled) - { - if (throwOnCancellation) - cancellationToken.ThrowIfCancellationRequested(); - else if (cancellationToken.IsCancellationRequested) - return; - - // The following pattern allows for the reader to complete if it actually reached the end before cancellation. - var cancelled = false; - while (reader.Read()) - { - if (cancelled) - { - handler(reader); // we recieved the results, might as well use them. - if (throwOnCancellation) - cancellationToken.ThrowIfCancellationRequested(); - - break; - } - else - { - cancelled = cancellationToken.IsCancellationRequested; - handler(reader); - } - } - } - else - { - while (reader.Read()) - handler(reader); - } - } - - /// - public static void ForEach( + { + if (reader is null) throw new ArgumentNullException(nameof(reader)); + if (handler is null) throw new ArgumentNullException(nameof(handler)); + Contract.EndContractBlock(); + + if (cancellationToken.CanBeCanceled) + { + if (throwOnCancellation) + cancellationToken.ThrowIfCancellationRequested(); + else if (cancellationToken.IsCancellationRequested) + return; + + // The following pattern allows for the reader to complete if it actually reached the end before cancellation. + bool cancelled = false; + while (reader.Read()) + { + if (cancelled) + { + handler(reader); // we recieved the results, might as well use them. + if (throwOnCancellation) + cancellationToken.ThrowIfCancellationRequested(); + + break; + } + else + { + cancelled = cancellationToken.IsCancellationRequested; + handler(reader); + } + } + } + else + { + while (reader.Read()) + handler(reader); + } + } + + /// + public static void ForEach( this IDataReader reader, Action handler, CancellationToken cancellationToken = default) - => ForEach(reader, handler, false, cancellationToken); + => ForEach(reader, handler, false, cancellationToken); /// /// Iterates all records from an . @@ -85,24 +76,24 @@ public static void ForEach( /// If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. /// Optional cancellation token. public static async ValueTask ForEachAsync(this DbDataReader reader, - Action handler, - bool useReadAsync = true, - CancellationToken cancellationToken = default) - { - if (reader is null) throw new ArgumentNullException(nameof(reader)); - if (handler is null) throw new ArgumentNullException(nameof(handler)); - Contract.EndContractBlock(); - - if (useReadAsync) - { - while (await reader.ReadAsync(cancellationToken).ConfigureAwait(true)) - handler(reader); - } - else - { - ForEach(reader, handler, true, cancellationToken); - } - } + Action handler, + bool useReadAsync = true, + CancellationToken cancellationToken = default) + { + if (reader is null) throw new ArgumentNullException(nameof(reader)); + if (handler is null) throw new ArgumentNullException(nameof(handler)); + Contract.EndContractBlock(); + + if (useReadAsync) + { + while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false)) + handler(reader); + } + else + { + ForEach(reader, handler, true, cancellationToken); + } + } /// public static async ValueTask ForEachAsync( @@ -110,173 +101,197 @@ public static async ValueTask ForEachAsync( Func handler, bool useReadAsync = true, CancellationToken cancellationToken = default) - { - if (reader is null) throw new ArgumentNullException(nameof(reader)); - if (handler is null) throw new ArgumentNullException(nameof(handler)); - Contract.EndContractBlock(); - - if (useReadAsync) - { - while (await reader.ReadAsync(cancellationToken).ConfigureAwait(true)) - await handler(reader).ConfigureAwait(false); - } - else if (cancellationToken.CanBeCanceled) - { - cancellationToken.ThrowIfCancellationRequested(); - - // The following pattern allows for the reader to complete if it actually reached the end before cancellation. - var cancelled = false; - while (reader.Read()) - { - if (cancelled) - { - await handler(reader).ConfigureAwait(false); // we recieved the results, might as well use them. - cancellationToken.ThrowIfCancellationRequested(); - } - else - { - cancelled = cancellationToken.IsCancellationRequested; - await handler(reader).ConfigureAwait(false); - } - } - } - else - { - while (reader.Read()) - await handler(reader).ConfigureAwait(false); - } - } + { + if (reader is null) throw new ArgumentNullException(nameof(reader)); + if (handler is null) throw new ArgumentNullException(nameof(handler)); + Contract.EndContractBlock(); + + if (useReadAsync) + { + while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false)) + await handler(reader).ConfigureAwait(false); + } + else if (cancellationToken.CanBeCanceled) + { + cancellationToken.ThrowIfCancellationRequested(); + + // The following pattern allows for the reader to complete if it actually reached the end before cancellation. + bool cancelled = false; + while (reader.Read()) + { + if (cancelled) + { + await handler(reader).ConfigureAwait(false); // we recieved the results, might as well use them. + cancellationToken.ThrowIfCancellationRequested(); + } + else + { + cancelled = cancellationToken.IsCancellationRequested; + await handler(reader).ConfigureAwait(false); + } + } + } + else + { + while (reader.Read()) + await handler(reader).ConfigureAwait(false); + } + } /// public static ValueTask ForEachAsync(this DbDataReader reader, Func handler, CancellationToken cancellationToken) - => ForEachAsync(reader, handler, true, cancellationToken); - - /// - public static IEnumerable AsEnumerable(this IDataReader reader) - { - return reader is null - ? throw new ArgumentNullException(nameof(reader)) - : AsEnumerableCore(reader); - - static IEnumerable AsEnumerableCore(IDataReader reader) - { - if (!reader.Read()) - yield break; - - var fieldCount = reader.FieldCount; - do - { - var row = new object[fieldCount]; - reader.GetValues(row); - yield return row; - } while (reader.Read()); - } - } + => ForEachAsync(reader, handler, true, cancellationToken); + + static IEnumerable AsEnumerableCore(IDataReader reader) + { + Debug.Assert(reader is not null); + + if (!reader.Read()) + yield break; + + int fieldCount = reader.FieldCount; + do + { + object[] row = new object[fieldCount]; + reader.GetValues(row); + yield return row; + } while (reader.Read()); + } + + static IEnumerable AsEnumerableCore(IDataReader reader, ArrayPool arrayPool) + { + Debug.Assert(reader is not null); + Debug.Assert(arrayPool is not null); + + if (!reader.Read()) + yield break; + + int fieldCount = reader.FieldCount; + do + { + object[] row = arrayPool.Rent(fieldCount); + reader.GetValues(row); + yield return row; + } while (reader.Read()); + } /// - public static IEnumerable AsEnumerable(this IDataReader reader, ArrayPool arrayPool) - { - return reader is null - ? throw new ArgumentNullException(nameof(reader)) - : arrayPool is null - ? throw new ArgumentNullException(nameof(arrayPool)) - : AsEnumerableCore(reader, arrayPool); - - static IEnumerable AsEnumerableCore(IDataReader reader, ArrayPool arrayPool) - { - if (!reader.Read()) - yield break; - - var fieldCount = reader.FieldCount; - do - { - var row = arrayPool.Rent(fieldCount); - reader.GetValues(row); - yield return row; - } while (reader.Read()); - } - } - - internal static IEnumerable AsEnumerableInternal(this IDataReader reader, IEnumerable ordinals, bool readStarted) - { - return reader is null + public static IEnumerable AsEnumerable(this IDataReader reader) + => reader is null ? throw new ArgumentNullException(nameof(reader)) - : ordinals is null - ? throw new ArgumentNullException(nameof(ordinals)) - : AsEnumerableInternalCore(reader, ordinals, readStarted); - - static IEnumerable AsEnumerableInternalCore(IDataReader reader, IEnumerable ordinals, bool readStarted) - { - if (!readStarted && !reader.Read()) - yield break; - - var o = ordinals as IList ?? ordinals.ToArray(); - var fieldCount = o.Count; - if (fieldCount == 0) - { - do - { - yield return Array.Empty(); - } - while (reader.Read()); - yield break; - } - - do - { - var row = new object[fieldCount]; - for (var i = 0; i < fieldCount; i++) - row[i] = reader.GetValue(o[i]); - yield return row; - } - while (reader.Read()); - } - } - - internal static IEnumerable AsEnumerableInternal(this IDataReader reader, IEnumerable ordinals, bool readStarted, ArrayPool arrayPool) - { - return reader is null + : AsEnumerableCore(reader); + + /// + public static IEnumerable AsEnumerable(this IDataReader reader, ArrayPool? arrayPool) + => reader is null ? throw new ArgumentNullException(nameof(reader)) - : ordinals is null - ? throw new ArgumentNullException(nameof(ordinals)) - : arrayPool is null ? throw new ArgumentNullException(nameof(arrayPool)) - : AsEnumerableInternalCore(); + : arrayPool is null + ? AsEnumerableCore(reader) + : AsEnumerableCore(reader, arrayPool); + + static IEnumerable AsEnumerableInternalCore( + IDataReader reader, IEnumerable ordinals, bool readStarted) + { + Debug.Assert(reader is not null); + Debug.Assert(ordinals is not null); + + if (!readStarted && !reader.Read()) + yield break; + + IList o = ordinals as IList ?? ordinals.ToArray(); + int fieldCount = o.Count; + if (fieldCount == 0) + { + do + { + yield return Array.Empty(); + } + while (reader.Read()); + yield break; + } + + do + { + object[] row = new object[fieldCount]; + for (int i = 0; i < fieldCount; i++) + row[i] = reader.GetValue(o[i]); + yield return row; + } + while (reader.Read()); + } + + static IEnumerable AsEnumerableInternalCore( + IDataReader reader, IEnumerable ordinals, bool readStarted, ArrayPool arrayPool) + { + Debug.Assert(reader is not null); + Debug.Assert(ordinals is not null); + Debug.Assert(arrayPool is not null); + + if (!readStarted && !reader.Read()) + yield break; - IEnumerable AsEnumerableInternalCore() + IList o = ordinals as IList ?? ordinals.ToArray(); + int fieldCount = o.Count; + do { - if (!readStarted && !reader.Read()) - yield break; + object[] row = arrayPool.Rent(fieldCount); + for (int i = 0; i < fieldCount; i++) + row[i] = reader.GetValue(o[i]); + yield return row; + } + while (reader.Read()); + } + + internal static IEnumerable AsEnumerableInternal( + this IDataReader reader, + IEnumerable ordinals, + bool readStarted) + { + if (reader is null) throw new ArgumentNullException(nameof(reader)); + if (ordinals is null) throw new ArgumentNullException(nameof(ordinals)); + Contract.EndContractBlock(); - var o = ordinals as IList ?? ordinals.ToArray(); - var fieldCount = o.Count; - do - { - var row = arrayPool.Rent(fieldCount); - for (var i = 0; i < fieldCount; i++) - row[i] = reader.GetValue(o[i]); - yield return row; - } - while (reader.Read()); - } + return AsEnumerableInternalCore(reader, ordinals, readStarted); + } + + internal static IEnumerable AsEnumerableInternal( + this IDataReader reader, + IEnumerable ordinals, + bool readStarted, + ArrayPool? arrayPool) + { + if (reader is null) throw new ArgumentNullException(nameof(reader)); + if (ordinals is null) throw new ArgumentNullException(nameof(ordinals)); + Contract.EndContractBlock(); + + return arrayPool is null + ? AsEnumerableInternalCore(reader, ordinals, readStarted) + : AsEnumerableInternalCore(reader, ordinals, readStarted, arrayPool); } /// public static IEnumerable AsEnumerable(this IDataReader reader, IEnumerable ordinals) - => AsEnumerableInternal(reader, ordinals, false); - - /// The reader to enumerate. - /// The limited set of ordinals to include. If none are specified, the returned objects will be empty. - /// The array pool to acquire buffers from. - /// - public static IEnumerable AsEnumerable(this IDataReader reader, IEnumerable ordinals, ArrayPool arrayPool) - => AsEnumerableInternal(reader, ordinals, false, arrayPool); + => AsEnumerableInternal(reader, ordinals, false); /// The reader to enumerate. /// The first ordinal to include in the request to the reader for each record. /// The remaining ordinals to request from the reader for each record. /// +#if NET8_0_OR_GREATER + public static IEnumerable AsEnumerable(this IDataReader reader, int n, params IEnumerable others) + => AsEnumerableInternal(reader, others.Prepend(n), false); +#else + public static IEnumerable AsEnumerable(this IDataReader reader, int n, params int[] others) - => AsEnumerable(reader, CoreExtensions.Concat(n, others)); + => AsEnumerable(reader, CoreExtensions.Concat(n, others)); +#endif + + /// The reader to enumerate. + /// The limited set of ordinals to include. If none are specified, the returned objects will be empty. + /// The array pool to acquire buffers from. + /// + public static IEnumerable AsEnumerable(this IDataReader reader, IEnumerable ordinals, ArrayPool? arrayPool) + => AsEnumerableInternal(reader, ordinals, false, arrayPool); /// /// Provides an enumerable for iterating all the remaining values of the current result set of a data reader. @@ -287,17 +302,17 @@ public static IEnumerable AsEnumerable(this IDataReader reader, int n, /// The first ordinal to include in the request to the reader for each record. /// The remaining ordinals to request from the reader for each record. /// An enumerable of the values returned from a data reader. - public static IEnumerable AsEnumerable(this IDataReader reader, ArrayPool arrayPool, int n, params int[] others) - => AsEnumerable(reader, CoreExtensions.Concat(n, others), arrayPool); + public static IEnumerable AsEnumerable(this IDataReader reader, ArrayPool? arrayPool, int n, params int[] others) + => AsEnumerable(reader, CoreExtensions.Concat(n, others), arrayPool); - /// - public static IEnumerable Select(this IDataReader reader, Func transform) - { + /// + public static IEnumerable Select(this IDataReader reader, Func transform) + { return reader is null ? throw new ArgumentNullException(nameof(reader)) : transform is null - ? throw new ArgumentNullException(nameof(transform)) - : SelectCore(); + ? throw new ArgumentNullException(nameof(transform)) + : SelectCore(); IEnumerable SelectCore() { @@ -306,17 +321,17 @@ IEnumerable SelectCore() } } - /// - /// Iterates records from an and passes the IDataRecord to a transform function. - /// - /// The return type of the transform function. - /// The reader to iterate. - /// The transform function to process each . - /// A cancellation token for stopping the iteration. - /// If true, when cancelled, may exit the iteration via an exception. Otherwise when cancelled will simply stop iterating and return without exception. - /// An enumerable used to iterate the results. - public static IEnumerable Select(this IDataReader reader, Func transform, CancellationToken cancellationToken, bool throwOnCancellation = false) - { + /// + /// Iterates records from an and passes the IDataRecord to a transform function. + /// + /// The return type of the transform function. + /// The reader to iterate. + /// The transform function to process each . + /// A cancellation token for stopping the iteration. + /// If true, when canceled, may exit the iteration via an exception. Otherwise when canceled will simply stop iterating and return without exception. + /// An enumerable used to iterate the results. + public static IEnumerable Select(this IDataReader reader, Func transform, CancellationToken cancellationToken, bool throwOnCancellation = false) + { return reader is null ? throw new ArgumentNullException(nameof(reader)) : transform is null @@ -333,7 +348,7 @@ static IEnumerable SelectCore(IDataReader reader, Func transf yield break; // The following pattern allows for the reader to complete if it actually reached the end before cancellation. - var cancelled = false; + bool cancelled = false; while (reader.Read()) { if (cancelled) @@ -360,74 +375,81 @@ static IEnumerable SelectCore(IDataReader reader, Func transf } /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload provided for convienience.")] - public static IEnumerable Select(this IDataReader reader, CancellationToken cancellationToken, Func transform, bool throwOnCancellation = false) - => Select(reader, transform, cancellationToken, throwOnCancellation); + public static IEnumerable Select(this IDataReader reader, CancellationToken cancellationToken, Func transform, bool throwOnCancellation = false) + => Select(reader, transform, cancellationToken, throwOnCancellation); -#if NETSTANDARD2_1 +#if NETSTANDARD2_0 +#else - /// The reader to enumerate. - /// Optional iteration cancellation token. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2016:Forward the 'CancellationToken' parameter to methods that take one", Justification = "Intentional for this method to prevent cancellation exception.")] - public static IAsyncEnumerable AsAsyncEnumerable( - this DbDataReader reader, - CancellationToken cancellationToken = default) - { - return reader is null - ? throw new ArgumentNullException(nameof(reader)) - : AsAsyncEnumerableCore(reader, cancellationToken); + static async IAsyncEnumerable AsAsyncEnumerableCore(DbDataReader reader, [EnumeratorCancellation] CancellationToken cancellationToken) + { + Contract.EndContractBlock(); - static async IAsyncEnumerable AsAsyncEnumerableCore(DbDataReader reader, [EnumeratorCancellation] CancellationToken cancellationToken) + if (cancellationToken.IsCancellationRequested + || !await reader.ReadAsync(CancellationToken.None).ConfigureAwait(false)) { - Contract.EndContractBlock(); + yield break; + } - if (cancellationToken.IsCancellationRequested || !await reader.ReadAsync().ConfigureAwait(true)) - yield break; + int fieldCount = reader.FieldCount; + do + { + object[] row = new object[fieldCount]; + reader.GetValues(row); + yield return row; + } + while (!cancellationToken.IsCancellationRequested + && await reader.ReadAsync(CancellationToken.None).ConfigureAwait(false)); + } - var fieldCount = reader.FieldCount; - do - { - var row = new object[fieldCount]; - reader.GetValues(row); - yield return row; - } - while (!cancellationToken.IsCancellationRequested && await reader.ReadAsync().ConfigureAwait(true)); + static async IAsyncEnumerable AsAsyncEnumerableCore(DbDataReader reader, ArrayPool arrayPool, [EnumeratorCancellation] CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested || !await reader.ReadAsync().ConfigureAwait(false)) + yield break; + + int fieldCount = reader.FieldCount; + do + { + object[] row = arrayPool.Rent(fieldCount); + reader.GetValues(row); + yield return row; } + while (!cancellationToken.IsCancellationRequested && await reader.ReadAsync().ConfigureAwait(false)); } /// The reader to enumerate. - /// An optional array pool to acquire buffers from. /// Optional iteration cancellation token. /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2016:Forward the 'CancellationToken' parameter to methods that take one", Justification = "Intentional for this method to prevent cancellation exception.")] - public static IAsyncEnumerable AsAsyncEnumerable(this DbDataReader reader, ArrayPool arrayPool, CancellationToken cancellationToken = default) - { - return reader is null + public static IAsyncEnumerable AsAsyncEnumerable( + this DbDataReader reader, + CancellationToken cancellationToken = default) + => reader is null ? throw new ArgumentNullException(nameof(reader)) - : arrayPool is null - ? throw new ArgumentNullException(nameof(arrayPool)) - : AsAsyncEnumerableCore(reader, arrayPool, cancellationToken); + : AsAsyncEnumerableCore(reader, cancellationToken); - static async IAsyncEnumerable AsAsyncEnumerableCore(DbDataReader reader, ArrayPool arrayPool, [EnumeratorCancellation] CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested || !await reader.ReadAsync().ConfigureAwait(true)) - yield break; + /// The reader to enumerate. + /// An optional array pool to acquire buffers from. + /// Optional iteration cancellation token. + /// + public static IAsyncEnumerable AsAsyncEnumerable( + this DbDataReader reader, + ArrayPool? arrayPool, + CancellationToken cancellationToken = default) + { + if (reader is null) throw new ArgumentNullException(nameof(reader)); + Contract.EndContractBlock(); - var fieldCount = reader.FieldCount; - do - { - var row = arrayPool.Rent(fieldCount); - reader.GetValues(row); - yield return row; - } - while (!cancellationToken.IsCancellationRequested && await reader.ReadAsync().ConfigureAwait(true)); - } + return arrayPool is null + ? AsAsyncEnumerableCore(reader, cancellationToken) + : AsAsyncEnumerableCore(reader, arrayPool, cancellationToken); } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2016:Forward the 'CancellationToken' parameter to methods that take one", Justification = "Intentional for this method to prevent cancellation exception.")] - static IAsyncEnumerable AsAsyncEnumerableInternal(this DbDataReader reader, IEnumerable ordinals, bool readStarted, CancellationToken cancellationToken) - { + static IAsyncEnumerable AsAsyncEnumerableInternal( + this DbDataReader reader, + IEnumerable ordinals, + bool readStarted, + CancellationToken cancellationToken) + { return reader is null ? throw new ArgumentNullException(nameof(reader)) : ordinals is null @@ -436,92 +458,95 @@ static IAsyncEnumerable AsAsyncEnumerableInternal(this DbDataReader re static async IAsyncEnumerable AsAsyncEnumerableInternalCore(DbDataReader reader, IEnumerable ordinals, bool readStarted, [EnumeratorCancellation] CancellationToken cancellationToken) { - if (!readStarted && (cancellationToken.IsCancellationRequested || !await reader.ReadAsync().ConfigureAwait(true))) + if (!readStarted && (cancellationToken.IsCancellationRequested || !await reader.ReadAsync(CancellationToken.None).ConfigureAwait(false))) yield break; - var o = ordinals as IList ?? ordinals.ToArray(); - var fieldCount = o.Count; + IList o = ordinals as IList ?? ordinals.ToArray(); + int fieldCount = o.Count; if (fieldCount == 0) { do { yield return Array.Empty(); } - while (!cancellationToken.IsCancellationRequested && await reader.ReadAsync().ConfigureAwait(true)); + while (!cancellationToken.IsCancellationRequested && await reader.ReadAsync(CancellationToken.None).ConfigureAwait(false)); } else { do { - var row = new object[fieldCount]; - for (var i = 0; i < fieldCount; i++) + object[] row = new object[fieldCount]; + for (int i = 0; i < fieldCount; i++) row[i] = reader.GetValue(o[i]); yield return row; } - while (!cancellationToken.IsCancellationRequested && await reader.ReadAsync().ConfigureAwait(true)); + while (!cancellationToken.IsCancellationRequested && await reader.ReadAsync(CancellationToken.None).ConfigureAwait(false)); } } } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2016:Forward the 'CancellationToken' parameter to methods that take one", Justification = "Intentional for this method to prevent cancellation exception.")] - static IAsyncEnumerable AsAsyncEnumerableInternal( + static IAsyncEnumerable AsAsyncEnumerableInternal( this DbDataReader reader, IEnumerable ordinals, bool readStarted, - ArrayPool arrayPool, + ArrayPool arrayPool, CancellationToken cancellationToken) - { - return reader is null - ? throw new ArgumentNullException(nameof(reader)) - : ordinals is null - ? throw new ArgumentNullException(nameof(ordinals)) - : arrayPool is null - ? throw new ArgumentNullException(nameof(arrayPool)) - : AsAsyncEnumerableInternalCore(reader, ordinals, readStarted, arrayPool, cancellationToken); + { + if (reader is null) throw new ArgumentNullException(nameof(reader)); + if (ordinals is null) throw new ArgumentNullException(nameof(ordinals)); + Debug.Assert(arrayPool is not null); + Contract.EndContractBlock(); + + return AsAsyncEnumerableInternalCore(reader, ordinals, readStarted, arrayPool, cancellationToken); - static async IAsyncEnumerable AsAsyncEnumerableInternalCore( + static async IAsyncEnumerable AsAsyncEnumerableInternalCore( DbDataReader reader, IEnumerable ordinals, bool readStarted, - ArrayPool arrayPool, - [EnumeratorCancellation] CancellationToken cancellationToken) + ArrayPool arrayPool, + [EnumeratorCancellation] CancellationToken cancellationToken) { - if (!readStarted && (cancellationToken.IsCancellationRequested || !await reader.ReadAsync().ConfigureAwait(true))) + if (!readStarted && (cancellationToken.IsCancellationRequested + || !await reader.ReadAsync(CancellationToken.None).ConfigureAwait(false))) + { yield break; + } - var o = ordinals as IList ?? ordinals.ToArray(); - var fieldCount = o.Count; + IList o = ordinals as IList ?? ordinals.ToArray(); + int fieldCount = o.Count; do { - var row = arrayPool.Rent(fieldCount); - for (var i = 0; i < fieldCount; i++) + object[] row = arrayPool.Rent(fieldCount); + for (int i = 0; i < fieldCount; i++) row[i] = reader.GetValue(o[i]); yield return row; } - while (!cancellationToken.IsCancellationRequested && await reader.ReadAsync().ConfigureAwait(true)); + while (!cancellationToken.IsCancellationRequested + && await reader.ReadAsync(CancellationToken.None).ConfigureAwait(false)); } } /// public static IAsyncEnumerable AsAsyncEnumerable(this DbDataReader reader, IEnumerable ordinals, CancellationToken cancellationToken = default) - => AsAsyncEnumerableInternal(reader, ordinals, false, cancellationToken); + => AsAsyncEnumerableInternal(reader, ordinals, false, cancellationToken); /// The reader to enumerate. /// The limited set of ordinals to include. If none are specified, the returned objects will be empty. /// The array pool to acquire buffers from. /// Optional iteration cancellation token. /// - public static IAsyncEnumerable AsAsyncEnumerable(this DbDataReader reader, IEnumerable ordinals, ArrayPool arrayPool, CancellationToken cancellationToken = default) - => AsAsyncEnumerableInternal(reader, ordinals, false, arrayPool, cancellationToken); + public static IAsyncEnumerable AsAsyncEnumerable(this DbDataReader reader, IEnumerable ordinals, ArrayPool? arrayPool, CancellationToken cancellationToken = default) + => arrayPool is null + ? AsAsyncEnumerableInternal(reader, ordinals, false, cancellationToken) + : AsAsyncEnumerableInternal(reader, ordinals, false, arrayPool, cancellationToken); /// The reader to enumerate. /// The iteration cancellation token. /// The first ordinal to include in the request to the reader for each record. /// The remaining ordinals to request from the reader for each record. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Extended params prevent this.")] - public static IAsyncEnumerable AsAsyncEnumerable(this DbDataReader reader, CancellationToken cancellationToken, int n, params int[] others) - => AsAsyncEnumerable(reader, CoreExtensions.Concat(n, others), cancellationToken); + /// + public static IAsyncEnumerable AsAsyncEnumerable(this DbDataReader reader, CancellationToken cancellationToken, int n, params int[] others) + => AsAsyncEnumerable(reader, CoreExtensions.Concat(n, others), cancellationToken); /// The reader to enumerate. /// The array pool to acquire buffers from. @@ -529,17 +554,16 @@ public static IAsyncEnumerable AsAsyncEnumerable(this DbDataReader rea /// The first ordinal to include in the request to the reader for each record. /// The remaining ordinals to request from the reader for each record. /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Extended params prevent this.")] - public static IAsyncEnumerable AsAsyncEnumerable(this DbDataReader reader, ArrayPool arrayPool, CancellationToken cancellationToken, int n, params int[] others) - => AsAsyncEnumerable(reader, CoreExtensions.Concat(n, others), arrayPool, cancellationToken); + public static IAsyncEnumerable AsAsyncEnumerable(this DbDataReader reader, ArrayPool? arrayPool, CancellationToken cancellationToken, int n, params int[] others) + => AsAsyncEnumerable(reader, CoreExtensions.Concat(n, others), arrayPool, cancellationToken); /// public static IAsyncEnumerable AsAsyncEnumerable(this DbDataReader reader, int n, params int[] others) - => AsAsyncEnumerable(reader, CoreExtensions.Concat(n, others)); + => AsAsyncEnumerable(reader, CoreExtensions.Concat(n, others)); /// - public static IAsyncEnumerable AsAsyncEnumerable(this DbDataReader reader, ArrayPool arrayPool, int n, params int[] others) - => AsAsyncEnumerable(reader, CoreExtensions.Concat(n, others), arrayPool); + public static IAsyncEnumerable AsAsyncEnumerable(this DbDataReader reader, ArrayPool? arrayPool, int n, params int[] others) + => AsAsyncEnumerable(reader, CoreExtensions.Concat(n, others), arrayPool); /// /// Asyncronously iterates all records from a data reader..] @@ -547,51 +571,56 @@ public static IAsyncEnumerable AsAsyncEnumerable(this DbDataReader rea /// The return type of the transform function. /// The DbDataReader to iterate. /// The transform function to process each . - /// If true, when cancelled, may exit the iteration via an exception. Otherwise when cancelled will simply stop iterating and return without exception. + /// If true, when canceled, may exit the iteration via an exception. Otherwise when canceled will simply stop iterating and return without exception. /// The cancellation token. /// An enumerable used to iterate the results. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2016:Forward the 'CancellationToken' parameter to methods that take one", Justification = "Intentional for this method to prevent cancellation exception.")] - public static IAsyncEnumerable SelectAsync(this DbDataReader reader, - Func transform, - bool throwOnCancellation, - CancellationToken cancellationToken = default) - { + public static IAsyncEnumerable SelectAsync(this DbDataReader reader, + Func transform, + bool throwOnCancellation, + CancellationToken cancellationToken = default) + { return reader is null ? throw new ArgumentNullException(nameof(reader)) : transform is null ? throw new ArgumentNullException(nameof(transform)) : SelectAsyncCore(reader, transform, throwOnCancellation, cancellationToken); - static async IAsyncEnumerable SelectAsyncCore(DbDataReader reader, Func transform, bool throwOnCancellation, [EnumeratorCancellation] CancellationToken cancellationToken) + static async IAsyncEnumerable SelectAsyncCore( + DbDataReader reader, + Func transform, + bool throwOnCancellation, + [EnumeratorCancellation] CancellationToken cancellationToken) { if (throwOnCancellation) { cancellationToken.ThrowIfCancellationRequested(); - while (await reader.ReadAsync(cancellationToken).ConfigureAwait(true)) + while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false)) yield return transform(reader); } else { if (cancellationToken.IsCancellationRequested) yield break; - while (!cancellationToken.IsCancellationRequested && await reader.ReadAsync().ConfigureAwait(true)) + while (!cancellationToken.IsCancellationRequested + && await reader.ReadAsync(CancellationToken.None).ConfigureAwait(false)) + { yield return transform(reader); + } } } } - /// - public static IAsyncEnumerable SelectAsync(this DbDataReader reader, - Func transform, - CancellationToken cancellationToken = default) - => SelectAsync(reader, transform, false, cancellationToken); + /// + public static IAsyncEnumerable SelectAsync(this DbDataReader reader, + Func transform, + CancellationToken cancellationToken = default) + => SelectAsync(reader, transform, false, cancellationToken); /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2016:Forward the 'CancellationToken' parameter to methods that take one", Justification = "Intentional for this method to prevent cancellation exception.")] - public static IAsyncEnumerable SelectAsync(this IDataReader reader, - Func> transform, - bool throwOnCancellation, - CancellationToken cancellationToken = default) - { + public static IAsyncEnumerable SelectAsync(this IDataReader reader, + Func> transform, + bool throwOnCancellation, + CancellationToken cancellationToken = default) + { return reader is null ? throw new ArgumentNullException(nameof(reader)) : transform is null @@ -605,7 +634,7 @@ static async IAsyncEnumerable SelectAsyncCore(IDataReader reader, Func SelectAsyncCore(IDataReader reader, Func SelectAsyncCore(IDataReader reader, Func public static IAsyncEnumerable SelectAsync(this IDataReader reader, - Func> transform, - CancellationToken cancellationToken = default) - => SelectAsync(reader, transform, false, cancellationToken); + Func> transform, + CancellationToken cancellationToken = default) + => SelectAsync(reader, transform, false, cancellationToken); #endif - /// - /// Shortcut for .Iterate(transform).ToList(); - /// - /// The return type of the transform function. - /// The IDataReader to iterate. - /// The transform function to process each . - /// Optional cancellation token. - /// A list of the transformed results. - public static List ToList(this IDataReader reader, - Func transform, CancellationToken cancellationToken = default) - => reader.Select(transform, cancellationToken).ToList(); - - /// - /// Asynchronously iterates all records using the data reader and returns the desired results as a list. - /// - /// The return type of the transform function. - /// The SqlDataReader to read from. - /// The transform function to process each . - /// Optional cancellation token. - /// A task containing a list of all results. - public static async ValueTask> ToListAsync(this DbDataReader reader, - Func transform, CancellationToken cancellationToken = default) - { - if (reader is null) throw new ArgumentNullException(nameof(reader)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - var list = new List(); - while (await reader.ReadAsync(cancellationToken).ConfigureAwait(true)) list.Add(transform(reader)); - return list; - } - - /// - public static async ValueTask> ToListAsync(this DbDataReader reader, - Func> transform, CancellationToken cancellationToken = default) - { - if (reader is null) throw new ArgumentNullException(nameof(reader)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - var list = new List(); - while (await reader.ReadAsync(cancellationToken).ConfigureAwait(true)) list.Add(await transform(reader).ConfigureAwait(false)); - return list; - } - - /// - /// Shortcut for .Select(transform).ToArray(); - /// - /// The return type of the transform function. - /// The IDataReader to iterate. - /// The transform function to process each . - /// An array of the transformed results. - public static T[] ToArray(this IDataReader reader, Func transform) - => reader.Select(transform).ToArray(); - - /// - /// Shortcut for .Select(transform).ToImmutableArray(); - /// - /// The return type of the transform function. - /// The IDataReader to iterate. - /// The transform function to process each . - /// An immutable array of the transformed results. - public static ImmutableArray ToImmutableArray(this IDataReader reader, Func transform) - => reader.Select(transform).ToImmutableArray(); - - /// - /// Loads all remaining data from an into a DataTable. - /// - /// The IDataReader to load data from. - /// The resultant DataTable. - public static DataTable ToDataTable(this IDataReader reader) - { - if (reader is null) throw new ArgumentNullException(nameof(reader)); - Contract.EndContractBlock(); - - var table = new DataTable(); - table.Load(reader); - return table; - } - - /// - /// Loads all data from a command through an into a DataTables. - /// Calls .NextResult() to check for more results. - /// - /// The IDataReader to load data from. - /// The resultant list of DataTables. - public static List ToDataTables(this IDataReader reader) - { - if (reader is null) throw new ArgumentNullException(nameof(reader)); - Contract.EndContractBlock(); - - var results = new List(); - do - { - results.Add(reader.ToDataTable()); - } - while (reader.NextResult()); - return results; - } + /// + /// Shortcut for .Iterate(transform).ToList(); + /// + /// The return type of the transform function. + /// The IDataReader to iterate. + /// The transform function to process each . + /// Optional cancellation token. + /// A list of the transformed results. + public static List ToList(this IDataReader reader, + Func transform, CancellationToken cancellationToken = default) + => reader.Select(transform, cancellationToken).ToList(); + + /// + /// Asynchronously iterates all records using the data reader and returns the desired results as a list. + /// + /// The return type of the transform function. + /// The SqlDataReader to read from. + /// The transform function to process each . + /// Optional cancellation token. + /// A task containing a list of all results. + public static async ValueTask> ToListAsync(this DbDataReader reader, + Func transform, CancellationToken cancellationToken = default) + { + if (reader is null) throw new ArgumentNullException(nameof(reader)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + var list = new List(); + while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false)) list.Add(transform(reader)); + return list; + } + + /// + public static async ValueTask> ToListAsync(this DbDataReader reader, + Func> transform, CancellationToken cancellationToken = default) + { + if (reader is null) throw new ArgumentNullException(nameof(reader)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + var list = new List(); + while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false)) list.Add(await transform(reader).ConfigureAwait(false)); + return list; + } + + /// + /// Shortcut for .Select(transform).ToArray(); + /// + /// The return type of the transform function. + /// The IDataReader to iterate. + /// The transform function to process each . + /// An array of the transformed results. + public static T[] ToArray(this IDataReader reader, Func transform) + => reader.Select(transform).ToArray(); + + /// + /// Shortcut for .Select(transform).ToImmutableArray(); + /// + /// The return type of the transform function. + /// The IDataReader to iterate. + /// The transform function to process each . + /// An immutable array of the transformed results. + public static ImmutableArray ToImmutableArray(this IDataReader reader, Func transform) + => reader.Select(transform).ToImmutableArray(); + + /// + /// Loads all remaining data from an into a DataTable. + /// + /// The IDataReader to load data from. + /// The resultant DataTable. + public static DataTable ToDataTable(this IDataReader reader) + { + if (reader is null) throw new ArgumentNullException(nameof(reader)); + Contract.EndContractBlock(); + + var table = new DataTable(); + table.Load(reader); + return table; + } + + /// + /// Loads all data from a command through an into a DataTables. + /// Calls .NextResult() to check for more results. + /// + /// The IDataReader to load data from. + /// The resultant list of DataTables. + public static List ToDataTables(this IDataReader reader) + { + if (reader is null) throw new ArgumentNullException(nameof(reader)); + Contract.EndContractBlock(); + + var results = new List(); + do + { + results.Add(reader.ToDataTable()); + } + while (reader.NextResult()); + return results; + } /// /// Iterates an while the predicate returns true. /// /// The to iterate. /// The handler function that processes each and decides if iteration should continue. - /// If true, when cancelled, may exit the iteration via an exception. Otherwise when cancelled will simply stop iterating and return without exception. + /// If true, when canceled, may exit the iteration via an exception. Otherwise when canceled will simply stop iterating and return without exception. /// An optional cancellation token for stopping the iteration. public static void IterateWhile(this IDataReader reader, Func predicate, bool throwOnCancellation, CancellationToken cancellationToken = default) - { - if (reader is null) throw new ArgumentNullException(nameof(reader)); - if (predicate is null) throw new ArgumentNullException(nameof(predicate)); - Contract.EndContractBlock(); - - if (cancellationToken.CanBeCanceled) - { - if (throwOnCancellation) - cancellationToken.ThrowIfCancellationRequested(); - else if (cancellationToken.IsCancellationRequested) - return; - - // The following pattern allows for the reader to complete if it actually reached the end before cancellation. - var cancelled = false; - while (reader.Read() && predicate(reader)) - { - if (cancelled) - { - if (throwOnCancellation) - cancellationToken.ThrowIfCancellationRequested(); - - break; - } - else - { - cancelled = cancellationToken.IsCancellationRequested; - } - } - } - else - { - while (reader.Read() && predicate(reader)) { } - } - } - - /// - public static void IterateWhile(this IDataReader reader, Func predicate, CancellationToken cancellationToken = default) - => IterateWhile(reader, predicate, false, cancellationToken); + { + if (reader is null) throw new ArgumentNullException(nameof(reader)); + if (predicate is null) throw new ArgumentNullException(nameof(predicate)); + Contract.EndContractBlock(); + + if (cancellationToken.CanBeCanceled) + { + if (throwOnCancellation) + cancellationToken.ThrowIfCancellationRequested(); + else if (cancellationToken.IsCancellationRequested) + return; + + // The following pattern allows for the reader to complete if it actually reached the end before cancellation. + bool cancelled = false; + while (reader.Read() && predicate(reader)) + { + if (cancelled) + { + if (throwOnCancellation) + cancellationToken.ThrowIfCancellationRequested(); + + break; + } + else + { + cancelled = cancellationToken.IsCancellationRequested; + } + } + } + else + { + while (reader.Read() && predicate(reader)) { } + } + } + + /// + public static void IterateWhile(this IDataReader reader, Func predicate, CancellationToken cancellationToken = default) + => IterateWhile(reader, predicate, false, cancellationToken); /// public static async ValueTask IterateWhileAsync( this DbDataReader reader, Func predicate, CancellationToken cancellationToken = default) - { - if (reader is null) throw new ArgumentNullException(nameof(reader)); - if (predicate is null) throw new ArgumentNullException(nameof(predicate)); - Contract.EndContractBlock(); + { + if (reader is null) throw new ArgumentNullException(nameof(reader)); + if (predicate is null) throw new ArgumentNullException(nameof(predicate)); + Contract.EndContractBlock(); - while (await reader.ReadAsync(cancellationToken).ConfigureAwait(true) && predicate(reader)) { } - } + while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false) && predicate(reader)) { } + } /// The DbDataReader to load data from. /// The handler function that processes each and decides if iteration should continue. @@ -807,78 +836,78 @@ public static async ValueTask IterateWhileAsync( /// Optional cancellation token. /// public static async ValueTask IterateWhileAsync(this DbDataReader reader, Func predicate, bool useReadAsync, CancellationToken cancellationToken = default) - { - if (reader is null) throw new ArgumentNullException(nameof(reader)); - if (predicate is null) throw new ArgumentNullException(nameof(predicate)); - Contract.EndContractBlock(); - - if (useReadAsync) - { - while (await reader.ReadAsync(cancellationToken).ConfigureAwait(true) && predicate(reader)) { } - } - else if (cancellationToken.CanBeCanceled) - { - cancellationToken.ThrowIfCancellationRequested(); - while (reader.Read() && predicate(reader)) - cancellationToken.ThrowIfCancellationRequested(); - } - else - { - while (reader.Read() && predicate(reader)) { } - } - } - - static async ValueTask IterateWhileAsyncInternal(IDataReader reader, Func> predicate, CancellationToken cancellationToken) - { - if (reader is null) throw new ArgumentNullException(nameof(reader)); - if (predicate is null) throw new ArgumentNullException(nameof(predicate)); - Contract.EndContractBlock(); - - if (cancellationToken.CanBeCanceled) - { - cancellationToken.ThrowIfCancellationRequested(); - - // The following pattern allows for the reader to complete if it actually reached the end before cancellation. - var cancelled = false; - while (reader.Read() && await predicate(reader).ConfigureAwait(true)) - { - if (cancelled) - { - cancellationToken.ThrowIfCancellationRequested(); - break; - } - else - { - cancelled = cancellationToken.IsCancellationRequested; - } - } - } - else - { - while (reader.Read() && await predicate(reader).ConfigureAwait(true)) { } - } - } + { + if (reader is null) throw new ArgumentNullException(nameof(reader)); + if (predicate is null) throw new ArgumentNullException(nameof(predicate)); + Contract.EndContractBlock(); + + if (useReadAsync) + { + while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false) && predicate(reader)) { } + } + else if (cancellationToken.CanBeCanceled) + { + cancellationToken.ThrowIfCancellationRequested(); + while (reader.Read() && predicate(reader)) + cancellationToken.ThrowIfCancellationRequested(); + } + else + { + while (reader.Read() && predicate(reader)) { } + } + } + + static async ValueTask IterateWhileAsyncInternal(IDataReader reader, Func> predicate, CancellationToken cancellationToken) + { + if (reader is null) throw new ArgumentNullException(nameof(reader)); + if (predicate is null) throw new ArgumentNullException(nameof(predicate)); + Contract.EndContractBlock(); + + if (cancellationToken.CanBeCanceled) + { + cancellationToken.ThrowIfCancellationRequested(); + + // The following pattern allows for the reader to complete if it actually reached the end before cancellation. + bool cancelled = false; + while (reader.Read() && await predicate(reader).ConfigureAwait(false)) + { + if (cancelled) + { + cancellationToken.ThrowIfCancellationRequested(); + break; + } + else + { + cancelled = cancellationToken.IsCancellationRequested; + } + } + } + else + { + while (reader.Read() && await predicate(reader).ConfigureAwait(false)) { } + } + } /// public static async ValueTask IterateWhileAsync( this IDataReader reader, Func> predicate, CancellationToken cancellationToken = default) - { - if (reader is null) throw new ArgumentNullException(nameof(reader)); - if (predicate is null) throw new ArgumentNullException(nameof(predicate)); - Contract.EndContractBlock(); - - if (reader is DbDataReader r) - { - while (await r.ReadAsync(cancellationToken).ConfigureAwait(true) && await predicate(reader).ConfigureAwait(true)) { } - } - else - { - // Does not use .ReadAsync(); - await IterateWhileAsyncInternal(reader, predicate, cancellationToken).ConfigureAwait(false); - } - } + { + if (reader is null) throw new ArgumentNullException(nameof(reader)); + if (predicate is null) throw new ArgumentNullException(nameof(predicate)); + Contract.EndContractBlock(); + + if (reader is DbDataReader r) + { + while (await r.ReadAsync(cancellationToken).ConfigureAwait(false) && await predicate(reader).ConfigureAwait(false)) { } + } + else + { + // Does not use .ReadAsync(); + await IterateWhileAsyncInternal(reader, predicate, cancellationToken).ConfigureAwait(false); + } + } /// The to iterate. /// The handler function that processes each and decides if iteration should continue. @@ -886,131 +915,132 @@ public static async ValueTask IterateWhileAsync( /// Optional cancellation token. /// public static ValueTask IterateWhileAsync(this IDataReader reader, Func> predicate, bool useReadAsync, CancellationToken cancellationToken = default) - { - if (reader is null) throw new ArgumentNullException(nameof(reader)); - if (predicate is null) throw new ArgumentNullException(nameof(predicate)); - Contract.EndContractBlock(); - - return useReadAsync - ? IterateWhileAsync(reader, predicate, cancellationToken) - // Does not use .ReadAsync(); - : IterateWhileAsyncInternal(reader, predicate, cancellationToken); - } - - /// - /// Reads the first column values from every record. - /// values are then converted to null. - /// - /// The enumerable first ordinal values. - public static IEnumerable FirstOrdinalResults(this IDataReader reader) - { - if (reader is null) throw new ArgumentNullException(nameof(reader)); - Contract.EndContractBlock(); - - var results = new Queue(reader.Select(r => r.GetValue(0))); - return results.DequeueEach().DBNullToNull(); - } - - /// - /// Reads the first column values from every record. - /// Any values are then converted to null and casted to type T0; - /// - /// The enumerable of casted values. - public static IEnumerable FirstOrdinalResults(this IDataReader reader) - => reader is DbDataReader dbr - ? dbr.FirstOrdinalResults() - : reader.FirstOrdinalResults().Cast(); - - /// - /// Reads the first column values from every record. - /// Any values are then converted to null and casted to type T0; - /// - /// The enumerable of casted values. - public static IEnumerable FirstOrdinalResults(this DbDataReader reader) - { - if (reader is null) throw new ArgumentNullException(nameof(reader)); - Contract.EndContractBlock(); - - var results = new Queue(); - while (reader.Read()) - { - results.Enqueue( - reader.IsDBNull(0) - ? default! - : reader.GetFieldValue(0) - ); - } - - return results.DequeueEach(); - } - - /// - /// Reads the first column values from every record. - /// values are converted to null. - /// - /// The IDataReader to iterate. - /// If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. - /// Optional cancellation token. - /// The list of values. - public static async ValueTask> FirstOrdinalResultsAsync(this DbDataReader reader, bool useReadAsync = true, CancellationToken cancellationToken = default) - { - if (reader is null) throw new ArgumentNullException(nameof(reader)); - Contract.EndContractBlock(); - - var results = new Queue(); - await reader.ForEachAsync(r => results.Enqueue(r.GetValue(0)), useReadAsync, cancellationToken).ConfigureAwait(false); - return results.DequeueEach().DBNullToNull(); - } - - /// - /// Reads the first column values from every record. - /// Any values are then converted to null and casted to type T0; - /// - /// The IDataReader to iterate. - /// If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. - /// Optional cancellation token. - /// The enumerable of casted values. - public static async ValueTask> FirstOrdinalResultsAsync(this DbDataReader reader, bool useReadAsync = true, CancellationToken cancellationToken = default) - { - if (reader is null) throw new ArgumentNullException(nameof(reader)); - Contract.EndContractBlock(); - - var results = new Queue(); - if (useReadAsync) - { - while (await reader.ReadAsync(cancellationToken).ConfigureAwait(true)) - { - results.Enqueue( - await reader.IsDBNullAsync(0, cancellationToken).ConfigureAwait(false) - ? default! - : await reader.GetFieldValueAsync(0, cancellationToken).ConfigureAwait(false) - ); - } - } - else if (cancellationToken.CanBeCanceled) - { - while (!cancellationToken.IsCancellationRequested && reader.Read()) - { - results.Enqueue( - reader.IsDBNull(0) - ? default! - : reader.GetFieldValue(0) - ); - } - cancellationToken.ThrowIfCancellationRequested(); - } - else - { - while (reader.Read()) - { - results.Enqueue( - reader.IsDBNull(0) - ? default! - : reader.GetFieldValue(0) - ); - } - } - - return results.DequeueEach(); - } + { + if (reader is null) throw new ArgumentNullException(nameof(reader)); + if (predicate is null) throw new ArgumentNullException(nameof(predicate)); + Contract.EndContractBlock(); + + return useReadAsync + ? IterateWhileAsync(reader, predicate, cancellationToken) + // Does not use .ReadAsync(); + : IterateWhileAsyncInternal(reader, predicate, cancellationToken); + } + + /// + /// Reads the first column values from every record. + /// values are then converted to null. + /// + /// The enumerable first ordinal values. + public static IEnumerable FirstOrdinalResults(this IDataReader reader) + { + if (reader is null) throw new ArgumentNullException(nameof(reader)); + Contract.EndContractBlock(); + + var results = new Queue(reader.Select(r => r.GetValue(0))); + return results.DequeueEach().DBNullToNull(); + } + + /// + /// Reads the first column values from every record. + /// Any values are then converted to null and casted to type T0; + /// + /// The enumerable of casted values. + public static IEnumerable FirstOrdinalResults(this IDataReader reader) + => reader is DbDataReader dbr + ? dbr.FirstOrdinalResults() + : reader.FirstOrdinalResults().Cast(); + + /// + /// Reads the first column values from every record. + /// Any values are then converted to null and casted to type T0; + /// + /// The enumerable of casted values. + public static IEnumerable FirstOrdinalResults(this DbDataReader reader) + { + if (reader is null) throw new ArgumentNullException(nameof(reader)); + Contract.EndContractBlock(); + + var results = new Queue(); + while (reader.Read()) + { + results.Enqueue( + reader.IsDBNull(0) + ? default! + : reader.GetFieldValue(0) + ); + } + + return results.DequeueEach(); + } + + /// + /// Reads the first column values from every record. + /// values are converted to null. + /// + /// The IDataReader to iterate. + /// If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. + /// Optional cancellation token. + /// The list of values. + public static async ValueTask> FirstOrdinalResultsAsync(this DbDataReader reader, bool useReadAsync = true, CancellationToken cancellationToken = default) + { + if (reader is null) throw new ArgumentNullException(nameof(reader)); + Contract.EndContractBlock(); + + var results = new Queue(); + await reader.ForEachAsync(r => results.Enqueue(r.GetValue(0)), useReadAsync, cancellationToken).ConfigureAwait(false); + return results.DequeueEach().DBNullToNull(); + } + + /// + /// Reads the first column values from every record. + /// Any values are then converted to null and casted to type T0; + /// + /// The IDataReader to iterate. + /// If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. + /// Optional cancellation token. + /// The enumerable of casted values. + public static async ValueTask> FirstOrdinalResultsAsync(this DbDataReader reader, bool useReadAsync = true, CancellationToken cancellationToken = default) + { + if (reader is null) throw new ArgumentNullException(nameof(reader)); + Contract.EndContractBlock(); + + var results = new Queue(); + if (useReadAsync) + { + while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false)) + { + results.Enqueue( + await reader.IsDBNullAsync(0, cancellationToken).ConfigureAwait(false) + ? default! + : await reader.GetFieldValueAsync(0, cancellationToken).ConfigureAwait(false) + ); + } + } + else if (cancellationToken.CanBeCanceled) + { + while (!cancellationToken.IsCancellationRequested && reader.Read()) + { + results.Enqueue( + reader.IsDBNull(0) + ? default! + : reader.GetFieldValue(0) + ); + } + + cancellationToken.ThrowIfCancellationRequested(); + } + else + { + while (reader.Read()) + { + results.Enqueue( + reader.IsDBNull(0) + ? default! + : reader.GetFieldValue(0) + ); + } + } + + return results.DequeueEach(); + } } diff --git a/Source/Core/Extensions/DataRecord.cs b/Source/Core/Extensions/DataRecord.cs index 418eb54..fbb41e2 100644 --- a/Source/Core/Extensions/DataRecord.cs +++ b/Source/Core/Extensions/DataRecord.cs @@ -1,240 +1,232 @@ -using System; -using System.Buffers; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Data; -using System.Diagnostics.Contracts; -using System.Linq; - -namespace Open.Database.Extensions; +namespace Open.Database.Extensions; /// /// Extension methods for IDataRecord access. /// public static partial class DataRecordExtensions { - /// - public static object[] GetValues(this IDataRecord record) - { - if (record is null) throw new ArgumentNullException(nameof(record)); - Contract.EndContractBlock(); - - var result = new object[record.FieldCount]; - record.GetValues(result); - return result; - } - - /// - /// Returns an array of values with the specified field count. - /// - /// The reader to get column names from. - /// The size of the resultant array. - /// The array of values. - public static object[] GetValues(this IDataRecord record, int arrayLength) - { - if (record is null) throw new ArgumentNullException(nameof(record)); - Contract.EndContractBlock(); - - var result = new object[arrayLength]; - record.GetValues(result); - return result; - } + /// + public static object[] GetValues(this IDataRecord record) + { + if (record is null) throw new ArgumentNullException(nameof(record)); + Contract.EndContractBlock(); + + object[] result = new object[record.FieldCount]; + record.GetValues(result); + return result; + } + + /// + /// Returns an array of values with the specified field count. + /// + /// The reader to get column names from. + /// The size of the resultant array. + /// The array of values. + public static object[] GetValues(this IDataRecord record, int arrayLength) + { + if (record is null) throw new ArgumentNullException(nameof(record)); + Contract.EndContractBlock(); + + object[] result = new object[arrayLength]; + record.GetValues(result); + return result; + } /// The reader to get column names from. /// The minimum size of the resultant array. /// The array pool to acquire buffers from. /// - public static object?[] GetValues(this IDataRecord record, int minimumArrayLength, ArrayPool arrayPool) - { - if (record is null) throw new ArgumentNullException(nameof(record)); - if (arrayPool is null) throw new ArgumentNullException(nameof(arrayPool)); - - Contract.EndContractBlock(); - var result = arrayPool.Rent(minimumArrayLength); - record.GetValues(result); - return result; - } + public static object[] GetValues(this IDataRecord record, int minimumArrayLength, ArrayPool arrayPool) + { + if (record is null) throw new ArgumentNullException(nameof(record)); + if (arrayPool is null) throw new ArgumentNullException(nameof(arrayPool)); + + Contract.EndContractBlock(); + object[] result = arrayPool.Rent(minimumArrayLength); + record.GetValues(result); + return result; + } /// The reader to get column names from. /// The array pool to acquire buffers from. /// - public static object?[] GetValues(this IDataRecord record, ArrayPool arrayPool) - { - if (record is null) throw new ArgumentNullException(nameof(record)); - if (arrayPool is null) throw new ArgumentNullException(nameof(arrayPool)); - - Contract.EndContractBlock(); - var result = arrayPool.Rent(record.FieldCount); - record.GetValues(result); - return result; - } - - /// - /// Returns all the column names for the current result set. - /// - /// The reader to get column names from. - /// The enumerable of column names. - public static IEnumerable ColumnNames(this IDataRecord record) - { + public static object[] GetValues(this IDataRecord record, ArrayPool arrayPool) + { + if (record is null) throw new ArgumentNullException(nameof(record)); + if (arrayPool is null) throw new ArgumentNullException(nameof(arrayPool)); + + Contract.EndContractBlock(); + object[] result = arrayPool.Rent(record.FieldCount); + record.GetValues(result); + return result; + } + + /// + /// Returns all the column names for the current result set. + /// + /// The reader to get column names from. + /// The enumerable of column names. + public static IEnumerable ColumnNames(this IDataRecord record) + { return record is null - ? throw new ArgumentNullException(nameof(record)) - : ColumnNamesCore(record); + ? throw new ArgumentNullException(nameof(record)) + : ColumnNamesCore(record); static IEnumerable ColumnNamesCore(IDataRecord record) { - var fieldCount = record.FieldCount; - for (var i = 0; i < fieldCount; i++) + int fieldCount = record.FieldCount; + for (int i = 0; i < fieldCount; i++) yield return record.GetName(i); } } - /// - /// Returns all the column names for the current result set. - /// - /// The reader to get column names from. - /// The array of column names. - public static ImmutableArray GetNames(this IDataRecord record) - { - if (record is null) throw new ArgumentNullException(nameof(record)); - Contract.EndContractBlock(); - - var fieldCount = record.FieldCount; - var columnNames = ImmutableArray.CreateBuilder(fieldCount); - for (var i = 0; i < fieldCount; i++) - columnNames[i] = record.GetName(i); - return columnNames.ToImmutable(); - } - - /// - /// Returns all the column names for the current result set by index provided by the ordinals. - /// - /// The reader to get column names from. - /// The list (and order) of ordinals to look up. - /// The array of column names. - public static ImmutableArray GetNames(this IDataRecord record, IEnumerable ordinals) - { - if (record is null) throw new ArgumentNullException(nameof(record)); - Contract.EndContractBlock(); - - return ordinals.Select(o => record.GetName(o)).ToImmutableArray(); - } - - /// - /// Returns the (name,ordinal) mapping for current result set. - /// - /// The reader to get column names from. - /// An enumerable of the mappings. - public static IEnumerable<(string Name, int Ordinal)> OrdinalMapping(this IDataRecord record) - => record.ColumnNames().Select((n, o) => (Name: n, Ordinal: o)); - - /// - /// Returns an array of name to ordinal mappings. - /// - /// The to query the ordinals from. - /// The requested column names. - /// An enumerable of the mappings. - public static IEnumerable<(string Name, int Ordinal)> OrdinalMapping(this IDataRecord record, IEnumerable columnNames) - => columnNames.Select(n => - { - // Does do a case-insensitive search after a case-sensitive one. - // https://docs.microsoft.com/en-us/dotnet/api/system.data.idatarecord.getordinal - var ordinal = record.GetOrdinal(n); - var name = record.GetName(ordinal); // Get actual casing. - return (name, ordinal); - }); - - /// - /// Returns an array of name to ordinal mappings. - /// - /// The to query the ordinals from. - /// The requested column names. - /// If true, will order the results by ordinal ascending. - /// An enumerable of the mappings. - public static IEnumerable<(string Name, int Ordinal)> OrdinalMapping(this IDataRecord record, IEnumerable columnNames, bool sort) - => sort - ? OrdinalMapping(record, columnNames).OrderBy(m => m.Ordinal) - : OrdinalMapping(record, columnNames); - - /// - /// Returns an array of name to ordinal mappings. - /// - /// The to query the ordinals from. - /// The requested column names. - /// If true, will order the results by ordinal ascending. - /// - public static (string Name, int Ordinal)[] GetOrdinalMapping(this IDataRecord record, IEnumerable columnNames, bool sort = false) - { - if (columnNames is ICollection cn && cn.Count == 0) - return Array.Empty<(string Name, int Ordinal)>(); - - try - { - return record - .OrdinalMapping(columnNames, sort) - .ToArray(); - } - catch (IndexOutOfRangeException ex) - { - var mismatch = new HashSet(columnNames); - mismatch.ExceptWith(record.GetNames()); - - // Columns not mapped correctly. Report all columns that are mismatched/missing. - throw new IndexOutOfRangeException($"Invalid columns: {string.Join(", ", mismatch.OrderBy(c => c).ToArray())}", ex); - } - } + /// + /// Returns all the column names for the current result set. + /// + /// The reader to get column names from. + /// The array of column names. + public static ImmutableArray GetNames(this IDataRecord record) + { + if (record is null) throw new ArgumentNullException(nameof(record)); + Contract.EndContractBlock(); + + int fieldCount = record.FieldCount; + ImmutableArray.Builder columnNames = ImmutableArray.CreateBuilder(fieldCount); + columnNames.Count = fieldCount; + for (int i = 0; i < fieldCount; i++) + columnNames[i] = record.GetName(i); + return columnNames.MoveToImmutable(); + } + + /// + /// Returns all the column names for the current result set by index provided by the ordinals. + /// + /// The reader to get column names from. + /// The list (and order) of ordinals to look up. + /// The array of column names. + public static ImmutableArray GetNames(this IDataRecord record, IEnumerable ordinals) + { + if (record is null) throw new ArgumentNullException(nameof(record)); + Contract.EndContractBlock(); + + return ordinals.Select(o => record.GetName(o)).ToImmutableArray(); + } + + /// + /// Returns the (name,ordinal) mapping for current result set. + /// + /// The reader to get column names from. + /// An enumerable of the mappings. + public static IEnumerable<(string Name, int Ordinal)> OrdinalMapping(this IDataRecord record) + => record.ColumnNames().Select((n, o) => (Name: n, Ordinal: o)); + + /// + /// Returns an array of name to ordinal mappings. + /// + /// The to query the ordinals from. + /// The requested column names. + /// An enumerable of the mappings. + public static IEnumerable<(string Name, int Ordinal)> OrdinalMapping(this IDataRecord record, IEnumerable columnNames) + => columnNames.Select(n => + { + // Does do a case-insensitive search after a case-sensitive one. + // https://docs.microsoft.com/en-us/dotnet/api/system.data.idatarecord.getordinal + int ordinal = record.GetOrdinal(n); + string name = record.GetName(ordinal); // Get actual casing. + return (name, ordinal); + }); + + /// + /// Returns an array of name to ordinal mappings. + /// + /// The to query the ordinals from. + /// The requested column names. + /// If true, will order the results by ordinal ascending. + /// An enumerable of the mappings. + public static IEnumerable<(string Name, int Ordinal)> OrdinalMapping(this IDataRecord record, IEnumerable columnNames, bool sort) + => sort + ? OrdinalMapping(record, columnNames).OrderBy(m => m.Ordinal) + : OrdinalMapping(record, columnNames); + + /// + /// Returns an array of name to ordinal mappings. + /// + /// The to query the ordinals from. + /// The requested column names. + /// If true, will order the results by ordinal ascending. + public static (string Name, int Ordinal)[] GetOrdinalMapping(this IDataRecord record, IEnumerable columnNames, bool sort = false) + { + if (columnNames is ICollection cn && cn.Count == 0) + return []; + + try + { + return record + .OrdinalMapping(columnNames, sort) + .ToArray(); + } + catch (IndexOutOfRangeException ex) + { + var mismatch = new HashSet(columnNames); + mismatch.ExceptWith(record.GetNames()); + + // Columns not mapped correctly. Report all columns that are mismatched/missing. + throw new IndexOutOfRangeException($"Invalid columns: {string.Join(", ", mismatch.OrderBy(c => c))}", ex); + } + } /// /// Produces an array of values based upon their ordinal positions. /// /// public static IEnumerable EnumerateValues(this IDataRecord record) - { + { return record is null - ? throw new ArgumentNullException(nameof(record)) - : EnumerateValuesCore(record); + ? throw new ArgumentNullException(nameof(record)) + : EnumerateValuesCore(record); static IEnumerable EnumerateValuesCore(IDataRecord record) { - var count = record.FieldCount; - for (var i = 0; i < count; i++) + int count = record.FieldCount; + for (int i = 0; i < count; i++) yield return record.GetValue(i); } } - /// - /// Produces a selective set of column values based upon the desired ordinal positions. - /// - /// The to query. - /// The set of ordinals to query. - /// An enumerable of values matching the ordinal positions requested. - public static IEnumerable EnumerateValuesFromOrdinals(this IDataRecord record, IEnumerable ordinals) - { + /// + /// Produces a selective set of column values based upon the desired ordinal positions. + /// + /// The to query. + /// The set of ordinals to query. + /// An enumerable of values matching the ordinal positions requested. + public static IEnumerable EnumerateValuesFromOrdinals(this IDataRecord record, IEnumerable ordinals) + { return record is null ? throw new ArgumentNullException(nameof(record)) : ordinals is null - ? throw new ArgumentNullException(nameof(ordinals)) - : EnumerateValuesFromOrdinalsCore(record, ordinals); + ? throw new ArgumentNullException(nameof(ordinals)) + : EnumerateValuesFromOrdinalsCore(record, ordinals); static IEnumerable EnumerateValuesFromOrdinalsCore(IDataRecord record, IEnumerable ordinals) { - foreach (var i in ordinals) + foreach (int i in ordinals) yield return record.GetValue(i); } } - /// - public static IEnumerable EnumerateValuesFromOrdinals(this IDataRecord record, IList ordinals) - { + /// + public static IEnumerable EnumerateValuesFromOrdinals(this IDataRecord record, IList ordinals) + { return record is null ? throw new ArgumentNullException(nameof(record)) : ordinals is null - ? throw new ArgumentNullException(nameof(ordinals)) - : EnumerateValuesFromOrdinalsCore(record, ordinals); + ? throw new ArgumentNullException(nameof(ordinals)) + : EnumerateValuesFromOrdinalsCore(record, ordinals); static IEnumerable EnumerateValuesFromOrdinalsCore(IDataRecord record, IList ordinals) { // Avoid creating an another enumerator if possible. - var count = ordinals.Count; - for (var i = 0; i < count; i++) + int count = ordinals.Count; + for (int i = 0; i < count; i++) yield return record.GetValue(ordinals[i]); } } @@ -244,7 +236,7 @@ static IEnumerable EnumerateValuesFromOrdinalsCore(IDataRecord record, I /// The remaining set of ordinals to query. /// public static IEnumerable EnumerateValuesFromOrdinals(this IDataRecord record, int firstOrdinal, params int[] remainingOrdinals) - { + { return record is null ? throw new ArgumentNullException(nameof(record)) : EnumerateValuesFromOrdinalsCore(record, firstOrdinal, remainingOrdinals); @@ -252,8 +244,8 @@ public static IEnumerable EnumerateValuesFromOrdinals(this IDataRecord r static IEnumerable EnumerateValuesFromOrdinalsCore(IDataRecord record, int firstOrdinal, int[] remainingOrdinals) { yield return record.GetValue(firstOrdinal); - var len = remainingOrdinals.Length; - for (var i = 0; i < len; i++) + int len = remainingOrdinals.Length; + for (int i = 0; i < len; i++) yield return record.GetValue(remainingOrdinals[i]); } } @@ -263,127 +255,127 @@ static IEnumerable EnumerateValuesFromOrdinalsCore(IDataRecord record, i /// The target to store the values. /// The provided span, updated with values matching the ordinal positions requested. /// - public static Span GetValuesFromOrdinals(this IDataRecord record, in ReadOnlySpan ordinals, Span values) - { - if (record is null) throw new ArgumentNullException(nameof(record)); - Contract.EndContractBlock(); - - var len = ordinals.Length; - for (var i = 0; i < len; i++) - values[i] = record.GetValue(ordinals[i]); - return values; - } + public static Span GetValuesFromOrdinals(this IDataRecord record, ReadOnlySpan ordinals, Span values) + { + if (record is null) throw new ArgumentNullException(nameof(record)); + Contract.EndContractBlock(); + + int len = ordinals.Length; + for (int i = 0; i < len; i++) + values[i] = record.GetValue(ordinals[i]); + return values; + } /// The provided list, updated with values matching the ordinal positions requested. - /// + /// public static TList GetValuesFromOrdinals(this IDataRecord record, IList ordinals, TList values) - where TList : IList - { - if (record is null) throw new ArgumentNullException(nameof(record)); - if (ordinals is null) throw new ArgumentNullException(nameof(ordinals)); - Contract.EndContractBlock(); - - var count = ordinals.Count; - for (var i = 0; i < count; i++) - values[i] = record.GetValue(ordinals[i]); - return values; - } + where TList : IList + { + if (record is null) throw new ArgumentNullException(nameof(record)); + if (ordinals is null) throw new ArgumentNullException(nameof(ordinals)); + Contract.EndContractBlock(); + + int count = ordinals.Count; + for (int i = 0; i < count; i++) + values[i] = record.GetValue(ordinals[i]); + return values; + } /// An array of values matching the ordinal positions requested. - /// + /// public static object[] GetValuesFromOrdinals(this IDataRecord record, IList ordinals) - { - if (record is null) throw new ArgumentNullException(nameof(record)); - if (ordinals is null) throw new ArgumentNullException(nameof(ordinals)); - Contract.EndContractBlock(); - - var count = ordinals.Count; - if (count == 0) return Array.Empty(); - - var values = new object[count]; - for (var i = 0; i < count; i++) - values[i] = record.GetValue(ordinals[i]); - return values; - } - - /// - /// Returns an enumerable of name to ordinal mappings. - /// - /// The to query the ordinals from. - /// The requested column names. - /// If true, will order the results by ordinal ascending. - /// The enumerable of name to ordinal mappings. - public static IEnumerable<(string Name, int Ordinal)> MatchingOrdinals(this IDataRecord record, IEnumerable columnNames, bool sort = false) - { - // Normalize the requested column names to be lowercase. - columnNames = columnNames.Select(c - => string.IsNullOrWhiteSpace(c) + { + if (record is null) throw new ArgumentNullException(nameof(record)); + if (ordinals is null) throw new ArgumentNullException(nameof(ordinals)); + Contract.EndContractBlock(); + + int count = ordinals.Count; + if (count == 0) return []; + + object[] values = new object[count]; + for (int i = 0; i < count; i++) + values[i] = record.GetValue(ordinals[i]); + return values; + } + + /// + /// Returns an enumerable of name to ordinal mappings. + /// + /// The to query the ordinals from. + /// The requested column names. + /// If true, will order the results by ordinal ascending. + /// The enumerable of name to ordinal mappings. + public static IEnumerable<(string Name, int Ordinal)> MatchingOrdinals(this IDataRecord record, IEnumerable columnNames, bool sort = false) + { + // Normalize the requested column names to be lowercase. + columnNames = columnNames.Select(c + => string.IsNullOrWhiteSpace(c) ? throw new ArgumentException("Column names cannot be null or whitespace only.") : c.ToUpperInvariant()); - var actual = record.OrdinalMapping(); - if (sort) - { - var requested = new HashSet(columnNames); - // Return actual values based upon if their lower-case counterparts exist in the requested. - return actual - .Where(m => requested.Contains(m.Name.ToUpperInvariant())); - } - else - { - // Create a map of lower-case keys to actual. - var actualColumns = actual.ToDictionary(m => m.Name.ToUpperInvariant(), m => m); - return columnNames - .Where(c => actualColumns.ContainsKey(c)) // Select lower case column names if they exist in the dictionary. - .Select(c => actualColumns[c]); // Then select the actual values based upon that key. - } - } - - /// - /// Returns an array of name to ordinal mappings. - /// - /// The to query the ordinals from. - /// The requested column names. - /// If true, will order the results by ordinal ascending. - /// The array of name to ordinal mappings. - public static (string Name, int Ordinal)[] GetMatchingOrdinals(this IDataRecord record, IEnumerable columnNames, bool sort = false) - => MatchingOrdinals(record, columnNames, sort).ToArray(); - - /// - /// Returns all the data type names for the columns of current result set. - /// - /// The reader to get data type names from. - /// The enumerable of data type names. - public static IEnumerable DataTypeNames(this IDataRecord record) - { + IEnumerable<(string Name, int Ordinal)> actual = record.OrdinalMapping(); + if (sort) + { + var requested = new HashSet(columnNames); + // Return actual values based upon if their lower-case counterparts exist in the requested. + return actual + .Where(m => requested.Contains(m.Name.ToUpperInvariant())); + } + else + { + // Create a map of lower-case keys to actual. + var actualColumns = actual.ToDictionary(m => m.Name.ToUpperInvariant(), m => m); + return columnNames + .Where(c => actualColumns.ContainsKey(c)) // Select lower case column names if they exist in the dictionary. + .Select(c => actualColumns[c]); // Then select the actual values based upon that key. + } + } + + /// + /// Returns an array of name to ordinal mappings. + /// + /// The to query the ordinals from. + /// The requested column names. + /// If true, will order the results by ordinal ascending. + /// The array of name to ordinal mappings. + public static (string Name, int Ordinal)[] GetMatchingOrdinals(this IDataRecord record, IEnumerable columnNames, bool sort = false) + => MatchingOrdinals(record, columnNames, sort).ToArray(); + + /// + /// Returns all the data type names for the columns of current result set. + /// + /// The reader to get data type names from. + /// The enumerable of data type names. + public static IEnumerable DataTypeNames(this IDataRecord record) + { return record is null - ? throw new ArgumentNullException(nameof(record)) - : DataTypeNamesCore(record); + ? throw new ArgumentNullException(nameof(record)) + : DataTypeNamesCore(record); static IEnumerable DataTypeNamesCore(IDataRecord record) { - var fieldCount = record.FieldCount; - for (var i = 0; i < fieldCount; i++) + int fieldCount = record.FieldCount; + for (int i = 0; i < fieldCount; i++) yield return record.GetDataTypeName(i); } } - /// - /// Returns all the data type names for the columns of current result set. - /// - /// The reader to get data type names from. - /// The array of data type names. - public static string[] GetDataTypeNames(this IDataRecord record) - { - if (record is null) throw new ArgumentNullException(nameof(record)); - Contract.EndContractBlock(); - - var fieldCount = record.FieldCount; - var results = new string[fieldCount]; - for (var i = 0; i < fieldCount; i++) - results[i] = record.GetDataTypeName(i); - return results; - } + /// + /// Returns all the data type names for the columns of current result set. + /// + /// The reader to get data type names from. + /// The array of data type names. + public static string[] GetDataTypeNames(this IDataRecord record) + { + if (record is null) throw new ArgumentNullException(nameof(record)); + Contract.EndContractBlock(); + + int fieldCount = record.FieldCount; + string[] results = new string[fieldCount]; + for (int i = 0; i < fieldCount; i++) + results[i] = record.GetDataTypeName(i); + return results; + } /// /// Returns the specified column data of as a Dictionary. @@ -393,10 +385,10 @@ public static string[] GetDataTypeNames(this IDataRecord record) /// The column ids and resultant names to query. /// The resultant Dictionary of values. public static Dictionary ToDictionary(this IDataRecord record, IEnumerable> columnMap) - => columnMap - .ToDictionary( - c => c.Value, - c => CoreExtensions.DBNullValueToNull(record.GetValue(c.Key))); + => columnMap + .ToDictionary( + c => c.Value, + c => CoreExtensions.DBNullValueToNull(record.GetValue(c.Key))); /// /// Returns the specified column data of as a Dictionary. @@ -406,10 +398,10 @@ public static string[] GetDataTypeNames(this IDataRecord record) /// The column ids and resultant names to query. /// The resultant Dictionary of values. public static Dictionary ToDictionary(this IDataRecord record, IEnumerable<(string Name, int Ordinal)> ordinalMapping) - => ordinalMapping - .ToDictionary( - c => c.Name, - c => CoreExtensions.DBNullValueToNull(record.GetValue(c.Ordinal))); + => ordinalMapping + .ToDictionary( + c => c.Name, + c => CoreExtensions.DBNullValueToNull(record.GetValue(c.Ordinal))); /// /// Returns the specified column data of as a Dictionary. @@ -419,20 +411,21 @@ public static string[] GetDataTypeNames(this IDataRecord record) /// The column ids and resultant names to query. /// The resultant Dictionary of values. public static Dictionary ToDictionary(this IDataRecord record, IList<(string Name, int Ordinal)> ordinalMapping) - { - if (record is null) throw new ArgumentNullException(nameof(record)); - if (ordinalMapping is null) throw new ArgumentNullException(nameof(ordinalMapping)); - Contract.EndContractBlock(); - - var e = new Dictionary(); - var count = ordinalMapping.Count; - for (var i = 0; i < count; i++) - { - var (name, ordinal) = ordinalMapping[i]; - e.Add(name, CoreExtensions.DBNullValueToNull(record[ordinal])); - } - return e; - } + { + if (record is null) throw new ArgumentNullException(nameof(record)); + if (ordinalMapping is null) throw new ArgumentNullException(nameof(ordinalMapping)); + Contract.EndContractBlock(); + + var e = new Dictionary(); + int count = ordinalMapping.Count; + for (int i = 0; i < count; i++) + { + (string name, int ordinal) = ordinalMapping[i]; + e.Add(name, CoreExtensions.DBNullValueToNull(record[ordinal])); + } + + return e; + } /// /// Returns the specified column data of as a Dictionary. @@ -441,20 +434,21 @@ public static string[] GetDataTypeNames(this IDataRecord record) /// The to extract values from. /// The column ids and resultant names to query. /// The resultant Dictionary of values. - public static Dictionary ToDictionary(this IDataRecord record, in ReadOnlySpan<(string Name, int Ordinal)> ordinalMapping) - { - if (record is null) throw new ArgumentNullException(nameof(record)); - Contract.EndContractBlock(); - - var e = new Dictionary(); - var count = ordinalMapping.Length; - for (var i = 0; i < count; i++) - { - var (name, ordinal) = ordinalMapping[i]; - e.Add(name, CoreExtensions.DBNullValueToNull(record[ordinal])); - } - return e; - } + public static Dictionary ToDictionary(this IDataRecord record, ReadOnlySpan<(string Name, int Ordinal)> ordinalMapping) + { + if (record is null) throw new ArgumentNullException(nameof(record)); + Contract.EndContractBlock(); + + var e = new Dictionary(); + int count = ordinalMapping.Length; + for (int i = 0; i < count; i++) + { + (string name, int ordinal) = ordinalMapping[i]; + e.Add(name, CoreExtensions.DBNullValueToNull(record[ordinal])); + } + + return e; + } /// /// Returns the specified column data of as a Dictionary. @@ -464,16 +458,16 @@ public static string[] GetDataTypeNames(this IDataRecord record) /// The column names to query. /// The resultant Dictionary of values. public static Dictionary ToDictionary(this IDataRecord record, IEnumerable columnNames) - { - if (record is null) throw new ArgumentNullException(nameof(record)); - if (columnNames is null) throw new ArgumentNullException(nameof(columnNames)); - Contract.EndContractBlock(); - - var e = new Dictionary(); - foreach (var name in columnNames) - e.Add(name, CoreExtensions.DBNullValueToNull(record[name])); - return e; - } + { + if (record is null) throw new ArgumentNullException(nameof(record)); + if (columnNames is null) throw new ArgumentNullException(nameof(columnNames)); + Contract.EndContractBlock(); + + var e = new Dictionary(); + foreach (string name in columnNames) + e.Add(name, CoreExtensions.DBNullValueToNull(record[name])); + return e; + } /// /// Returns the specified column data of as a Dictionary. @@ -483,20 +477,21 @@ public static string[] GetDataTypeNames(this IDataRecord record) /// The column names to query. /// The resultant Dictionary of values. public static Dictionary ToDictionary(this IDataRecord record, IList columnNames) - { - if (record is null) throw new ArgumentNullException(nameof(record)); - if (columnNames is null) throw new ArgumentNullException(nameof(columnNames)); - Contract.EndContractBlock(); - - var e = new Dictionary(); - var count = columnNames.Count; - for (var i = 0; i < count; i++) - { - var name = columnNames[i]; - e.Add(name, CoreExtensions.DBNullValueToNull(record[name])); - } - return e; - } + { + if (record is null) throw new ArgumentNullException(nameof(record)); + if (columnNames is null) throw new ArgumentNullException(nameof(columnNames)); + Contract.EndContractBlock(); + + var e = new Dictionary(); + int count = columnNames.Count; + for (int i = 0; i < count; i++) + { + string name = columnNames[i]; + e.Add(name, CoreExtensions.DBNullValueToNull(record[name])); + } + + return e; + } /// /// Returns the specified column data of as a Dictionary. @@ -505,18 +500,19 @@ public static string[] GetDataTypeNames(this IDataRecord record) /// The to extract values from. /// The column names to query. /// The resultant Dictionary of values. - public static Dictionary ToDictionary(this IDataRecord record, in ReadOnlySpan columnNames) - { - if (record is null) throw new ArgumentNullException(nameof(record)); - Contract.EndContractBlock(); - - var e = new Dictionary(); - var count = columnNames.Length; - for (var i = 0; i < count; i++) - { - var name = columnNames[i]; - e.Add(name, CoreExtensions.DBNullValueToNull(record[name])); - } - return e; - } + public static Dictionary ToDictionary(this IDataRecord record, ReadOnlySpan columnNames) + { + if (record is null) throw new ArgumentNullException(nameof(record)); + Contract.EndContractBlock(); + + var e = new Dictionary(); + int count = columnNames.Length; + for (int i = 0; i < count; i++) + { + string name = columnNames[i]; + e.Add(name, CoreExtensions.DBNullValueToNull(record[name])); + } + + return e; + } } diff --git a/Source/Core/Extensions/IExecuteReader.cs b/Source/Core/Extensions/IExecuteReader.cs index 777df71..867efa3 100644 --- a/Source/Core/Extensions/IExecuteReader.cs +++ b/Source/Core/Extensions/IExecuteReader.cs @@ -1,549 +1,540 @@ -using Open.Database.Extensions.Core; -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Data; -using System.Data.Common; -using System.Diagnostics.Contracts; -using System.Linq; -using System.Threading.Tasks; - -namespace Open.Database.Extensions; +namespace Open.Database.Extensions; /// /// Core non-DB-specific extensions for retrieving data from a command using best practices. /// public static class IExecuteReaderExtensions { - /// - /// Iterates a reader on a command with a handler function. - /// - /// The IExecuteReader to iterate. - /// The handler function for each IDataRecord. - /// The behavior to use with the data reader. - public static void IterateReader(this IExecuteReader command, Action handler, CommandBehavior behavior = CommandBehavior.Default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (handler is null) throw new ArgumentNullException(nameof(handler)); - Contract.EndContractBlock(); - - command.ExecuteReader( - reader => reader.ForEach(handler), - behavior | CommandBehavior.SingleResult); - } - - /// - /// Iterates a reader on a command while the handler function returns true. - /// - /// The IExecuteReader to iterate. - /// The handler function for each IDataRecord. - /// The behavior to use with the data reader. - public static void IterateReaderWhile(this IExecuteReader command, Func handler, CommandBehavior behavior = CommandBehavior.Default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (handler is null) throw new ArgumentNullException(nameof(handler)); - Contract.EndContractBlock(); - - command.ExecuteReader( - reader => reader.IterateWhile(handler), - behavior | CommandBehavior.SingleResult); - } - - /// - /// Iterates a reader on a command with a handler function. - /// - /// The IExecuteReader to iterate. - /// The handler function for each IDataRecord. - /// The behavior to use with the data reader. - public static ValueTask IterateReaderAsync(this IExecuteReaderAsync command, Action handler, CommandBehavior behavior = CommandBehavior.Default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (handler is null) throw new ArgumentNullException(nameof(handler)); - Contract.EndContractBlock(); - - return command.ExecuteReaderAsync( - reader => - { - if (reader is DbDataReader r) - return r.ForEachAsync(handler, command.UseAsyncRead, command.CancellationToken); - - reader.ForEach(handler, true, command.CancellationToken); - return new ValueTask(); - }, - behavior | CommandBehavior.SingleResult); - } - - /// - /// Iterates a reader on a command while the handler function returns true. - /// - /// The IExecuteReader to iterate. - /// The handler function for each IDataRecord. - /// The behavior to use with the data reader. - public static ValueTask IterateReaderWhileAsync(this IExecuteReaderAsync command, Func handler, CommandBehavior behavior = CommandBehavior.Default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (handler is null) throw new ArgumentNullException(nameof(handler)); - Contract.EndContractBlock(); - - return command.ExecuteReaderAsync( - reader => - { - if (reader is DbDataReader r) - return r.IterateWhileAsync(handler, command.UseAsyncRead, command.CancellationToken); - - reader.IterateWhile(handler, true, command.CancellationToken); - return new ValueTask(); - }, - behavior | CommandBehavior.SingleResult); - } - - /// - /// Iterates a reader on a command while the handler function returns true. - /// - /// The IExecuteReader to iterate. - /// The handler function for each IDataRecord. - /// The behavior to use with the data reader. - public static ValueTask IterateReaderWhileAsync(this IExecuteReaderAsync command, Func> handler, CommandBehavior behavior = CommandBehavior.Default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (handler is null) throw new ArgumentNullException(nameof(handler)); - Contract.EndContractBlock(); - - return command.ExecuteReaderAsync( - reader => reader.IterateWhileAsync(handler, command.CancellationToken), - behavior | CommandBehavior.SingleResult); - } - - /// - /// Executes a reader on a command with a transform function. - /// - /// The return type of the transform function applied to each record. - /// The type returned by the selector. - /// The IExecuteReader to iterate. - /// The transform function for each IDataRecord. - /// Provides an IEnumerable<TEntity> to select individual results by. - /// The behavior to use with the data reader. - /// The result of the transform. - public static TResult IterateReader( - this IExecuteReader command, - Func transform, - Func, TResult> selector, CommandBehavior behavior = CommandBehavior.Default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - return command.ExecuteReader( - reader => selector(reader.Select(transform)), - behavior | CommandBehavior.SingleResult); - } - - /// - /// Iterates an IDataReader and returns the first result through a transform function. Throws if none. - /// - /// The return type of the transform function. - /// The IExecuteReader to iterate. - /// The transform function to process each IDataRecord. - /// The value from the transform. - public static T First(this IExecuteReader command, Func transform) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - return command.ExecuteReader( - reader => reader.Select(transform).First(), - CommandBehavior.SingleRow | CommandBehavior.SingleResult); - } - - /// - /// Iterates an IDataReader and returns the first result through a transform function. Returns default(T) if none. - /// - /// The return type of the transform function. - /// The IExecuteReader to iterate. - /// The transform function to process each IDataRecord. - /// The value from the transform. - public static T FirstOrDefault(this IExecuteReader command, Func transform) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - return command.ExecuteReader( - reader => reader.Select(transform).FirstOrDefault(), - CommandBehavior.SingleRow | CommandBehavior.SingleResult); - } + /// + /// Iterates a reader on a command with a handler function. + /// + /// The IExecuteReader to iterate. + /// The handler function for each IDataRecord. + /// The behavior to use with the data reader. + public static void IterateReader(this IExecuteReader command, Action handler, CommandBehavior behavior = CommandBehavior.Default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (handler is null) throw new ArgumentNullException(nameof(handler)); + Contract.EndContractBlock(); + + command.ExecuteReader( + reader => reader.ForEach(handler), + behavior | CommandBehavior.SingleResult); + } /// - /// Iterates a IDataReader and returns the first result through a transform function. Throws if none or more than one entry. + /// Iterates a reader on a command while the handler function returns true. + /// + /// The IExecuteReader to iterate. + /// The handler function for each IDataRecord. + /// The behavior to use with the data reader. + public static void IterateReaderWhile(this IExecuteReader command, Func handler, CommandBehavior behavior = CommandBehavior.Default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (handler is null) throw new ArgumentNullException(nameof(handler)); + Contract.EndContractBlock(); + + command.ExecuteReader( + reader => reader.IterateWhile(handler), + behavior | CommandBehavior.SingleResult); + } + + /// + /// Iterates a reader on a command with a handler function. + /// + /// The IExecuteReader to iterate. + /// The handler function for each IDataRecord. + /// The behavior to use with the data reader. + public static ValueTask IterateReaderAsync(this IExecuteReaderAsync command, Action handler, CommandBehavior behavior = CommandBehavior.Default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (handler is null) throw new ArgumentNullException(nameof(handler)); + Contract.EndContractBlock(); + + return command.ExecuteReaderAsync( + reader => + { + if (reader is DbDataReader r) + return r.ForEachAsync(handler, command.UseAsyncRead, command.CancellationToken); + + reader.ForEach(handler, true, command.CancellationToken); + return new ValueTask(); + }, + behavior | CommandBehavior.SingleResult); + } + + /// + /// Iterates a reader on a command while the handler function returns true. + /// + /// The IExecuteReader to iterate. + /// The handler function for each IDataRecord. + /// The behavior to use with the data reader. + public static ValueTask IterateReaderWhileAsync(this IExecuteReaderAsync command, Func handler, CommandBehavior behavior = CommandBehavior.Default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (handler is null) throw new ArgumentNullException(nameof(handler)); + Contract.EndContractBlock(); + + return command.ExecuteReaderAsync( + reader => + { + if (reader is DbDataReader r) + return r.IterateWhileAsync(handler, command.UseAsyncRead, command.CancellationToken); + + reader.IterateWhile(handler, true, command.CancellationToken); + return new ValueTask(); + }, + behavior | CommandBehavior.SingleResult); + } + + /// + /// Iterates a reader on a command while the handler function returns true. + /// + /// The IExecuteReader to iterate. + /// The handler function for each IDataRecord. + /// The behavior to use with the data reader. + public static ValueTask IterateReaderWhileAsync(this IExecuteReaderAsync command, Func> handler, CommandBehavior behavior = CommandBehavior.Default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (handler is null) throw new ArgumentNullException(nameof(handler)); + Contract.EndContractBlock(); + + return command.ExecuteReaderAsync( + reader => reader.IterateWhileAsync(handler, command.CancellationToken), + behavior | CommandBehavior.SingleResult); + } + + /// + /// Executes a reader on a command with a transform function. + /// + /// The return type of the transform function applied to each record. + /// The type returned by the selector. + /// The IExecuteReader to iterate. + /// The transform function for each IDataRecord. + /// Provides an IEnumerable<TEntity> to select individual results by. + /// The behavior to use with the data reader. + /// The result of the transform. + public static TResult IterateReader( + this IExecuteReader command, + Func transform, + Func, TResult> selector, CommandBehavior behavior = CommandBehavior.Default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + return command.ExecuteReader( + reader => selector(reader.Select(transform)), + behavior | CommandBehavior.SingleResult); + } + + /// + /// Iterates an IDataReader and returns the first result through a transform function. Throws if none. + /// + /// The return type of the transform function. + /// The IExecuteReader to iterate. + /// The transform function to process each IDataRecord. + /// The value from the transform. + public static T First(this IExecuteReader command, Func transform) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + return command.ExecuteReader( + reader => reader.Select(transform).First(), + CommandBehavior.SingleRow | CommandBehavior.SingleResult); + } + + /// + /// Iterates an IDataReader and returns the first result through a transform function. Returns default(T) if none. + /// + /// The return type of the transform function. + /// The IExecuteReader to iterate. + /// The transform function to process each IDataRecord. + /// The value from the transform. + public static T? FirstOrDefault(this IExecuteReader command, Func transform) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + return command.ExecuteReader( + reader => reader.Select(transform).FirstOrDefault(), + CommandBehavior.SingleRow | CommandBehavior.SingleResult); + } + + /// + /// Iterates a IDataReader and returns the first result through a transform function. + /// Throws if none or more than one entry. /// /// The return type of the transform function. /// The IExecuteReader to iterate. /// The transform function to process each IDataRecord. /// The value from the transform. public static T Single(this IExecuteReader command, Func transform) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - return command.ExecuteReader( - reader => reader.Select(transform).Single(), - CommandBehavior.SingleResult); - } - - /// - /// Iterates an IDataReader and returns the first result through a transform function. Returns default(T) if none. Throws if more than one entry. - /// - /// The return type of the transform function. - /// The IExecuteReader to iterate. - /// The transform function to process each IDataRecord. - /// The value from the transform. - public static T SingleOrDefault(this IExecuteReader command, Func transform) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - return command.ExecuteReader( - reader => reader.Select(transform).SingleOrDefault(), - CommandBehavior.SingleResult); - } - - /// - /// Iterates an IDataReader and returns the first number of results defined by the count. - /// - /// The return type of the transform function. - /// The IExecuteReader to iterate. - /// The maximum number of records to return. - /// The transform function to process each IDataRecord. - /// The results from the transform limited by the take count. - public static List Take(this IExecuteReader command, int count, Func transform) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - if (count < 0) throw new ArgumentOutOfRangeException(nameof(count), count, "Cannot be negative."); - Contract.EndContractBlock(); - - return count == 0 - ? new List() - : command.ExecuteReader( - reader => reader.Select(transform).Take(count).ToList(), - CommandBehavior.SingleResult); - } - - /// - /// Iterates an IDataReader and skips the first number of results defined by the count. - /// - /// The return type of the transform function. - /// The IExecuteReader to iterate. - /// The number of records to skip. - /// The transform function to process each IDataRecord. - /// The results from the transform after the skip count. - public static List Skip(this IExecuteReader command, int count, Func transform) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - if (count < 0) throw new ArgumentOutOfRangeException(nameof(count), count, "Cannot be negative."); - Contract.EndContractBlock(); - - return command.ExecuteReader( - reader => count == 0 - ? reader.ToList(transform) - : reader.Select(transform).Skip(count).ToList(), - CommandBehavior.SingleResult); - } - - /// - /// Iterates an IDataReader and skips by the skip parameter returns the maximum remaining defined by the take parameter. - /// - /// The return type of the transform function. - /// The IExecuteReader to iterate. - /// The number of entries to skip before starting to take results. - /// The maximum number of records to return. - /// The transform function to process each IDataRecord. - /// The results from the skip, transform and take operation. - public static List SkipThenTake(this IExecuteReader command, int skip, int take, Func transform) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - if (skip < 0) throw new ArgumentOutOfRangeException(nameof(skip), skip, "Cannot be negative."); - if (take < 0) throw new ArgumentOutOfRangeException(nameof(take), take, "Cannot be negative."); - Contract.EndContractBlock(); - - return take == 0 - ? new List() - : command.ExecuteReader( - reader => reader.Select(transform).Skip(skip).Take(take).ToList(), - CommandBehavior.SingleResult); - } - - /// - /// Converts all IDataRecords into a list using a transform function. - /// - /// The expected return type. - /// The IExecuteReader to iterate. - /// The transform function. - /// The command behavior for once the command the reader is complete. - /// The list of transformed records. - public static List ToList(this IExecuteReader command, Func transform, CommandBehavior behavior = CommandBehavior.Default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - return command.ExecuteReader( - reader => reader.Select(transform).ToList(), - behavior | CommandBehavior.SingleResult); - } - - /// - /// Converts all IDataRecords into an array using a transform function. - /// - /// The expected return type. - /// The IExecuteReader to iterate. - /// The transform function. - /// The command behavior for once the command the reader is complete. - /// The array of transformed records. - public static T[] ToArray(this IExecuteReader command, Func transform, CommandBehavior behavior = CommandBehavior.Default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - return command.ExecuteReader( - reader => reader.Select(transform).ToArray(), - behavior | CommandBehavior.SingleResult); - } - - /// - /// Converts all IDataRecords into an immutable array using a transform function. - /// - /// The expected return type. - /// The IExecuteReader to iterate. - /// The transform function. - /// The command behavior for once the command the reader is complete. - /// The array of transformed records. - public static ImmutableArray ToImmutableArray(this IExecuteReader command, Func transform, CommandBehavior behavior = CommandBehavior.Default) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (transform is null) throw new ArgumentNullException(nameof(transform)); - Contract.EndContractBlock(); - - return command.ExecuteReader( - reader => reader.Select(transform).ToImmutableArray(), - behavior | CommandBehavior.SingleResult); - } - - /// - /// Iterates all records within the first result set using an IDataReader and returns the results. - /// values are left unchanged (retained). - /// - /// The IExecuteReader to iterate. - /// The QueryResult that contains all the results and the column mappings. - public static QueryResultQueue Retrieve(this IExecuteReader command) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - Contract.EndContractBlock(); - - return command.ExecuteReader( - reader => reader.Retrieve(), - CommandBehavior.SequentialAccess | CommandBehavior.SingleResult); - } - - /// - /// Iterates all records within the current result set using an IDataReader and returns the desired results. - /// values are left unchanged (retained). - /// - /// The IExecuteReader to iterate. - /// The ordinals to request from the reader for each record. - /// The QueryResult that contains all the results and the column mappings. - public static QueryResultQueue Retrieve(this IExecuteReader command, IEnumerable ordinals) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - Contract.EndContractBlock(); - - return command.ExecuteReader( - reader => reader.Retrieve(ordinals), - CommandBehavior.SingleResult); - } - - /// - /// Iterates all records within the current result set using an IDataReader and returns the desired results. - /// values are left unchanged (retained). - /// - /// The IExecuteReader to iterate. - /// The first ordinal to include in the request to the reader for each record. - /// The remaining ordinals to request from the reader for each record. - /// The QueryResult that contains all the results and the column mappings. - public static QueryResultQueue Retrieve(this IExecuteReader command, int n, params int[] others) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - Contract.EndContractBlock(); - - return command.ExecuteReader( - reader => reader.Retrieve(n, others), - CommandBehavior.SingleResult); - } - - /// - /// Iterates all records within the first result set using an IDataReader and returns the desired results as a list of Dictionaries containing only the specified column values. - /// values are left unchanged (retained). - /// - /// The IExecuteReader to iterate. - /// The column names to select. - /// The QueryResult that contains all the results and the column mappings. - public static QueryResultQueue Retrieve(this IExecuteReader command, IEnumerable columnNames) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (columnNames is null) throw new ArgumentNullException(nameof(columnNames)); - Contract.EndContractBlock(); - - return command.ExecuteReader( - reader => reader.Retrieve(columnNames), - CommandBehavior.SingleResult); - } - - /// - /// Iterates all records within the current result set using an IDataReader and returns the desired results. - /// values are left unchanged (retained). - /// - /// The IExecuteReader to iterate. - /// The first column name to include in the request to the reader for each record. - /// The remaining column names to request from the reader for each record. - /// The QueryResult that contains all the results and the column mappings. - public static QueryResultQueue Retrieve(this IExecuteReader command, string c, params string[] others) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - //if (c is null) throw new ArgumentNullException(nameof(c)); - //if (others.Any(e => e is null)) throw new ArgumentNullException(nameof(c)); - Contract.EndContractBlock(); - - return command.ExecuteReader( - reader => reader.Retrieve(c, others), - CommandBehavior.SingleResult); - } - - /// - /// Iterates each record and attempts to map the fields to type T. - /// Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. - /// - /// The IExecuteReader to iterate. - /// The model type to map the values to (using reflection). - /// An optional override map of field names to column names where the keys are the property names, and values are the column names. - /// The enumerable to pull the transformed results from. - public static IEnumerable Results(this IExecuteReader command, IEnumerable> fieldMappingOverrides) - where T : new() - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (fieldMappingOverrides is null) throw new ArgumentNullException(nameof(fieldMappingOverrides)); - Contract.EndContractBlock(); - - return command.ExecuteReader( - reader => reader.ResultsBuffered(fieldMappingOverrides), - CommandBehavior.SingleResult); - } - - /// - /// Iterates each record and attempts to map the fields to type T. - /// Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. - /// - /// The IExecuteReader to iterate. - /// The model type to map the values to (using reflection). - /// An optional override map of field names to column names where the keys are the property names, and values are the column names. - /// The enumerable to pull the transformed results from. - public static IEnumerable Results(this IExecuteReader command, IEnumerable<(string Field, string? Column)> fieldMappingOverrides) - where T : new() - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (fieldMappingOverrides is null) throw new ArgumentNullException(nameof(fieldMappingOverrides)); - Contract.EndContractBlock(); - - return command.ExecuteReader( - reader => reader.ResultsBuffered(fieldMappingOverrides), - CommandBehavior.SingleResult); - } - - /// - /// Iterates each record and attempts to map the fields to type T. - /// Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. - /// - /// The IExecuteReader to iterate. - /// The model type to map the values to (using reflection). - /// An optional override map of field names to column names where the keys are the property names, and values are the column names. - /// The enumerable to pull the transformed results from. - public static IEnumerable Results(this IExecuteReader command, params (string Field, string? Column)[] fieldMappingOverrides) - where T : new() - { - if (command is null) throw new ArgumentNullException(nameof(command)); - if (fieldMappingOverrides is null) throw new ArgumentNullException(nameof(fieldMappingOverrides)); - Contract.EndContractBlock(); - - return command.ExecuteReader( - reader => reader.ResultsBuffered(fieldMappingOverrides), - CommandBehavior.SingleResult); - } - - /// - /// Reads the first column from every record and returns the results as a list.. - /// values are converted to null. - /// - /// The IExecuteReader to iterate. - /// The list of transformed records. - public static IEnumerable FirstOrdinalResults(this IExecuteReader command) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - Contract.EndContractBlock(); - - return command.ExecuteReader( - reader => reader.FirstOrdinalResults(), - CommandBehavior.SequentialAccess | CommandBehavior.SingleResult); - } - - /// - /// Reads the first column from every record.. - /// values are converted to null. - /// - /// The IExecuteReader to iterate. - /// The enumerable of casted values. - public static IEnumerable FirstOrdinalResults(this IExecuteReader command) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - Contract.EndContractBlock(); - - return command.ExecuteReader( - reader => reader.FirstOrdinalResults(), - CommandBehavior.SequentialAccess | CommandBehavior.SingleResult); - } - - /// - /// Imports all data using an IDataReader into a DataTable. - /// - /// The IExecuteReader to iterate. - /// The resultant DataTable. - public static DataTable LoadTable(this IExecuteReader command) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - Contract.EndContractBlock(); - - return command.ExecuteReader( - reader => reader.ToDataTable(), - CommandBehavior.SequentialAccess | CommandBehavior.SingleResult); - } - - /// - /// Loads all data from a command through an IDataReader into a DataTables. - /// Calls .NextResult() to check for more results. - /// - /// The IExecuteReader to iterate. - /// The resultant list of DataTables. - public static List LoadTables(this IExecuteReader command) - { - if (command is null) throw new ArgumentNullException(nameof(command)); - Contract.EndContractBlock(); - - return command.ExecuteReader( - reader => reader.ToDataTables(), - CommandBehavior.SequentialAccess); - } + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + return command.ExecuteReader( + reader => reader.Select(transform).Single(), + CommandBehavior.SingleResult); + } + + /// + /// Iterates an IDataReader and returns the first result through a transform function. + /// Returns default(T) if none. + /// Throws if more than one entry. + /// + /// The return type of the transform function. + /// The IExecuteReader to iterate. + /// The transform function to process each IDataRecord. + /// The value from the transform. + public static T? SingleOrDefault(this IExecuteReader command, Func transform) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + return command.ExecuteReader( + reader => reader.Select(transform).SingleOrDefault(), + CommandBehavior.SingleResult); + } + + /// + /// Iterates an IDataReader and returns the first number of results defined by the count. + /// + /// The return type of the transform function. + /// The IExecuteReader to iterate. + /// The maximum number of records to return. + /// The transform function to process each IDataRecord. + /// The results from the transform limited by the take count. + public static List Take(this IExecuteReader command, int count, Func transform) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + if (count < 0) throw new ArgumentOutOfRangeException(nameof(count), count, "Cannot be negative."); + Contract.EndContractBlock(); + + return count == 0 ? [] : command.ExecuteReader( + reader => reader.Select(transform).Take(count).ToList(), + CommandBehavior.SingleResult); + } + + /// + /// Iterates an IDataReader and skips the first number of results defined by the count. + /// + /// The return type of the transform function. + /// The IExecuteReader to iterate. + /// The number of records to skip. + /// The transform function to process each IDataRecord. + /// The results from the transform after the skip count. + public static List Skip(this IExecuteReader command, int count, Func transform) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + if (count < 0) throw new ArgumentOutOfRangeException(nameof(count), count, "Cannot be negative."); + Contract.EndContractBlock(); + + return command.ExecuteReader( + reader => count == 0 + ? reader.ToList(transform) + : reader.Select(transform).Skip(count).ToList(), + CommandBehavior.SingleResult); + } + + /// + /// Iterates an IDataReader and skips by the skip parameter returns the maximum remaining defined by the take parameter. + /// + /// The return type of the transform function. + /// The IExecuteReader to iterate. + /// The number of entries to skip before starting to take results. + /// The maximum number of records to return. + /// The transform function to process each IDataRecord. + /// The results from the skip, transform and take operation. + public static List SkipThenTake(this IExecuteReader command, int skip, int take, Func transform) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + if (skip < 0) throw new ArgumentOutOfRangeException(nameof(skip), skip, "Cannot be negative."); + if (take < 0) throw new ArgumentOutOfRangeException(nameof(take), take, "Cannot be negative."); + Contract.EndContractBlock(); + + return take == 0 + ? [] + : command.ExecuteReader( + reader => reader.Select(transform).Skip(skip).Take(take).ToList(), + CommandBehavior.SingleResult); + } + + /// + /// Converts all IDataRecords into a list using a transform function. + /// + /// The expected return type. + /// The IExecuteReader to iterate. + /// The transform function. + /// The command behavior for once the command the reader is complete. + /// The list of transformed records. + public static List ToList(this IExecuteReader command, Func transform, CommandBehavior behavior = CommandBehavior.Default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + return command.ExecuteReader( + reader => reader.Select(transform).ToList(), + behavior | CommandBehavior.SingleResult); + } + + /// + /// Converts all IDataRecords into an array using a transform function. + /// + /// The expected return type. + /// The IExecuteReader to iterate. + /// The transform function. + /// The command behavior for once the command the reader is complete. + /// The array of transformed records. + public static T[] ToArray(this IExecuteReader command, Func transform, CommandBehavior behavior = CommandBehavior.Default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + return command.ExecuteReader( + reader => reader.Select(transform).ToArray(), + behavior | CommandBehavior.SingleResult); + } + + /// + /// Converts all IDataRecords into an immutable array using a transform function. + /// + /// The expected return type. + /// The IExecuteReader to iterate. + /// The transform function. + /// The command behavior for once the command the reader is complete. + /// The array of transformed records. + public static ImmutableArray ToImmutableArray(this IExecuteReader command, Func transform, CommandBehavior behavior = CommandBehavior.Default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + return command.ExecuteReader( + reader => reader.Select(transform).ToImmutableArray(), + behavior | CommandBehavior.SingleResult); + } + + /// + /// Iterates all records within the first result set using an IDataReader and returns the results. + /// values are left unchanged (retained). + /// + /// The IExecuteReader to iterate. + /// The QueryResult that contains all the results and the column mappings. + public static QueryResultQueue Retrieve(this IExecuteReader command) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + Contract.EndContractBlock(); + + return command.ExecuteReader( + reader => reader.Retrieve(), + CommandBehavior.SequentialAccess | CommandBehavior.SingleResult); + } + + /// + /// Iterates all records within the current result set using an IDataReader and returns the desired results. + /// values are left unchanged (retained). + /// + /// The IExecuteReader to iterate. + /// The ordinals to request from the reader for each record. + /// The QueryResult that contains all the results and the column mappings. + public static QueryResultQueue Retrieve(this IExecuteReader command, IEnumerable ordinals) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + Contract.EndContractBlock(); + + return command.ExecuteReader( + reader => reader.Retrieve(ordinals), + CommandBehavior.SingleResult); + } + + /// + /// Iterates all records within the current result set using an IDataReader and returns the desired results. + /// values are left unchanged (retained). + /// + /// The IExecuteReader to iterate. + /// The first ordinal to include in the request to the reader for each record. + /// The remaining ordinals to request from the reader for each record. + /// The QueryResult that contains all the results and the column mappings. + public static QueryResultQueue Retrieve(this IExecuteReader command, int n, params int[] others) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + Contract.EndContractBlock(); + + return command.ExecuteReader( + reader => reader.Retrieve(n, others), + CommandBehavior.SingleResult); + } + + /// + /// Iterates all records within the first result set using an IDataReader and returns the desired results as a list of Dictionaries containing only the specified column values. + /// values are left unchanged (retained). + /// + /// The IExecuteReader to iterate. + /// The column names to select. + /// The QueryResult that contains all the results and the column mappings. + public static QueryResultQueue Retrieve(this IExecuteReader command, IEnumerable columnNames) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (columnNames is null) throw new ArgumentNullException(nameof(columnNames)); + Contract.EndContractBlock(); + + return command.ExecuteReader( + reader => reader.Retrieve(columnNames), + CommandBehavior.SingleResult); + } + + /// + /// Iterates all records within the current result set using an IDataReader and returns the desired results. + /// values are left unchanged (retained). + /// + /// The IExecuteReader to iterate. + /// The first column name to include in the request to the reader for each record. + /// The remaining column names to request from the reader for each record. + /// The QueryResult that contains all the results and the column mappings. + public static QueryResultQueue Retrieve(this IExecuteReader command, string c, params string[] others) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + //if (c is null) throw new ArgumentNullException(nameof(c)); + //if (others.Any(e => e is null)) throw new ArgumentNullException(nameof(c)); + Contract.EndContractBlock(); + + return command.ExecuteReader( + reader => reader.Retrieve(c, others), + CommandBehavior.SingleResult); + } + + /// + /// Iterates each record and attempts to map the fields to type T. + /// Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. + /// + /// The IExecuteReader to iterate. + /// The model type to map the values to (using reflection). + /// An optional override map of field names to column names where the keys are the property names, and values are the column names. + /// The enumerable to pull the transformed results from. + public static IEnumerable Results(this IExecuteReader command, IEnumerable> fieldMappingOverrides) + where T : new() + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (fieldMappingOverrides is null) throw new ArgumentNullException(nameof(fieldMappingOverrides)); + Contract.EndContractBlock(); + + return command.ExecuteReader( + reader => reader.ResultsBuffered(fieldMappingOverrides), + CommandBehavior.SingleResult); + } + + /// + /// Iterates each record and attempts to map the fields to type T. + /// Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. + /// + /// The IExecuteReader to iterate. + /// The model type to map the values to (using reflection). + /// An optional override map of field names to column names where the keys are the property names, and values are the column names. + /// The enumerable to pull the transformed results from. + public static IEnumerable Results(this IExecuteReader command, IEnumerable<(string Field, string? Column)> fieldMappingOverrides) + where T : new() + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (fieldMappingOverrides is null) throw new ArgumentNullException(nameof(fieldMappingOverrides)); + Contract.EndContractBlock(); + + return command.ExecuteReader( + reader => reader.ResultsBuffered(fieldMappingOverrides), + CommandBehavior.SingleResult); + } + + /// + /// Iterates each record and attempts to map the fields to type T. + /// Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. + /// + /// The IExecuteReader to iterate. + /// The model type to map the values to (using reflection). + /// An optional override map of field names to column names where the keys are the property names, and values are the column names. + /// The enumerable to pull the transformed results from. + public static IEnumerable Results(this IExecuteReader command, params (string Field, string? Column)[] fieldMappingOverrides) + where T : new() + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (fieldMappingOverrides is null) throw new ArgumentNullException(nameof(fieldMappingOverrides)); + Contract.EndContractBlock(); + + return command.ExecuteReader( + reader => reader.ResultsBuffered(fieldMappingOverrides), + CommandBehavior.SingleResult); + } + + /// + /// Reads the first column from every record and returns the results as a list.. + /// values are converted to null. + /// + /// The IExecuteReader to iterate. + /// The list of transformed records. + public static IEnumerable FirstOrdinalResults(this IExecuteReader command) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + Contract.EndContractBlock(); + + return command.ExecuteReader( + reader => reader.FirstOrdinalResults(), + CommandBehavior.SequentialAccess | CommandBehavior.SingleResult); + } + + /// + /// Reads the first column from every record.. + /// values are converted to null. + /// + /// The IExecuteReader to iterate. + /// The enumerable of casted values. + public static IEnumerable FirstOrdinalResults(this IExecuteReader command) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + Contract.EndContractBlock(); + + return command.ExecuteReader( + reader => reader.FirstOrdinalResults(), + CommandBehavior.SequentialAccess | CommandBehavior.SingleResult); + } + + /// + /// Imports all data using an IDataReader into a DataTable. + /// + /// The IExecuteReader to iterate. + /// The resultant DataTable. + public static DataTable LoadTable(this IExecuteReader command) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + Contract.EndContractBlock(); + + return command.ExecuteReader( + reader => reader.ToDataTable(), + CommandBehavior.SequentialAccess | CommandBehavior.SingleResult); + } + + /// + /// Loads all data from a command through an IDataReader into a DataTables. + /// Calls .NextResult() to check for more results. + /// + /// The IExecuteReader to iterate. + /// The resultant list of DataTables. + public static List LoadTables(this IExecuteReader command) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + Contract.EndContractBlock(); + + return command.ExecuteReader( + reader => reader.ToDataTables(), + CommandBehavior.SequentialAccess); + } } diff --git a/Source/Core/Extensions/Results.cs b/Source/Core/Extensions/Results.cs index 9f993c4..21343af 100644 --- a/Source/Core/Extensions/Results.cs +++ b/Source/Core/Extensions/Results.cs @@ -1,387 +1,380 @@ -using Open.Database.Extensions.Core; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; -using System.Diagnostics.Contracts; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Open.Database.Extensions; +namespace Open.Database.Extensions; public static partial class CoreExtensions { - /// - /// Iterates each record and attempts to map the fields to type T. - /// - /// The model type to map the values to (using reflection). - /// The IDataReader to read results from. - /// An optional override map of field names to column names where the keys are the property names, and values are the column names. - /// The enumerable to pull the transformed results from. - public static IEnumerable Results(this IDataReader reader, IEnumerable<(string Field, string? Column)>? fieldMappingOverrides) - where T : new() - { - if (reader is null) throw new System.ArgumentNullException(nameof(reader)); - Contract.EndContractBlock(); - - var x = new Transformer(fieldMappingOverrides); - return x.Results(reader); - } - - /// - /// Iterates each record and attempts to map the fields to type T. - /// - /// The model type to map the values to (using reflection). - /// The IDataReader to read results from. - /// An optional override map of field names to column names where the keys are the property names, and values are the column names. - /// The enumerable to pull the transformed results from. - public static IEnumerable Results(this IDataReader reader, params (string Field, string? Column)[] fieldMappingOverrides) - where T : new() - => Results(reader, fieldMappingOverrides as IEnumerable<(string Field, string? Column)>); - - /// - /// Iterates each record and attempts to map the fields to type T. - /// - /// The model type to map the values to (using reflection). - /// The IDataReader to read results from. - /// An optional override map of field names to column names where the keys are the property names, and values are the column names. - /// The enumerable to pull the transformed results from. - public static IEnumerable Results(this IDataReader reader, IEnumerable> fieldMappingOverrides) - where T : new() - => Results(reader, fieldMappingOverrides?.Select(kvp => (kvp.Key, kvp.Value))); - - /// - /// Iterates each record and attempts to map the fields to type T. - /// Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. - /// - /// The model type to map the values to (using reflection). - /// The IDataReader to read results from. - /// An optional override map of field names to column names where the keys are the property names, and values are the column names. - /// The enumerable to pull the transformed results from. - public static IEnumerable ResultsBuffered(this IDataReader reader, IEnumerable<(string Field, string? Column)>? fieldMappingOverrides) - where T : new() - { - if (reader is null) throw new System.ArgumentNullException(nameof(reader)); - Contract.EndContractBlock(); - - if (!reader.Read()) - return Enumerable.Empty(); - - var x = new Transformer(fieldMappingOverrides); - return x.ResultsBuffered(reader, true); - } - - /// - /// Iterates each record and attempts to map the fields to type T. - /// Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. - /// - /// The model type to map the values to (using reflection). - /// The IDataReader to read results from. - /// An optional override map of field names to column names where the keys are the property names, and values are the column names. - /// The enumerable to pull the transformed results from. - public static IEnumerable ResultsBuffered(this IDataReader reader, params (string Field, string? Column)[] fieldMappingOverrides) - where T : new() - => ResultsBuffered(reader, fieldMappingOverrides as IEnumerable<(string Field, string? Column)>); - - /// - /// Iterates each record and attempts to map the fields to type T. - /// Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. - /// - /// The model type to map the values to (using reflection). - /// The IDataReader to read results from. - /// An optional override map of field names to column names where the keys are the property names, and values are the column names. - /// The enumerable to pull the transformed results from. - public static IEnumerable ResultsBuffered(this IDataReader reader, IEnumerable> fieldMappingOverrides) - where T : new() - => ResultsBuffered(reader, fieldMappingOverrides?.Select(kvp => (kvp.Key, kvp.Value))); - - /// - /// Iterates each record and attempts to map the fields to type T. - /// Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. - /// - /// The model type to map the values to (using reflection). - /// The command to generate a reader from. - /// An optional override map of field names to column names where the keys are the property names, and values are the column names. - /// The enumerable to pull the transformed results from. - public static IEnumerable Results(this IDbCommand command, IEnumerable<(string Field, string? Column)>? fieldMappingOverrides) - where T : new() - { - if (command is null) throw new System.ArgumentNullException(nameof(command)); - Contract.EndContractBlock(); - - return command.ExecuteReader(reader => reader.ResultsBuffered(fieldMappingOverrides)); - } - - /// - /// Iterates each record and attempts to map the fields to type T. - /// Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. - /// - /// The model type to map the values to (using reflection). - /// The command to generate a reader from. - /// An optional override map of field names to column names where the keys are the property names, and values are the column names. - /// The enumerable to pull the transformed results from. - public static IEnumerable Results(this IDbCommand command, params (string Field, string? Column)[] fieldMappingOverrides) - where T : new() - => Results(command, fieldMappingOverrides as IEnumerable<(string Field, string? Column)>); - - /// - /// Iterates each record and attempts to map the fields to type T. - /// Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. - /// - /// The model type to map the values to (using reflection). - /// The command to generate a reader from. - /// An optional override map of field names to column names where the keys are the property names, and values are the column names. - /// The enumerable to pull the transformed results from. - public static IEnumerable Results(this IDbCommand command, IEnumerable> fieldMappingOverrides) - where T : new() - => Results(command, fieldMappingOverrides?.Select(kvp => (kvp.Key, kvp.Value))); -#if NETSTANDARD2_1 - /// - /// Asynchronously iterates each record and attempts to map the fields to type T. - /// - /// The model type to map the values to (using reflection). - /// The IDataReader to read results from. - /// An optional override map of field names to column names where the keys are the property names, and values are the column names. - /// An optional cancellation token. - /// The enumerable to pull the transformed results from. - public static IAsyncEnumerable ResultsAsync(this DbDataReader reader, IEnumerable<(string Field, string? Column)>? fieldMappingOverrides, CancellationToken cancellationToken = default) - where T : new() - { - if (reader is null) throw new System.ArgumentNullException(nameof(reader)); - Contract.EndContractBlock(); - - var x = new Transformer(fieldMappingOverrides); - return x.ResultsAsync(reader, cancellationToken); - } - - /// - /// Asynchronously iterates each record and attempts to map the fields to type T. - /// - /// The model type to map the values to (using reflection). - /// The IDataReader to read results from. - /// An optional override map of field names to column names where the keys are the property names, and values are the column names. - /// The enumerable to pull the transformed results from. - public static IAsyncEnumerable ResultsAsync(this DbDataReader reader, params (string Field, string? Column)[] fieldMappingOverrides) - where T : new() - => ResultsAsync(reader, fieldMappingOverrides as IEnumerable<(string Field, string? Column)>); - - /// - /// Asynchronously iterates each record and attempts to map the fields to type T. - /// - /// The model type to map the values to (using reflection). - /// The IDataReader to read results from. - /// The cancellation token. - /// An optional override map of field names to column names where the keys are the property names, and values are the column names. - /// The enumerable to pull the transformed results from. - public static IAsyncEnumerable ResultsAsync(this DbDataReader reader, CancellationToken cancellationToken, params (string Field, string? Column)[] fieldMappingOverrides) - where T : new() - => ResultsAsync(reader, fieldMappingOverrides as IEnumerable<(string Field, string? Column)>, cancellationToken); - - /// - /// Iterates each record and attempts to map the fields to type T. - /// - /// The model type to map the values to (using reflection). - /// The IDataReader to read results from. - /// An optional override map of field names to column names where the keys are the property names, and values are the column names. - /// An optional cancellation token. - /// The enumerable to pull the transformed results from. - public static IAsyncEnumerable ResultsAsync(this DbDataReader reader, IEnumerable> fieldMappingOverrides, CancellationToken cancellationToken = default) - where T : new() - => ResultsAsync(reader, fieldMappingOverrides?.Select(kvp => (kvp.Key, kvp.Value)), cancellationToken); + /// + /// Iterates each record and attempts to map the fields to type T. + /// + /// The model type to map the values to (using reflection). + /// The IDataReader to read results from. + /// An optional override map of field names to column names where the keys are the property names, and values are the column names. + /// The enumerable to pull the transformed results from. + public static IEnumerable Results(this IDataReader reader, IEnumerable<(string Field, string? Column)>? fieldMappingOverrides) + where T : new() + { + if (reader is null) throw new System.ArgumentNullException(nameof(reader)); + Contract.EndContractBlock(); + + var x = new Transformer(fieldMappingOverrides); + return x.Results(reader); + } + + /// + /// Iterates each record and attempts to map the fields to type T. + /// + /// The model type to map the values to (using reflection). + /// The IDataReader to read results from. + /// An optional override map of field names to column names where the keys are the property names, and values are the column names. + /// The enumerable to pull the transformed results from. + public static IEnumerable Results(this IDataReader reader, params (string Field, string? Column)[] fieldMappingOverrides) + where T : new() + => Results(reader, fieldMappingOverrides as IEnumerable<(string Field, string? Column)>); + + /// + /// Iterates each record and attempts to map the fields to type T. + /// + /// The model type to map the values to (using reflection). + /// The IDataReader to read results from. + /// An optional override map of field names to column names where the keys are the property names, and values are the column names. + /// The enumerable to pull the transformed results from. + public static IEnumerable Results(this IDataReader reader, IEnumerable> fieldMappingOverrides) + where T : new() + => Results(reader, fieldMappingOverrides?.Select(kvp => (kvp.Key, kvp.Value))); + + /// + /// Iterates each record and attempts to map the fields to type T. + /// Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. + /// + /// The model type to map the values to (using reflection). + /// The IDataReader to read results from. + /// An optional override map of field names to column names where the keys are the property names, and values are the column names. + /// The enumerable to pull the transformed results from. + public static IEnumerable ResultsBuffered(this IDataReader reader, IEnumerable<(string Field, string? Column)>? fieldMappingOverrides) + where T : new() + { + if (reader is null) throw new System.ArgumentNullException(nameof(reader)); + Contract.EndContractBlock(); + + if (!reader.Read()) + return Enumerable.Empty(); + + var x = new Transformer(fieldMappingOverrides); + return x.ResultsBuffered(reader, true); + } + + /// + /// Iterates each record and attempts to map the fields to type T. + /// Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. + /// + /// The model type to map the values to (using reflection). + /// The IDataReader to read results from. + /// An optional override map of field names to column names where the keys are the property names, and values are the column names. + /// The enumerable to pull the transformed results from. + public static IEnumerable ResultsBuffered(this IDataReader reader, params (string Field, string? Column)[] fieldMappingOverrides) + where T : new() + => ResultsBuffered(reader, fieldMappingOverrides as IEnumerable<(string Field, string? Column)>); + + /// + /// Iterates each record and attempts to map the fields to type T. + /// Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. + /// + /// The model type to map the values to (using reflection). + /// The IDataReader to read results from. + /// An optional override map of field names to column names where the keys are the property names, and values are the column names. + /// The enumerable to pull the transformed results from. + public static IEnumerable ResultsBuffered(this IDataReader reader, IEnumerable> fieldMappingOverrides) + where T : new() + => ResultsBuffered(reader, fieldMappingOverrides?.Select(kvp => (kvp.Key, kvp.Value))); + + /// + /// Iterates each record and attempts to map the fields to type T. + /// Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. + /// + /// The model type to map the values to (using reflection). + /// The command to generate a reader from. + /// An optional override map of field names to column names where the keys are the property names, and values are the column names. + /// The enumerable to pull the transformed results from. + public static IEnumerable Results(this IDbCommand command, IEnumerable<(string Field, string? Column)>? fieldMappingOverrides) + where T : new() + { + if (command is null) throw new System.ArgumentNullException(nameof(command)); + Contract.EndContractBlock(); + + return command.ExecuteReader(reader => reader.ResultsBuffered(fieldMappingOverrides)); + } + + /// + /// Iterates each record and attempts to map the fields to type T. + /// Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. + /// + /// The model type to map the values to (using reflection). + /// The command to generate a reader from. + /// An optional override map of field names to column names where the keys are the property names, and values are the column names. + /// The enumerable to pull the transformed results from. + public static IEnumerable Results(this IDbCommand command, params (string Field, string? Column)[] fieldMappingOverrides) + where T : new() + => Results(command, fieldMappingOverrides as IEnumerable<(string Field, string? Column)>); + + /// + /// Iterates each record and attempts to map the fields to type T. + /// Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. + /// + /// The model type to map the values to (using reflection). + /// The command to generate a reader from. + /// An optional override map of field names to column names where the keys are the property names, and values are the column names. + /// The enumerable to pull the transformed results from. + public static IEnumerable Results(this IDbCommand command, IEnumerable> fieldMappingOverrides) + where T : new() + => Results(command, fieldMappingOverrides?.Select(kvp => (kvp.Key, kvp.Value))); + +#if NETSTANDARD2_0 +#else + /// + /// Asynchronously iterates each record and attempts to map the fields to type T. + /// + /// The model type to map the values to (using reflection). + /// The IDataReader to read results from. + /// An optional override map of field names to column names where the keys are the property names, and values are the column names. + /// An optional cancellation token. + /// The enumerable to pull the transformed results from. + public static IAsyncEnumerable ResultsAsync(this DbDataReader reader, IEnumerable<(string Field, string? Column)>? fieldMappingOverrides, CancellationToken cancellationToken = default) + where T : new() + { + if (reader is null) throw new System.ArgumentNullException(nameof(reader)); + Contract.EndContractBlock(); + + var x = new Transformer(fieldMappingOverrides); + return x.ResultsAsync(reader, cancellationToken); + } + + /// + /// Asynchronously iterates each record and attempts to map the fields to type T. + /// + /// The model type to map the values to (using reflection). + /// The IDataReader to read results from. + /// An optional override map of field names to column names where the keys are the property names, and values are the column names. + /// The enumerable to pull the transformed results from. + public static IAsyncEnumerable ResultsAsync(this DbDataReader reader, params (string Field, string? Column)[] fieldMappingOverrides) + where T : new() + => ResultsAsync(reader, fieldMappingOverrides as IEnumerable<(string Field, string? Column)>); + + /// + /// Asynchronously iterates each record and attempts to map the fields to type T. + /// + /// The model type to map the values to (using reflection). + /// The IDataReader to read results from. + /// The cancellation token. + /// An optional override map of field names to column names where the keys are the property names, and values are the column names. + /// The enumerable to pull the transformed results from. + public static IAsyncEnumerable ResultsAsync(this DbDataReader reader, CancellationToken cancellationToken, params (string Field, string? Column)[] fieldMappingOverrides) + where T : new() + => ResultsAsync(reader, fieldMappingOverrides as IEnumerable<(string Field, string? Column)>, cancellationToken); + + /// + /// Iterates each record and attempts to map the fields to type T. + /// + /// The model type to map the values to (using reflection). + /// The IDataReader to read results from. + /// An optional override map of field names to column names where the keys are the property names, and values are the column names. + /// An optional cancellation token. + /// The enumerable to pull the transformed results from. + public static IAsyncEnumerable ResultsAsync(this DbDataReader reader, IEnumerable> fieldMappingOverrides, CancellationToken cancellationToken = default) + where T : new() + => ResultsAsync(reader, fieldMappingOverrides?.Select(kvp => (kvp.Key, kvp.Value)), cancellationToken); #endif - /// - /// Asynchronously returns all records and iteratively attempts to map the fields to type T. - /// - /// The model type to map the values to (using reflection). - /// The IDataReader to read results from. - /// An override map of field names to column names where the keys are the property names, and values are the column names. - /// If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. - /// Optional cancellation token. - /// A task containing the list of results. - public static async ValueTask> ResultsBufferedAsync(this DbDataReader reader, IEnumerable<(string Field, string? Column)>? fieldMappingOverrides, bool useReadAsync = true, CancellationToken cancellationToken = default) - where T : new() - { - if (reader is null) throw new System.ArgumentNullException(nameof(reader)); - Contract.EndContractBlock(); - - if (!await reader.ReadAsync(cancellationToken).ConfigureAwait(true)) - return Enumerable.Empty(); // else readStarted = true; - - var x = new Transformer(fieldMappingOverrides); - // Ignore missing columns. - var columns = reader.GetMatchingOrdinals(x.ColumnNames, true); - - return x.AsDequeueingEnumerable( - await RetrieveAsyncInternal( - Transformer.LocalPool, - reader, cancellationToken, - columns.Select(c => c.Ordinal), - columns.Select(c => c.Name), - readStarted: true, - useReadAsync: useReadAsync).ConfigureAwait(false), - Transformer.LocalPool); - } - - /// - /// Asynchronously returns all records and iteratively attempts to map the fields to type T. - /// - /// The model type to map the values to (using reflection). - /// The IDataReader to read results from. - /// An override map of field names to column names where the keys are the property names, and values are the column names. - /// The cancellation token. - /// A task containing the list of results. - public static ValueTask> ResultsBufferedAsync(this DbDataReader reader, IEnumerable<(string Field, string? Column)>? fieldMappingOverrides, CancellationToken cancellationToken) - where T : new() - => ResultsBufferedAsync(reader, fieldMappingOverrides, true, cancellationToken); - - /// - /// Asynchronously returns all records and iteratively attempts to map the fields to type T. - /// - /// The model type to map the values to (using reflection). - /// The IDataReader to read results from. - /// An override map of field names to column names where the keys are the property names, and values are the column names. - /// If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. - /// Optional cancellation token. - /// A task containing the list of results. - public static ValueTask> ResultsBufferedAsync(this DbDataReader reader, IEnumerable> fieldMappingOverrides, bool useReadAsync = true, CancellationToken cancellationToken = default) - where T : new() - => ResultsBufferedAsync(reader, fieldMappingOverrides?.Select(kvp => (kvp.Key, kvp.Value)), useReadAsync, cancellationToken); - - /// - /// Asynchronously returns all records and iteratively attempts to map the fields to type T. - /// - /// The model type to map the values to (using reflection). - /// The IDataReader to read results from. - /// An override map of field names to column names where the keys are the property names, and values are the column names. - /// Optional cancellation token. - /// A task containing the list of results. - public static ValueTask> ResultsBufferedAsync(this DbDataReader reader, IEnumerable> fieldMappingOverrides, CancellationToken cancellationToken) - where T : new() - => ResultsBufferedAsync(reader, fieldMappingOverrides, true, cancellationToken); - - /// - /// Asynchronously returns all records and iteratively attempts to map the fields to type T. - /// - /// The model type to map the values to (using reflection). - /// The IDataReader to read results from. - /// An override map of field names to column names where the keys are the property names, and values are the column names. - /// A task containing the list of results. - public static ValueTask> ResultsBufferedAsync(this DbDataReader reader, params (string Field, string? Column)[] fieldMappingOverrides) where T : new() - => ResultsBufferedAsync(reader, (IEnumerable<(string Field, string? Column)>)fieldMappingOverrides); - - /// - /// Asynchronously returns all records and iteratively attempts to map the fields to type T. - /// - /// The model type to map the values to (using reflection). - /// The IDataReader to read results from. - /// The cancellation token. - /// An override map of field names to column names where the keys are the property names, and values are the column names. - /// A task containing the list of results. - public static ValueTask> ResultsBufferedAsync(this DbDataReader reader, CancellationToken cancellationToken, params (string Field, string? Column)[] fieldMappingOverrides) - where T : new() - => ResultsBufferedAsync(reader, fieldMappingOverrides, cancellationToken); - - /// - /// Asynchronously returns all records and iteratively attempts to map the fields to type T. - /// - /// The model type to map the values to (using reflection). - /// The command to generate a reader from. - /// An override map of field names to column names where the keys are the property names, and values are the column names. - /// If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. - /// Optional cancellation token. - /// A task containing the list of results. - public static ValueTask> ResultsAsync(this DbCommand command, IEnumerable<(string Field, string? Column)>? fieldMappingOverrides, bool useReadAsync = true, CancellationToken cancellationToken = default) - where T : new() - => command.ExecuteReaderAsync(reader => ResultsBufferedAsync(reader, fieldMappingOverrides, useReadAsync, cancellationToken), CommandBehavior.SingleResult, cancellationToken); - - /// - /// Asynchronously returns all records and iteratively attempts to map the fields to type T. - /// - /// The model type to map the values to (using reflection). - /// The command to generate a reader from. - /// An override map of field names to column names where the keys are the property names, and values are the column names. - /// The cancellation token. - /// A task containing the list of results. - public static ValueTask> ResultsAsync(this DbCommand command, IEnumerable<(string Field, string? Column)>? fieldMappingOverrides, CancellationToken cancellationToken) - where T : new() - => ResultsAsync(command, fieldMappingOverrides, true, cancellationToken); - - /// - /// Asynchronously returns all records and iteratively attempts to map the fields to type T. - /// - /// The model type to map the values to (using reflection). - /// The command to generate a reader from. - /// An override map of field names to column names where the keys are the property names, and values are the column names. - /// If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. - /// Optional cancellation token. - /// A task containing the list of results. - public static ValueTask> ResultsAsync(this DbCommand command, IEnumerable> fieldMappingOverrides, bool useReadAsync = true, CancellationToken cancellationToken = default) - where T : new() - => ResultsAsync(command, fieldMappingOverrides?.Select(kvp => (kvp.Key, kvp.Value)), useReadAsync, cancellationToken); - - /// - /// Asynchronously returns all records and iteratively attempts to map the fields to type T. - /// - /// The model type to map the values to (using reflection). - /// The command to generate a reader from. - /// An override map of field names to column names where the keys are the property names, and values are the column names. - /// The cancellation token. - /// A task containing the list of results. - public static ValueTask> ResultsAsync(this DbCommand command, IEnumerable> fieldMappingOverrides, CancellationToken cancellationToken) - where T : new() - => ResultsAsync(command, fieldMappingOverrides, true, cancellationToken); - - /// - /// Asynchronously returns all records and iteratively attempts to map the fields to type T. - /// - /// The model type to map the values to (using reflection). - /// The command to generate a reader from. - /// An override map of field names to column names where the keys are the property names, and values are the column names. - /// A task containing the list of results. - public static ValueTask> ResultsAsync(this DbCommand command, params (string Field, string? Column)[] fieldMappingOverrides) where T : new() - => ResultsAsync(command, (IEnumerable<(string Field, string? Column)>)fieldMappingOverrides); - - /// - /// Asynchronously returns all records and iteratively attempts to map the fields to type T. - /// - /// The model type to map the values to (using reflection). - /// The command to generate a reader from. - /// A cancellation token. - /// An override map of field names to column names where the keys are the property names, and values are the column names. - /// A task containing the list of results. - public static ValueTask> ResultsAsync(this DbCommand command, CancellationToken cancellationToken, params (string Field, string? Column)[] fieldMappingOverrides) where T : new() - => ResultsAsync(command, fieldMappingOverrides, cancellationToken); - - // NOTE: The Results methods should be faster than the ResultsFromDataTable variations but are provided for validation of this assumption. - - /// - /// Loads all data into a DataTable before Iterates each record and attempts to map the fields to type T. - /// Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. - /// - /// The model type to map the values to (using reflection). - /// The IDataReader to read results from. - /// An optional override map of field names to column names where the keys are the property names, and values are the column names. - /// The enumerable to pull the transformed results from. - public static IEnumerable ResultsFromDataTable(this IDataReader reader, IEnumerable>? fieldMappingOverrides = null) - where T : new() - { - using var table = reader.ToDataTable(); - return table.To(fieldMappingOverrides, true); - } - - /// - /// Loads all data into a DataTable before Iterates each record and attempts to map the fields to type T. - /// Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. - /// - /// The model type to map the values to (using reflection). - /// The command to generate a reader from. - /// An optional override map of field names to column names where the keys are the property names, and values are the column names. - /// The enumerable to pull the transformed results from. - public static IEnumerable ResultsFromDataTable(this IDbCommand command, IEnumerable>? fieldMappingOverrides = null) - where T : new() - { - using var table = command.ToDataTable(); - return table.To(fieldMappingOverrides, true); - } + /// + /// Asynchronously returns all records and iteratively attempts to map the fields to type T. + /// + /// The model type to map the values to (using reflection). + /// The IDataReader to read results from. + /// An override map of field names to column names where the keys are the property names, and values are the column names. + /// If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. + /// Optional cancellation token. + /// A task containing the list of results. + public static async ValueTask> ResultsBufferedAsync(this DbDataReader reader, IEnumerable<(string Field, string? Column)>? fieldMappingOverrides, bool useReadAsync = true, CancellationToken cancellationToken = default) + where T : new() + { + if (reader is null) throw new System.ArgumentNullException(nameof(reader)); + Contract.EndContractBlock(); + + if (!await reader.ReadAsync(cancellationToken).ConfigureAwait(false)) + return Enumerable.Empty(); // else readStarted = true; + + var x = new Transformer(fieldMappingOverrides); + // Ignore missing columns. + (string Name, int Ordinal)[] columns = reader.GetMatchingOrdinals(x.ColumnNames, true); + + return x.AsDequeueingEnumerable( + await RetrieveAsyncInternal( + Transformer.LocalPool, + reader, cancellationToken, + columns.Select(c => c.Ordinal), + columns.Select(c => c.Name), + readStarted: true, + useReadAsync: useReadAsync).ConfigureAwait(false), + Transformer.LocalPool); + } + + /// + /// Asynchronously returns all records and iteratively attempts to map the fields to type T. + /// + /// The model type to map the values to (using reflection). + /// The IDataReader to read results from. + /// An override map of field names to column names where the keys are the property names, and values are the column names. + /// The cancellation token. + /// A task containing the list of results. + public static ValueTask> ResultsBufferedAsync(this DbDataReader reader, IEnumerable<(string Field, string? Column)>? fieldMappingOverrides, CancellationToken cancellationToken) + where T : new() + => ResultsBufferedAsync(reader, fieldMappingOverrides, true, cancellationToken); + + /// + /// Asynchronously returns all records and iteratively attempts to map the fields to type T. + /// + /// The model type to map the values to (using reflection). + /// The IDataReader to read results from. + /// An override map of field names to column names where the keys are the property names, and values are the column names. + /// If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. + /// Optional cancellation token. + /// A task containing the list of results. + public static ValueTask> ResultsBufferedAsync(this DbDataReader reader, IEnumerable> fieldMappingOverrides, bool useReadAsync = true, CancellationToken cancellationToken = default) + where T : new() + => ResultsBufferedAsync(reader, fieldMappingOverrides?.Select(kvp => (kvp.Key, kvp.Value)), useReadAsync, cancellationToken); + + /// + /// Asynchronously returns all records and iteratively attempts to map the fields to type T. + /// + /// The model type to map the values to (using reflection). + /// The IDataReader to read results from. + /// An override map of field names to column names where the keys are the property names, and values are the column names. + /// Optional cancellation token. + /// A task containing the list of results. + public static ValueTask> ResultsBufferedAsync(this DbDataReader reader, IEnumerable> fieldMappingOverrides, CancellationToken cancellationToken) + where T : new() + => ResultsBufferedAsync(reader, fieldMappingOverrides, true, cancellationToken); + + /// + /// Asynchronously returns all records and iteratively attempts to map the fields to type T. + /// + /// The model type to map the values to (using reflection). + /// The IDataReader to read results from. + /// An override map of field names to column names where the keys are the property names, and values are the column names. + /// A task containing the list of results. + public static ValueTask> ResultsBufferedAsync(this DbDataReader reader, params (string Field, string? Column)[] fieldMappingOverrides) where T : new() + => ResultsBufferedAsync(reader, (IEnumerable<(string Field, string? Column)>)fieldMappingOverrides); + + /// + /// Asynchronously returns all records and iteratively attempts to map the fields to type T. + /// + /// The model type to map the values to (using reflection). + /// The IDataReader to read results from. + /// The cancellation token. + /// An override map of field names to column names where the keys are the property names, and values are the column names. + /// A task containing the list of results. + public static ValueTask> ResultsBufferedAsync(this DbDataReader reader, CancellationToken cancellationToken, params (string Field, string? Column)[] fieldMappingOverrides) + where T : new() + => ResultsBufferedAsync(reader, fieldMappingOverrides, cancellationToken); + + /// + /// Asynchronously returns all records and iteratively attempts to map the fields to type T. + /// + /// The model type to map the values to (using reflection). + /// The command to generate a reader from. + /// An override map of field names to column names where the keys are the property names, and values are the column names. + /// If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. + /// Optional cancellation token. + /// A task containing the list of results. + public static ValueTask> ResultsAsync(this DbCommand command, IEnumerable<(string Field, string? Column)>? fieldMappingOverrides, bool useReadAsync = true, CancellationToken cancellationToken = default) + where T : new() + => command.ExecuteReaderAsync(reader => ResultsBufferedAsync(reader, fieldMappingOverrides, useReadAsync, cancellationToken), CommandBehavior.SingleResult, cancellationToken); + + /// + /// Asynchronously returns all records and iteratively attempts to map the fields to type T. + /// + /// The model type to map the values to (using reflection). + /// The command to generate a reader from. + /// An override map of field names to column names where the keys are the property names, and values are the column names. + /// The cancellation token. + /// A task containing the list of results. + public static ValueTask> ResultsAsync(this DbCommand command, IEnumerable<(string Field, string? Column)>? fieldMappingOverrides, CancellationToken cancellationToken) + where T : new() + => ResultsAsync(command, fieldMappingOverrides, true, cancellationToken); + + /// + /// Asynchronously returns all records and iteratively attempts to map the fields to type T. + /// + /// The model type to map the values to (using reflection). + /// The command to generate a reader from. + /// An override map of field names to column names where the keys are the property names, and values are the column names. + /// If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. + /// Optional cancellation token. + /// A task containing the list of results. + public static ValueTask> ResultsAsync(this DbCommand command, IEnumerable> fieldMappingOverrides, bool useReadAsync = true, CancellationToken cancellationToken = default) + where T : new() + => ResultsAsync(command, fieldMappingOverrides?.Select(kvp => (kvp.Key, kvp.Value)), useReadAsync, cancellationToken); + + /// + /// Asynchronously returns all records and iteratively attempts to map the fields to type T. + /// + /// The model type to map the values to (using reflection). + /// The command to generate a reader from. + /// An override map of field names to column names where the keys are the property names, and values are the column names. + /// The cancellation token. + /// A task containing the list of results. + public static ValueTask> ResultsAsync(this DbCommand command, IEnumerable> fieldMappingOverrides, CancellationToken cancellationToken) + where T : new() + => ResultsAsync(command, fieldMappingOverrides, true, cancellationToken); + + /// + /// Asynchronously returns all records and iteratively attempts to map the fields to type T. + /// + /// The model type to map the values to (using reflection). + /// The command to generate a reader from. + /// An override map of field names to column names where the keys are the property names, and values are the column names. + /// A task containing the list of results. + public static ValueTask> ResultsAsync(this DbCommand command, params (string Field, string? Column)[] fieldMappingOverrides) where T : new() + => ResultsAsync(command, (IEnumerable<(string Field, string? Column)>)fieldMappingOverrides); + + /// + /// Asynchronously returns all records and iteratively attempts to map the fields to type T. + /// + /// The model type to map the values to (using reflection). + /// The command to generate a reader from. + /// A cancellation token. + /// An override map of field names to column names where the keys are the property names, and values are the column names. + /// A task containing the list of results. + public static ValueTask> ResultsAsync(this DbCommand command, CancellationToken cancellationToken, params (string Field, string? Column)[] fieldMappingOverrides) where T : new() + => ResultsAsync(command, fieldMappingOverrides, cancellationToken); + + // NOTE: The Results methods should be faster than the ResultsFromDataTable variations but are provided for validation of this assumption. + + /// + /// Loads all data into a DataTable before Iterates each record and attempts to map the fields to type T. + /// Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. + /// + /// The model type to map the values to (using reflection). + /// The IDataReader to read results from. + /// An optional override map of field names to column names where the keys are the property names, and values are the column names. + /// The enumerable to pull the transformed results from. + public static IEnumerable ResultsFromDataTable(this IDataReader reader, IEnumerable>? fieldMappingOverrides = null) + where T : new() + { + using var table = reader.ToDataTable(); + return table.To(fieldMappingOverrides, true); + } + + /// + /// Loads all data into a DataTable before Iterates each record and attempts to map the fields to type T. + /// Data is temporarily stored (buffered in entirety) in a queue before applying the transform for each iteration. + /// + /// The model type to map the values to (using reflection). + /// The command to generate a reader from. + /// An optional override map of field names to column names where the keys are the property names, and values are the column names. + /// The enumerable to pull the transformed results from. + public static IEnumerable ResultsFromDataTable(this IDbCommand command, IEnumerable>? fieldMappingOverrides = null) + where T : new() + { + using var table = command.ToDataTable(); + return table.To(fieldMappingOverrides, true); + } } diff --git a/Source/Core/Extensions/Retrieve.cs b/Source/Core/Extensions/Retrieve.cs index c184348..6e39cee 100644 --- a/Source/Core/Extensions/Retrieve.cs +++ b/Source/Core/Extensions/Retrieve.cs @@ -1,432 +1,414 @@ -using Open.Database.Extensions.Core; -using System; -using System.Buffers; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Data; -using System.Data.Common; -using System.Diagnostics.Contracts; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - + namespace Open.Database.Extensions; +[SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Internal function that requires a cancellation token.")] public static partial class CoreExtensions { - internal static QueryResultQueue RetrieveInternal( - IDataReader reader, - IEnumerable ordinals, - IEnumerable? columnNames = null, - bool readStarted = false) - { - var o = ordinals is IList i ? i : ordinals.ToImmutableArray(); - return new QueryResultQueue( - o, columnNames ?? reader.GetNames(o), - new Queue(reader.AsEnumerableInternal(o, readStarted))); - } - - internal static QueryResultQueue RetrieveInternal( - ArrayPool arrayPool, - IDataReader reader, - IEnumerable ordinals, - IEnumerable? columnNames = null, - bool readStarted = false) - { - var o = ordinals is IList i ? i : ordinals.ToImmutableArray(); - return new QueryResultQueue( - o, columnNames ?? reader.GetNames(o), - new Queue(reader.AsEnumerableInternal(o, readStarted, arrayPool))); - } - - /// - public static QueryResultQueue Retrieve(this IDataReader reader) - { - var names = reader.GetNames(); - return new QueryResultQueue( - Enumerable.Range(0, names.Length), names, - new Queue(reader.AsEnumerable())); - } - - /// - /// Iterates all records within the current result set using an and returns the desired results. - /// + internal static QueryResultQueue RetrieveInternal( + IDataReader reader, + IEnumerable ordinals, + IEnumerable? columnNames = null, + bool readStarted = false) + { + IList o = ordinals is IList i ? i : ordinals.ToImmutableArray(); + return new QueryResultQueue( + o, columnNames ?? reader.GetNames(o), + reader.AsEnumerableInternal(o, readStarted)); + } + + internal static QueryResultQueue RetrieveInternal( + ArrayPool arrayPool, + IDataReader reader, + IEnumerable ordinals, + IEnumerable? columnNames = null, + bool readStarted = false) + { + IList o = ordinals is IList i ? i : ordinals.ToImmutableArray(); + return new QueryResultQueue( + o, columnNames ?? reader.GetNames(o), + reader.AsEnumerableInternal(o, readStarted, arrayPool)); + } + + /// + public static QueryResultQueue Retrieve(this IDataReader reader) + { + ImmutableArray names = reader.GetNames(); + return new QueryResultQueue( + Enumerable.Range(0, names.Length), names, + reader.AsEnumerable()); + } + + /// + /// Iterates all records within the current result set using an and returns the desired results. + /// /// values are left unchanged (retained). - /// The to read results from. - /// The ordinals to request from the reader for each record. - /// The query result that contains all the results and the column mappings. - public static QueryResultQueue Retrieve(this IDataReader reader, IEnumerable ordinals) - => RetrieveInternal(reader, ordinals); - - /// The to read results from. - /// The first ordinal to include in the request to the reader for each record. - /// The remaining ordinals to request from the reader for each record. - /// - public static QueryResultQueue Retrieve(this IDataReader reader, int n, params int[] others) - => RetrieveInternal(reader, Concat(n, others)); - - /// The to read results from. - /// The column names to select. - /// - public static QueryResultQueue Retrieve(this IDataReader reader, IEnumerable columnNames) - { - var columns = reader.GetOrdinalMapping(columnNames); - return RetrieveInternal(reader, - columns.Select(c => c.Ordinal), - columns.Select(c => c.Name)); - } - - /// The to read results from. - /// The first column name to include in the request to the reader for each record. - /// The remaining column names to request from the reader for each record. - /// - public static QueryResultQueue Retrieve(this IDataReader reader, string c, params string[] others) - => Retrieve(reader, Concat(c, others)); - - /// The to generate the reader from. - /// The flags to use with the data reader. - /// - public static QueryResultQueue Retrieve(this IDbCommand command, CommandBehavior behavior = CommandBehavior.Default) - => command.ExecuteReader(reader => reader.Retrieve(), behavior | CommandBehavior.SequentialAccess); - - /// - /// Executes a reader and iterates all records within the remaining result set using an and returns the desired results. - /// + /// The to read results from. + /// The ordinals to request from the reader for each record. + /// The query result that contains all the results and the column mappings. + public static QueryResultQueue Retrieve(this IDataReader reader, IEnumerable ordinals) + => RetrieveInternal(reader, ordinals); + + /// The to read results from. + /// The first ordinal to include in the request to the reader for each record. + /// The remaining ordinals to request from the reader for each record. + /// + public static QueryResultQueue Retrieve(this IDataReader reader, int n, params int[] others) + => RetrieveInternal(reader, Concat(n, others)); + + /// The to read results from. + /// The column names to select. + /// + public static QueryResultQueue Retrieve(this IDataReader reader, IEnumerable columnNames) + { + (string Name, int Ordinal)[] columns = reader.GetOrdinalMapping(columnNames); + return RetrieveInternal(reader, + columns.Select(c => c.Ordinal), + columns.Select(c => c.Name)); + } + + /// The to read results from. + /// The first column name to include in the request to the reader for each record. + /// The remaining column names to request from the reader for each record. + /// + public static QueryResultQueue Retrieve(this IDataReader reader, string c, params string[] others) + => Retrieve(reader, Concat(c, others)); + + /// The to generate the reader from. + /// The flags to use with the data reader. + /// + public static QueryResultQueue Retrieve(this IDbCommand command, CommandBehavior behavior = CommandBehavior.Default) + => command.ExecuteReader(reader => reader.Retrieve(), behavior | CommandBehavior.SequentialAccess); + + /// + /// Executes a reader and iterates all records within the remaining result set using an and returns the desired results. + /// /// /// values are left unchanged (retained). /// The default behavior will open a connection, execute the reader and close the connection it if was not already open. - /// The to generate the reader from. - /// The ordinals to request from the reader for each record. - /// - public static QueryResultQueue Retrieve(this IDbCommand command, IEnumerable ordinals) - => command.ExecuteReader(reader => reader.Retrieve(ordinals)); - - /// The to generate the reader from. - /// The first ordinal to include in the request to the reader for each record. - /// The remaining ordinals to request from the reader for each record. - /// - public static QueryResultQueue Retrieve(this IDbCommand command, int n, params int[] others) - => command.ExecuteReader(reader => RetrieveInternal(reader, Concat(n, others))); - - /// The to generate the reader from. - /// The column names to select. - /// - public static QueryResultQueue Retrieve(this IDbCommand command, IEnumerable columnNames) - => command.ExecuteReader(reader => reader.Retrieve(columnNames)); - - /// The to generate the reader from. - /// The first column name to include in the request to the reader for each record. - /// The remaining column names to request from the reader for each record. - /// - public static QueryResultQueue Retrieve(this IDbCommand command, string c, params string[] others) - => command.ExecuteReader(reader => Retrieve(reader, Concat(c, others))); - - /// - public static async ValueTask> RetrieveAsync(this DbDataReader reader, bool useReadAsync = true, CancellationToken cancellationToken = default) - { - if (reader is null) throw new ArgumentNullException(nameof(reader)); - Contract.EndContractBlock(); - - var fieldCount = reader.FieldCount; - var names = reader.GetNames(); // pull before first read. - var buffer = new Queue(); - - while (useReadAsync ? await reader.ReadAsync(cancellationToken).ConfigureAwait(true) : (!cancellationToken.IsCancellationRequested && reader.Read())) - { - var row = new object[fieldCount]; - reader.GetValues(row); - buffer.Enqueue(row); - } - - if (!useReadAsync) cancellationToken.ThrowIfCancellationRequested(); - - return new QueryResultQueue( - Enumerable.Range(0, names.Length), - names, - buffer); - } - - /// - public static ValueTask> RetrieveAsync( + /// The to generate the reader from. + /// The ordinals to request from the reader for each record. + /// + public static QueryResultQueue Retrieve(this IDbCommand command, IEnumerable ordinals) + => command.ExecuteReader(reader => reader.Retrieve(ordinals)); + + /// The to generate the reader from. + /// The first ordinal to include in the request to the reader for each record. + /// The remaining ordinals to request from the reader for each record. + /// + public static QueryResultQueue Retrieve(this IDbCommand command, int n, params int[] others) + => command.ExecuteReader(reader => RetrieveInternal(reader, Concat(n, others))); + + /// The to generate the reader from. + /// The column names to select. + /// + public static QueryResultQueue Retrieve(this IDbCommand command, IEnumerable columnNames) + => command.ExecuteReader(reader => reader.Retrieve(columnNames)); + + /// The to generate the reader from. + /// The first column name to include in the request to the reader for each record. + /// The remaining column names to request from the reader for each record. + /// + public static QueryResultQueue Retrieve(this IDbCommand command, string c, params string[] others) + => command.ExecuteReader(reader => Retrieve(reader, Concat(c, others))); + + /// + public static async ValueTask> RetrieveAsync(this DbDataReader reader, bool useReadAsync = true, CancellationToken cancellationToken = default) + { + if (reader is null) throw new ArgumentNullException(nameof(reader)); + Contract.EndContractBlock(); + + int fieldCount = reader.FieldCount; + ImmutableArray names = reader.GetNames(); // pull before first read. + var buffer = new Queue(); + + while (useReadAsync ? await reader.ReadAsync(cancellationToken).ConfigureAwait(false) : (!cancellationToken.IsCancellationRequested && reader.Read())) + { + object[] row = new object[fieldCount]; + reader.GetValues(row); + buffer.Enqueue(row); + } + + if (!useReadAsync) cancellationToken.ThrowIfCancellationRequested(); + + return new QueryResultQueue( + Enumerable.Range(0, names.Length), + names, + buffer); + } + + /// + public static ValueTask> RetrieveAsync( this DbDataReader reader, CancellationToken cancellationToken) - => RetrieveAsync(reader, true, cancellationToken); + => RetrieveAsync(reader, true, cancellationToken); - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Internal function that requires a cancellation token.")] - static ValueTask> RetrieveAsyncInternal(DbDataReader reader, CancellationToken cancellationToken, IEnumerable ordinals, IEnumerable? columnNames = null, bool readStarted = false, bool useReadAsync = true) - => RetrieveAsyncInternal(null, reader, cancellationToken, ordinals, columnNames, readStarted, useReadAsync)!; + static ValueTask> RetrieveAsyncInternal(DbDataReader reader, CancellationToken cancellationToken, IEnumerable ordinals, IEnumerable? columnNames = null, bool readStarted = false, bool useReadAsync = true) + => RetrieveAsyncInternal(null, reader, cancellationToken, ordinals, columnNames, readStarted, useReadAsync)!; - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Internal function that requires a cancellation token.")] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "")] - static async ValueTask> RetrieveAsyncInternal(ArrayPool? arrayPool, DbDataReader reader, CancellationToken cancellationToken, IEnumerable ordinals, IEnumerable? columnNames = null, bool readStarted = false, bool useReadAsync = true) - { - if (reader is null) throw new ArgumentNullException(nameof(reader)); - Contract.EndContractBlock(); + static async ValueTask> RetrieveAsyncInternal(ArrayPool? arrayPool, DbDataReader reader, CancellationToken cancellationToken, IEnumerable ordinals, IEnumerable? columnNames = null, bool readStarted = false, bool useReadAsync = true) + { + if (reader is null) throw new ArgumentNullException(nameof(reader)); + Contract.EndContractBlock(); - var buffer - = new Queue(); + var buffer + = new Queue(); - var result - = new QueryResultQueue( - ordinals, - columnNames ?? ordinals.Select(reader.GetName), - buffer); + var result + = new QueryResultQueue( + ordinals, + columnNames ?? ordinals.Select(reader.GetName), + buffer); - var fieldCount = result.ColumnCount; - var o = result.Ordinals; + int fieldCount = result.ColumnCount; + ImmutableArray o = result.Ordinals; - Func handler = GetHandler(arrayPool, fieldCount, o); + Func handler = GetHandler(arrayPool, fieldCount, o); - if (readStarted) - buffer.Enqueue(handler(reader)); + if (readStarted) + buffer.Enqueue(handler(reader)); - while (useReadAsync - ? await reader.ReadAsync(cancellationToken).ConfigureAwait(true) - : (!cancellationToken.IsCancellationRequested && reader.Read())) + while (useReadAsync + ? await reader.ReadAsync(cancellationToken).ConfigureAwait(false) + : (!cancellationToken.IsCancellationRequested && reader.Read())) { buffer.Enqueue(handler(reader)); } if (!useReadAsync) - cancellationToken.ThrowIfCancellationRequested(); - - return result; - - static Func GetHandler(ArrayPool? arrayPool, int fieldCount, ImmutableArray o) - { - if (arrayPool != null) - { - return record => - { - var row = arrayPool.Rent(fieldCount); - for (var i = 0; i < fieldCount; i++) - row[i] = record.GetValue(o[i]); - return row; - }; - } + cancellationToken.ThrowIfCancellationRequested(); + + return result; + + static Func GetHandler(ArrayPool? arrayPool, int fieldCount, ImmutableArray o) + { + if (arrayPool != null) + { + return record => + { + object[] row = arrayPool.Rent(fieldCount); + for (int i = 0; i < fieldCount; i++) + row[i] = record.GetValue(o[i]); + return row; + }; + } return fieldCount == 0 ? (_ => Array.Empty()) : (record => - { - var row = new object[fieldCount]; - for (var i = 0; i < fieldCount; i++) - row[i] = record.GetValue(o[i]); - return row; - }); + { + object[] row = new object[fieldCount]; + for (int i = 0; i < fieldCount; i++) + row[i] = record.GetValue(o[i]); + return row; + }); } } - /// The reader to enumerate. - /// The limited set of ordinals to include. If none are specified, the returned objects will be empty. - /// If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. - /// The cancellation token. - /// - public static ValueTask> RetrieveAsync( + /// The reader to enumerate. + /// The limited set of ordinals to include. If none are specified, the returned objects will be empty. + /// If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. + /// The cancellation token. + /// + public static ValueTask> RetrieveAsync( this DbDataReader reader, IEnumerable ordinals, bool useReadAsync = true, CancellationToken cancellationToken = default) - => RetrieveAsyncInternal(reader, cancellationToken, ordinals, useReadAsync: useReadAsync); + => RetrieveAsyncInternal(reader, cancellationToken, ordinals, useReadAsync: useReadAsync); - /// - public static ValueTask> RetrieveAsync( + /// + public static ValueTask> RetrieveAsync( this DbDataReader reader, IEnumerable ordinals, CancellationToken cancellationToken) - => RetrieveAsyncInternal(reader, cancellationToken, ordinals); + => RetrieveAsyncInternal(reader, cancellationToken, ordinals); - /// - public static ValueTask> RetrieveAsync( + /// + public static ValueTask> RetrieveAsync( this DbDataReader reader, int n, params int[] others) - => RetrieveAsync(reader, Concat(n, others)); - - /// The reader to enumerate. - /// A cancellation token. - /// The first ordinal to include in the request to the reader for each record. - /// The remaining ordinals to request from the reader for each record. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Method takes params and cannot have a the cancellation token last.")] - public static ValueTask> RetrieveAsync( + => RetrieveAsync(reader, Concat(n, others)); + + /// The reader to enumerate. + /// A cancellation token. + /// The first ordinal to include in the request to the reader for each record. + /// The remaining ordinals to request from the reader for each record. + /// + public static ValueTask> RetrieveAsync( this DbDataReader reader, CancellationToken cancellationToken, int n, params int[] others) - => RetrieveAsync(reader, Concat(n, others), cancellationToken); - - /// - /// Asynchronously enumerates all records within the current result set using an and returns the desired results. - /// - /// The to read results from. - /// The column names to select. - /// Orders the results arrays by ordinal. - /// If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. - /// The cancellation token. - /// - public static ValueTask> RetrieveAsync( + => RetrieveAsync(reader, Concat(n, others), cancellationToken); + + /// + /// Asynchronously enumerates all records within the current result set using an and returns the desired results. + /// + /// The to read results from. + /// The column names to select. + /// Orders the results arrays by ordinal. + /// If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. + /// The cancellation token. + /// + public static ValueTask> RetrieveAsync( this DbDataReader reader, IEnumerable columnNames, bool normalizeColumnOrder = false, bool useReadAsync = true, CancellationToken cancellationToken = default) - { - // Validate columns first. - var columns = reader.GetOrdinalMapping(columnNames, normalizeColumnOrder); - return RetrieveAsyncInternal(reader, cancellationToken, - columns.Select(c => c.Ordinal), - columns.Select(c => c.Name), useReadAsync: useReadAsync); - } - - /// - public static ValueTask> RetrieveAsync( + { + // Validate columns first. + (string Name, int Ordinal)[] columns = reader.GetOrdinalMapping(columnNames, normalizeColumnOrder); + return RetrieveAsyncInternal(reader, cancellationToken, + columns.Select(c => c.Ordinal), + columns.Select(c => c.Name), useReadAsync: useReadAsync); + } + + /// + public static ValueTask> RetrieveAsync( this DbDataReader reader, IEnumerable columnNames, bool normalizeColumnOrder, CancellationToken cancellationToken) - => RetrieveAsync(reader, columnNames, normalizeColumnOrder, true, cancellationToken); + => RetrieveAsync(reader, columnNames, normalizeColumnOrder, true, cancellationToken); - /// - public static ValueTask> RetrieveAsync( + /// + public static ValueTask> RetrieveAsync( this DbDataReader reader, IEnumerable columnNames, CancellationToken cancellationToken) - => RetrieveAsync(reader, columnNames, false, true, cancellationToken); + => RetrieveAsync(reader, columnNames, false, true, cancellationToken); - /// - public static ValueTask> RetrieveAsync( + /// + public static ValueTask> RetrieveAsync( this DbDataReader reader, string c, params string[] others) - => RetrieveAsync(reader, Concat(c, others)); - - /// The to read results from. - /// The cancellation token. - /// The first column name to include in the request to the reader for each record. - /// The remaining column names to request from the reader for each record. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Method takes params and cannot have a the cancellation token last.")] - public static ValueTask> RetrieveAsync( + => RetrieveAsync(reader, Concat(c, others)); + + /// The to read results from. + /// The cancellation token. + /// The first column name to include in the request to the reader for each record. + /// The remaining column names to request from the reader for each record. + /// + public static ValueTask> RetrieveAsync( this DbDataReader reader, CancellationToken cancellationToken, string c, params string[] others) - => RetrieveAsync(reader, Concat(c, others), cancellationToken); - - /// - /// Asynchronously executes a reader and enumerates all the remaining values of the current result set and returns the desired results. - /// - /// The command to generate a reader from. - /// If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. - /// Optional cancellation token. - /// - public static ValueTask> RetrieveAsync( + => RetrieveAsync(reader, Concat(c, others), cancellationToken); + + /// + /// Asynchronously executes a reader and enumerates all the remaining values of the current result set and returns the desired results. + /// + /// The command to generate a reader from. + /// If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. + /// Optional cancellation token. + /// + public static ValueTask> RetrieveAsync( this DbCommand command, bool useReadAsync = true, CancellationToken cancellationToken = default) - => command.ExecuteReaderAsync(reader => RetrieveAsync(reader, useReadAsync, cancellationToken), CommandBehavior.SequentialAccess, cancellationToken); + => command.ExecuteReaderAsync(reader => RetrieveAsync(reader, useReadAsync, cancellationToken), CommandBehavior.SequentialAccess, cancellationToken); - /// - public static ValueTask> RetrieveAsync( + /// + public static ValueTask> RetrieveAsync( this DbCommand command, CancellationToken cancellationToken) - => RetrieveAsync(command, true, cancellationToken); + => RetrieveAsync(command, true, cancellationToken); - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Internal function that requires a cancellation token.")] - static ValueTask> RetrieveAsyncInternal( + static ValueTask> RetrieveAsyncInternal( DbCommand command, CancellationToken cancellationToken, IEnumerable ordinals, IEnumerable? columnNames = null, bool useReadAsync = true) - => command.ExecuteReaderAsync(reader => RetrieveAsyncInternal(reader, cancellationToken, ordinals, columnNames, useReadAsync: useReadAsync), cancellationToken: cancellationToken); - - /// The command to generate a reader from. - /// The limited set of ordinals to include. If none are specified, the returned objects will be empty. - /// If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. - /// The cancellation token. - /// - public static ValueTask> RetrieveAsync( + => command.ExecuteReaderAsync(reader => RetrieveAsyncInternal(reader, cancellationToken, ordinals, columnNames, useReadAsync: useReadAsync), cancellationToken: cancellationToken); + + /// The command to generate a reader from. + /// The limited set of ordinals to include. If none are specified, the returned objects will be empty. + /// If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. + /// The cancellation token. + /// + public static ValueTask> RetrieveAsync( this DbCommand command, IEnumerable ordinals, bool useReadAsync = true, CancellationToken cancellationToken = default) - => RetrieveAsyncInternal(command, cancellationToken, ordinals, useReadAsync: useReadAsync); + => RetrieveAsyncInternal(command, cancellationToken, ordinals, useReadAsync: useReadAsync); - /// - public static ValueTask> RetrieveAsync( + /// + public static ValueTask> RetrieveAsync( this DbCommand command, IEnumerable ordinals, CancellationToken cancellationToken) - => RetrieveAsyncInternal(command, cancellationToken, ordinals); - /// - /// - public static ValueTask> RetrieveAsync( + => RetrieveAsyncInternal(command, cancellationToken, ordinals); + /// + /// + public static ValueTask> RetrieveAsync( this DbCommand command, int n, params int[] others) - => RetrieveAsync(command, Concat(n, others)); - - /// The command to generate a reader from. - /// The cancellation token. - /// The first ordinal to include in the request to the reader for each record. - /// The remaining ordinals to request from the reader for each record. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Method takes params and cannot have a the cancellation token last.")] - public static ValueTask> RetrieveAsync( + => RetrieveAsync(command, Concat(n, others)); + + /// The command to generate a reader from. + /// The cancellation token. + /// The first ordinal to include in the request to the reader for each record. + /// The remaining ordinals to request from the reader for each record. + /// + public static ValueTask> RetrieveAsync( this DbCommand command, CancellationToken cancellationToken, int n, params int[] others) - => RetrieveAsync(command, Concat(n, others), cancellationToken); + => RetrieveAsync(command, Concat(n, others), cancellationToken); - /// The to generate a reader from. - /// The column names to select. - /// Orders the results arrays by ordinal. - /// If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. - /// The cancellation token. + /// The to generate a reader from. + /// The column names to select. + /// Orders the results arrays by ordinal. + /// If true (default) will iterate the results using .ReadAsync() otherwise will only Execute the reader asynchronously and then use .Read() to iterate the results but still allowing cancellation. + /// The cancellation token. /// - public static ValueTask> RetrieveAsync( + public static ValueTask> RetrieveAsync( this DbCommand command, IEnumerable columnNames, bool normalizeColumnOrder = false, bool useReadAsync = true, CancellationToken cancellationToken = default) - => command.ExecuteReaderAsync(reader => RetrieveAsync(reader, columnNames, normalizeColumnOrder, useReadAsync, cancellationToken), CommandBehavior.SingleResult, cancellationToken); + => command.ExecuteReaderAsync(reader => RetrieveAsync(reader, columnNames, normalizeColumnOrder, useReadAsync, cancellationToken), CommandBehavior.SingleResult, cancellationToken); - /// - public static ValueTask> RetrieveAsync( + /// + public static ValueTask> RetrieveAsync( this DbCommand command, IEnumerable columnNames, bool normalizeColumnOrder, CancellationToken cancellationToken) - => RetrieveAsync(command, columnNames, normalizeColumnOrder, true, cancellationToken); + => RetrieveAsync(command, columnNames, normalizeColumnOrder, true, cancellationToken); - /// - public static ValueTask> RetrieveAsync( + /// + public static ValueTask> RetrieveAsync( this DbCommand command, IEnumerable columnNames, CancellationToken cancellationToken) - => RetrieveAsync(command, columnNames, false, true, cancellationToken); + => RetrieveAsync(command, columnNames, false, true, cancellationToken); - /// - public static ValueTask> RetrieveAsync( + /// + public static ValueTask> RetrieveAsync( this DbCommand command, string columnName, params string[] otherColumnNames) - => RetrieveAsync(command, Concat(columnName, otherColumnNames)); - - /// The to generate a reader from. - /// The cancellation token. - /// The first column name to include in the request to the reader for each record. - /// The remaining column names to request from the reader for each record. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Method takes params and cannot have a the cancellation token last.")] - public static ValueTask> RetrieveAsync( + => RetrieveAsync(command, Concat(columnName, otherColumnNames)); + + /// The to generate a reader from. + /// The cancellation token. + /// The first column name to include in the request to the reader for each record. + /// The remaining column names to request from the reader for each record. + /// + public static ValueTask> RetrieveAsync( this DbCommand command, CancellationToken cancellationToken, string columnName, params string[] otherColumnNames) - => RetrieveAsync(command, Concat(columnName, otherColumnNames), false, cancellationToken); + => RetrieveAsync(command, Concat(columnName, otherColumnNames), false, cancellationToken); } diff --git a/Source/Core/Extensions/Transaction.CreateCommand.cs b/Source/Core/Extensions/Transaction.CreateCommand.cs index 1d7afe0..2c8ab0b 100644 --- a/Source/Core/Extensions/Transaction.CreateCommand.cs +++ b/Source/Core/Extensions/Transaction.CreateCommand.cs @@ -1,100 +1,97 @@ -using System; -using System.Data; -using System.Data.Common; -using System.Diagnostics.Contracts; - -namespace Open.Database.Extensions; +namespace Open.Database.Extensions; /// /// Core non-DB-specific extensions for database transactions. /// public static partial class TransactionExtensions { - const string EmptyOrWhiteSpace = "Command is empty or whitespace."; + const string EmptyOrWhiteSpace = "Command is empty or whitespace."; - /// - /// Shortcut for creating an IDbCommand from any IDbTransaction. - /// - /// The transaction to create a command from. - /// The command type. Text, StoredProcedure, or TableDirect. - /// The command text or stored procedure name to use. - /// The number of seconds to wait before the command times out. - /// The created SqlCommand. - public static IDbCommand CreateCommand(this IDbTransaction transaction, - CommandType type, string commandText, int secondsTimeout = CommandTimeout.DEFAULT_SECONDS) - { - if (transaction is null) throw new ArgumentNullException(nameof(transaction)); - if (commandText is null) throw new ArgumentNullException(nameof(commandText)); - if (string.IsNullOrWhiteSpace(commandText)) throw new ArgumentException(EmptyOrWhiteSpace, nameof(commandText)); - Contract.EndContractBlock(); + /// + /// Shortcut for creating an IDbCommand from any IDbTransaction. + /// + /// The transaction to create a command from. + /// The command type. Text, StoredProcedure, or TableDirect. + /// The command text or stored procedure name to use. + /// The number of seconds to wait before the command times out. + /// The created SqlCommand. + public static IDbCommand CreateCommand(this IDbTransaction transaction, + CommandType type, string commandText, int secondsTimeout = CommandTimeout.DEFAULT_SECONDS) + { + if (transaction is null) throw new ArgumentNullException(nameof(transaction)); + if (commandText is null) throw new ArgumentNullException(nameof(commandText)); + if (string.IsNullOrWhiteSpace(commandText)) throw new ArgumentException(EmptyOrWhiteSpace, nameof(commandText)); + if (transaction.Connection is null) throw new InvalidOperationException("Transaction has no connection."); + Contract.EndContractBlock(); - var command = transaction.Connection.CreateCommand(type, commandText, secondsTimeout); - command.Transaction = transaction; - return command; - } + IDbCommand command = transaction.Connection.CreateCommand(type, commandText, secondsTimeout); + command.Transaction = transaction; + return command; + } - /// - /// Shortcut for creating a text IDbCommand from any IDbTransaction. - /// - /// The transaction to create a command from. - /// The command text or stored procedure name to use. - /// The number of seconds to wait before the command times out. - /// The created SqlCommand. - public static IDbCommand CreateTextCommand(this IDbTransaction transaction, - string commandText, int secondsTimeout = CommandTimeout.DEFAULT_SECONDS) - => transaction.CreateCommand(CommandType.Text, commandText, secondsTimeout); + /// + /// Shortcut for creating a text IDbCommand from any IDbTransaction. + /// + /// The transaction to create a command from. + /// The command text or stored procedure name to use. + /// The number of seconds to wait before the command times out. + /// The created SqlCommand. + public static IDbCommand CreateTextCommand(this IDbTransaction transaction, + string commandText, int secondsTimeout = CommandTimeout.DEFAULT_SECONDS) + => transaction.CreateCommand(CommandType.Text, commandText, secondsTimeout); - /// - /// Shortcut for creating a stored procedure IDbCommand from any IDbTransaction. - /// - /// The transaction to create a command from. - /// The command text or stored procedure name to use. - /// The number of seconds to wait before the command times out. - /// The created SqlCommand. - public static IDbCommand CreateStoredProcedureCommand(this IDbTransaction transaction, - string procedureName, int secondsTimeout = CommandTimeout.DEFAULT_SECONDS) - => transaction.CreateCommand(CommandType.StoredProcedure, procedureName, secondsTimeout); + /// + /// Shortcut for creating a stored procedure IDbCommand from any IDbTransaction. + /// + /// The transaction to create a command from. + /// The command text or stored procedure name to use. + /// The number of seconds to wait before the command times out. + /// The created SqlCommand. + public static IDbCommand CreateStoredProcedureCommand(this IDbTransaction transaction, + string procedureName, int secondsTimeout = CommandTimeout.DEFAULT_SECONDS) + => transaction.CreateCommand(CommandType.StoredProcedure, procedureName, secondsTimeout); - /// - /// Shortcut for creating an DbCommand from any DbTransaction. - /// - /// The transaction to create a command from. - /// The command type. Text, StoredProcedure, or TableDirect. - /// The command text or stored procedure name to use. - /// The number of seconds to wait before the command times out. - /// The created SqlCommand. - public static DbCommand CreateCommand(this DbTransaction transaction, - CommandType type, string commandText, int secondsTimeout = CommandTimeout.DEFAULT_SECONDS) - { - if (transaction is null) throw new ArgumentNullException(nameof(transaction)); - if (commandText is null) throw new ArgumentNullException(nameof(commandText)); - if (string.IsNullOrWhiteSpace(commandText)) throw new ArgumentException(EmptyOrWhiteSpace, nameof(commandText)); - Contract.EndContractBlock(); + /// + /// Shortcut for creating an DbCommand from any DbTransaction. + /// + /// The transaction to create a command from. + /// The command type. Text, StoredProcedure, or TableDirect. + /// The command text or stored procedure name to use. + /// The number of seconds to wait before the command times out. + /// The created SqlCommand. + public static DbCommand CreateCommand(this DbTransaction transaction, + CommandType type, string commandText, int secondsTimeout = CommandTimeout.DEFAULT_SECONDS) + { + if (transaction is null) throw new ArgumentNullException(nameof(transaction)); + if (commandText is null) throw new ArgumentNullException(nameof(commandText)); + if (string.IsNullOrWhiteSpace(commandText)) throw new ArgumentException(EmptyOrWhiteSpace, nameof(commandText)); + if (transaction.Connection is null) throw new InvalidOperationException("Transaction has no connection."); + Contract.EndContractBlock(); - var command = transaction.Connection.CreateCommand(type, commandText, secondsTimeout); - command.Transaction = transaction; - return command; - } + DbCommand command = transaction.Connection.CreateCommand(type, commandText, secondsTimeout); + command.Transaction = transaction; + return command; + } - /// - /// Shortcut for creating a text DbCommand from any DbTransaction. - /// - /// The transaction to create a command from. - /// The command text or stored procedure name to use. - /// The number of seconds to wait before the command times out. - /// The created SqlCommand. - public static DbCommand CreateTextCommand(this DbTransaction transaction, - string commandText, int secondsTimeout = CommandTimeout.DEFAULT_SECONDS) - => transaction.CreateCommand(CommandType.Text, commandText, secondsTimeout); + /// + /// Shortcut for creating a text DbCommand from any DbTransaction. + /// + /// The transaction to create a command from. + /// The command text or stored procedure name to use. + /// The number of seconds to wait before the command times out. + /// The created SqlCommand. + public static DbCommand CreateTextCommand(this DbTransaction transaction, + string commandText, int secondsTimeout = CommandTimeout.DEFAULT_SECONDS) + => transaction.CreateCommand(CommandType.Text, commandText, secondsTimeout); - /// - /// Shortcut for creating a stored procedure DbCommand from any DbTransaction. - /// - /// The transaction to create a command from. - /// The command text or stored procedure name to use. - /// The number of seconds to wait before the command times out. - /// The created SqlCommand. - public static DbCommand CreateStoredProcedureCommand(this DbTransaction transaction, - string procedureName, int secondsTimeout = CommandTimeout.DEFAULT_SECONDS) - => transaction.CreateCommand(CommandType.StoredProcedure, procedureName, secondsTimeout); + /// + /// Shortcut for creating a stored procedure DbCommand from any DbTransaction. + /// + /// The transaction to create a command from. + /// The command text or stored procedure name to use. + /// The number of seconds to wait before the command times out. + /// The created SqlCommand. + public static DbCommand CreateStoredProcedureCommand(this DbTransaction transaction, + string procedureName, int secondsTimeout = CommandTimeout.DEFAULT_SECONDS) + => transaction.CreateCommand(CommandType.StoredProcedure, procedureName, secondsTimeout); } diff --git a/Source/Core/Extensions/Transaction.cs b/Source/Core/Extensions/Transaction.cs index d966810..ef2588e 100644 --- a/Source/Core/Extensions/Transaction.cs +++ b/Source/Core/Extensions/Transaction.cs @@ -1,576 +1,562 @@ -using System; -using System.Data; -using System.Data.Common; -using System.Diagnostics.Contracts; -using System.Threading; -using System.Threading.Tasks; - -namespace Open.Database.Extensions; +namespace Open.Database.Extensions; +[SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] public static partial class TransactionExtensions { - /// - /// Begins a transaction before executing the action. Commits if there are no exceptions, the 'Commit' value from the action is true and the optional cancellation token has not been cancelled. Otherwise rolls-back the transaction. - /// - /// The value returned from the action. - /// The connection to transact with. - /// The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. - /// The isolation level for the transaction. - /// A token that if cancelled will cause this transaction to be aborted or rolled-back. - /// The value returned from the conditional action. - public static (bool Commit, T Value) ExecuteTransactionConditional( - this DbConnection connection, - Func conditionalAction, - IsolationLevel isolationLevel = IsolationLevel.Unspecified, - CancellationToken cancellationToken = default) - { - if (connection is null) throw new ArgumentNullException(nameof(connection)); - if (conditionalAction is null) throw new ArgumentNullException(nameof(conditionalAction)); - Contract.EndContractBlock(); - - cancellationToken.ThrowIfCancellationRequested(); - - var success = false; - DbTransaction? transaction = null; - - connection.EnsureOpen(); - cancellationToken.ThrowIfCancellationRequested(); - - try - { - transaction = connection.BeginTransaction(isolationLevel); - var result = conditionalAction(transaction); - cancellationToken.ThrowIfCancellationRequested(); - success = result.Commit; - return result; - } - finally - { - if (transaction != null) // Just in case acquiring a transaction fails. - { - if (success) transaction.Commit(); - else transaction.Rollback(); - } - } - } - - /// - /// Begins a transaction before executing the action. Commits if there are no exceptions, the conditional action returns true, and the optional cancellation token is not cancelled. Otherwise rolls-back the transaction. - /// - /// The connection to transact with. - /// The handler to execute while a transaction is pending. Returning true signals to commit the transaction. - /// The isolation level for the transaction. - /// A token that if cancelled will cause this transaction to be aborted or rolled-back. - /// True if committed. - public static bool ExecuteTransactionConditional( - this DbConnection connection, - Func conditionalAction, - IsolationLevel isolationLevel = IsolationLevel.Unspecified, - CancellationToken cancellationToken = default) - => connection.ExecuteTransactionConditional( - isolationLevel, cancellationToken, t => (conditionalAction(t), true)).Commit; - - /// - /// Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - /// - /// The value returned from the action. - /// The connection to transact with. - /// The handler to execute while a transaction is pending. - /// The isolation level for the transaction. - /// A token that if cancelled will cause this transaction to be aborted or rolled-back. - /// The value of the action. - public static T ExecuteTransaction( - this DbConnection connection, - Func action, - IsolationLevel isolationLevel = IsolationLevel.Unspecified, - CancellationToken cancellationToken = default) - => connection.ExecuteTransactionConditional( - isolationLevel, cancellationToken, t => (true, action(t))).Value; - - /// - /// Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - /// - /// The connection to transact with. - /// The handler to execute while a transaction is pending. - /// The isolation level for the transaction. - /// A token that if cancelled will cause this transaction to be aborted or rolled-back. - public static void ExecuteTransaction( - this DbConnection connection, - Action action, - IsolationLevel isolationLevel = IsolationLevel.Unspecified, - CancellationToken cancellationToken = default) - => connection.ExecuteTransaction(t => { action(t); return true; }, isolationLevel, cancellationToken); - - /// - /// Begins a transaction before executing the action. Commits if there are no exceptions, the 'Commit' value from the action is true, and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - /// - /// The value returned from the action. - /// The connection to transact with. - /// The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. - /// The isolation level for the transaction. - /// A token that if cancelled will cause this transaction to be aborted or rolled-back. - /// The value of the awaited action. - public static async ValueTask<(bool Commit, T Value)> ExecuteTransactionConditionalAsync( - this DbConnection connection, - Func> conditionalAction, - IsolationLevel isolationLevel = IsolationLevel.Unspecified, - CancellationToken cancellationToken = default) - { - if (connection is null) throw new ArgumentNullException(nameof(connection)); - if (conditionalAction is null) throw new ArgumentNullException(nameof(conditionalAction)); - Contract.EndContractBlock(); - - cancellationToken.ThrowIfCancellationRequested(); - - var success = false; - DbTransaction? transaction = null; - - // Only await if needed... - if (connection.State != ConnectionState.Open) - { - await connection.EnsureOpenAsync(cancellationToken).ConfigureAwait(true); // If the task is cancelled, awaiting will throw. - cancellationToken.ThrowIfCancellationRequested(); - } - - try - { - transaction = connection.BeginTransaction(isolationLevel); - var result = await conditionalAction(transaction).ConfigureAwait(false); // If the task is cancelled, awaiting will throw. - cancellationToken.ThrowIfCancellationRequested(); - success = result.Commit; - return result; - } - finally - { - if (transaction != null) // Just in case acquiring a transaction fails. - { - if (success) transaction.Commit(); - else transaction.Rollback(); - } - } - } - - /// - /// Begins a transaction before executing the action. Commits if there are no exceptions, the 'Commit' value from the action is true, and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - /// - /// The connection to transact with. - /// The handler to execute while a transaction is pending. Returning true signals to commit the transaction. - /// The isolation level for the transaction. - /// A token that if cancelled will cause this transaction to be aborted or rolled-back. - /// The value of the awaited action. - public static async ValueTask ExecuteTransactionConditionalAsync( - this DbConnection connection, - Func> conditionalAction, - IsolationLevel isolationLevel = IsolationLevel.Unspecified, - CancellationToken cancellationToken = default) - => (await connection.ExecuteTransactionConditionalAsync( - async t => (await conditionalAction(t).ConfigureAwait(false), true), isolationLevel, cancellationToken).ConfigureAwait(false)).Commit; - - /// - /// Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - /// - /// The value returned from the action. - /// The connection to transact with. - /// The handler to execute while a transaction is pending. - /// The isolation level for the transaction. - /// A token that if cancelled will cause this transaction to be aborted or rolled-back. - /// The value of the awaited action. - public static async ValueTask ExecuteTransactionAsync( - this DbConnection connection, - Func> action, - IsolationLevel isolationLevel = IsolationLevel.Unspecified, - CancellationToken cancellationToken = default) - => (await connection.ExecuteTransactionConditionalAsync(async t => (true, await action(t).ConfigureAwait(false)), isolationLevel, cancellationToken).ConfigureAwait(false)).Value; - - /// - /// Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - /// - /// The connection to transact with. - /// The handler to execute while a transaction is pending. - /// The isolation level for the transaction. - /// A token that if cancelled will cause this transaction to be aborted or rolled-back. - public static async ValueTask ExecuteTransactionAsync( - this DbConnection connection, - Func action, - IsolationLevel isolationLevel = IsolationLevel.Unspecified, - CancellationToken cancellationToken = default) - => await connection.ExecuteTransactionAsync(async c => { await action(c).ConfigureAwait(false); return true; }, isolationLevel, cancellationToken).ConfigureAwait(false); - - #region Overloads - - #region No optional params - /// - /// Begins a transaction before executing the action. Commits if there are no exceptions and 'Commit' value from the action is true. Otherwise rolls-back the transaction. - /// - /// The value returned from the action. - /// The connection to transact with. - /// The isolation level for the transaction. - /// A token that if cancelled will cause this transaction to be aborted or rolled-back. - /// The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. - /// The value returned from the conditional action. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] - public static (bool Commit, T Value) ExecuteTransactionConditional( - this DbConnection connection, - IsolationLevel isolationLevel, - CancellationToken cancellationToken, - Func conditionalAction) - => connection.ExecuteTransactionConditional(conditionalAction, isolationLevel, cancellationToken); - - /// - /// Begins a transaction before executing the action. Commits if there are no exceptions and the conditional action returns true. Otherwise rolls-back the transaction. - /// - /// The connection to transact with. - /// The isolation level for the transaction. - /// A token that if cancelled will cause this transaction to be aborted or rolled-back. - /// The handler to execute while a transaction is pending. Returning true signals to commit the transaction. - /// True if committed. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] - public static bool ExecuteTransactionConditional( - this DbConnection connection, - IsolationLevel isolationLevel, - CancellationToken cancellationToken, - Func conditionalAction) - => connection.ExecuteTransactionConditional(conditionalAction, isolationLevel, cancellationToken); - - /// - /// Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. - /// - /// The value returned from the action. - /// The connection to transact with. - /// The isolation level for the transaction. - /// A token that if cancelled will cause this transaction to be aborted or rolled-back. - /// The handler to execute while a transaction is pending. - /// The value of the action. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] - public static T ExecuteTransaction( - this DbConnection connection, - IsolationLevel isolationLevel, - CancellationToken cancellationToken, - Func action) - => connection.ExecuteTransaction(action, isolationLevel, cancellationToken); - - /// - /// Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. - /// - /// The connection to transact with. - /// The isolation level for the transaction. - /// A token that if cancelled will cause this transaction to be aborted or rolled-back. - /// The handler to execute while a transaction is pending. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] - public static void ExecuteTransaction( - this DbConnection connection, - IsolationLevel isolationLevel, - CancellationToken cancellationToken, - Action action) - => connection.ExecuteTransaction(action, isolationLevel, cancellationToken); - - /// - /// Begins a transaction before executing the action. Commits if there are no exceptions and the 'Commit' value from the action is true. Otherwise rolls-back the transaction. - /// - /// The value returned from the action. - /// The connection to transact with. - /// The isolation level for the transaction. - /// A token that if cancelled will cause this transaction to be aborted or rolled-back. - /// The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. - /// The value of the awaited action. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] - public static ValueTask<(bool Commit, T Value)> ExecuteTransactionConditionalAsync( - this DbConnection connection, - IsolationLevel isolationLevel, - CancellationToken cancellationToken, - Func> conditionalAction) - => connection.ExecuteTransactionConditionalAsync(conditionalAction, isolationLevel, cancellationToken); - - /// - /// Begins a transaction before executing the action. Commits if there are no exceptions and the value from the action is true. Otherwise rolls-back the transaction. - /// - /// The connection to transact with. - /// The isolation level for the transaction. - /// A token that if cancelled will cause this transaction to be aborted or rolled-back. - /// The handler to execute while a transaction is pending. Returning true signals to commit the transaction. - /// The value of the awaited action. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] - public static ValueTask ExecuteTransactionConditionalAsync( - this DbConnection connection, - IsolationLevel isolationLevel, - CancellationToken cancellationToken, - Func> conditionalAction) - => connection.ExecuteTransactionConditionalAsync(conditionalAction, isolationLevel, cancellationToken); - - /// - /// Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. - /// - /// The value returned from the action. - /// The connection to transact with. - /// The isolation level for the transaction. - /// A token that if cancelled will cause this transaction to be aborted or rolled-back. - /// The handler to execute while a transaction is pending. - /// The value of the awaited action. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] - public static ValueTask ExecuteTransactionAsync( - this DbConnection connection, - IsolationLevel isolationLevel, - CancellationToken cancellationToken, - Func> action) - => connection.ExecuteTransactionAsync(action, isolationLevel, cancellationToken); - - /// - /// Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. - /// - /// The connection to transact with. - /// The isolation level for the transaction. - /// A token that if cancelled will cause this transaction to be aborted or rolled-back. - /// The handler to execute while a transaction is pending. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] - public static ValueTask ExecuteTransactionAsync( - this DbConnection connection, - IsolationLevel isolationLevel, - CancellationToken cancellationToken, - Func action) - => connection.ExecuteTransactionAsync(action, isolationLevel, cancellationToken); - - #endregion - - #region Optional Token - /// - /// Begins a transaction before executing the action. Commits if there are no exceptions and 'Commit' value from the action is true. Otherwise rolls-back the transaction. - /// - /// The value returned from the action. - /// The connection to transact with. - /// The isolation level for the transaction. - /// The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. - /// An optional token that if cancelled will cause this transaction to be aborted or rolled-back. - /// The value returned from the conditional action. - public static (bool Commit, T Value) ExecuteTransactionConditional( - this DbConnection connection, - IsolationLevel isolationLevel, - Func conditionalAction, - CancellationToken cancellationToken = default) - => connection.ExecuteTransactionConditional(conditionalAction, isolationLevel, cancellationToken); - - /// - /// Begins a transaction before executing the action. Commits if there are no exceptions and the conditional action returns true. Otherwise rolls-back the transaction. - /// - /// The connection to transact with. - /// The isolation level for the transaction. - /// The handler to execute while a transaction is pending. Returning true signals to commit the transaction. - /// An optional token that if cancelled will cause this transaction to be aborted or rolled-back. - /// True if committed. - public static bool ExecuteTransactionConditional( - this DbConnection connection, - IsolationLevel isolationLevel, - Func conditionalAction, - CancellationToken cancellationToken = default) - => connection.ExecuteTransactionConditional(conditionalAction, isolationLevel, cancellationToken); - - /// - /// Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. - /// - /// The value returned from the action. - /// The connection to transact with. - /// The isolation level for the transaction. - /// The handler to execute while a transaction is pending. - /// An optional token that if cancelled will cause this transaction to be aborted or rolled-back. - /// The value of the action. - public static T ExecuteTransaction( - this DbConnection connection, - IsolationLevel isolationLevel, - Func action, - CancellationToken cancellationToken = default) - => connection.ExecuteTransaction(action, isolationLevel, cancellationToken); - - /// - /// Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. - /// - /// The connection to transact with. - /// The isolation level for the transaction. - /// The handler to execute while a transaction is pending. - /// An optional token that if cancelled will cause this transaction to be aborted or rolled-back. - public static void ExecuteTransaction( - this DbConnection connection, - IsolationLevel isolationLevel, - Action action, - CancellationToken cancellationToken = default) - => connection.ExecuteTransaction(action, isolationLevel, cancellationToken); - - /// - /// Begins a transaction before executing the action. Commits if there are no exceptions and the 'Commit' value from the action is true. Otherwise rolls-back the transaction. - /// - /// The value returned from the action. - /// The connection to transact with. - /// The isolation level for the transaction. - /// The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. - /// An optional token that if cancelled will cause this transaction to be aborted or rolled-back. - /// The value of the awaited action. - public static ValueTask<(bool Commit, T Value)> ExecuteTransactionConditionalAsync( - this DbConnection connection, - IsolationLevel isolationLevel, - Func> conditionalAction, - CancellationToken cancellationToken = default) - => connection.ExecuteTransactionConditionalAsync(conditionalAction, isolationLevel, cancellationToken); - - /// - /// Begins a transaction before executing the action. Commits if there are no exceptions and the value from the action is true. Otherwise rolls-back the transaction. - /// - /// The connection to transact with. - /// The isolation level for the transaction. - /// The handler to execute while a transaction is pending. Returning true signals to commit the transaction. - /// An optional token that if cancelled will cause this transaction to be aborted or rolled-back. - /// The value of the awaited action. - public static ValueTask ExecuteTransactionConditionalAsync( - this DbConnection connection, - IsolationLevel isolationLevel, - Func> conditionalAction, - CancellationToken cancellationToken = default) - => connection.ExecuteTransactionConditionalAsync(conditionalAction, isolationLevel, cancellationToken); - - /// - /// Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. - /// - /// The value returned from the action. - /// The connection to transact with. - /// The isolation level for the transaction. - /// The handler to execute while a transaction is pending. - /// An optional token that if cancelled will cause this transaction to be aborted or rolled-back. - /// The value of the awaited action. - public static ValueTask ExecuteTransactionAsync( - this DbConnection connection, - IsolationLevel isolationLevel, - Func> action, - CancellationToken cancellationToken = default) - => connection.ExecuteTransactionAsync(action, isolationLevel, cancellationToken); - - /// - /// Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. - /// - /// The connection to transact with. - /// The isolation level for the transaction. - /// The handler to execute while a transaction is pending. - /// An optional token that if cancelled will cause this transaction to be aborted or rolled-back. - public static ValueTask ExecuteTransactionAsync( - this DbConnection connection, - IsolationLevel isolationLevel, - Func action, - CancellationToken cancellationToken = default) - => connection.ExecuteTransactionAsync(action, isolationLevel, cancellationToken); - #endregion - - #region Unspecified Isolation Level - /// - /// Begins a transaction before executing the action. Commits if there are no exceptions, the 'Commit' value from the action is true and the optional cancellation token has not been cancelled. Otherwise rolls-back the transaction. - /// - /// The value returned from the action. - /// The connection to transact with. - /// A token that if cancelled will cause this transaction to be aborted or rolled-back. - /// The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. - /// The value returned from the conditional action. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] - public static (bool Commit, T Value) ExecuteTransactionConditional( - this DbConnection connection, - CancellationToken cancellationToken, - Func conditionalAction) - => connection.ExecuteTransactionConditional(conditionalAction, IsolationLevel.Unspecified, cancellationToken); - - /// - /// Begins a transaction before executing the action. Commits if there are no exceptions, the conditional action returns true, and the optional cancellation token is not cancelled. Otherwise rolls-back the transaction. - /// - /// The connection to transact with. - /// An optional token that if cancelled will cause this transaction to be aborted or rolled-back. - /// The handler to execute while a transaction is pending. Returning true signals to commit the transaction. - /// True if committed. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] - public static bool ExecuteTransactionConditional( - this DbConnection connection, - CancellationToken cancellationToken, - Func conditionalAction) - => connection.ExecuteTransactionConditional(conditionalAction, IsolationLevel.Unspecified, cancellationToken); - - /// - /// Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - /// - /// The value returned from the action. - /// The connection to transact with. - /// A token that if cancelled will cause this transaction to be aborted or rolled-back. - /// The handler to execute while a transaction is pending. - /// The value of the action. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] - public static T ExecuteTransaction( - this DbConnection connection, - CancellationToken cancellationToken, - Func action) - => connection.ExecuteTransaction(action, IsolationLevel.Unspecified, cancellationToken); - - /// - /// Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - /// - /// The connection to transact with. - /// A token that if cancelled will cause this transaction to be aborted or rolled-back. - /// The handler to execute while a transaction is pending. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] - public static void ExecuteTransaction( - this DbConnection connection, - CancellationToken cancellationToken, - Action action) - => connection.ExecuteTransaction(action, IsolationLevel.Unspecified, cancellationToken); - - /// - /// Begins a transaction before executing the action. Commits if there are no exceptions, the 'Commit' value from the action is true, and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - /// - /// The value returned from the action. - /// The connection to transact with. - /// A token that if cancelled will cause this transaction to be aborted or rolled-back. - /// The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. - /// The value of the awaited action. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] - public static ValueTask<(bool Commit, T Value)> ExecuteTransactionConditionalAsync( - this DbConnection connection, - CancellationToken cancellationToken, - Func> conditionalAction) - => connection.ExecuteTransactionConditionalAsync(conditionalAction, IsolationLevel.Unspecified, cancellationToken); - - /// - /// Begins a transaction before executing the action. Commits if there are no exceptions, the value from the action is true, and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - /// - /// The connection to transact with. - /// A token that if cancelled will cause this transaction to be aborted or rolled-back. - /// The handler to execute while a transaction is pending. Returning true signals to commit the transaction. - /// The value of the awaited action. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] - public static ValueTask ExecuteTransactionConditionalAsync( - this DbConnection connection, - CancellationToken cancellationToken, - Func> conditionalAction) - => connection.ExecuteTransactionConditionalAsync(conditionalAction, IsolationLevel.Unspecified, cancellationToken); - - /// - /// Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - /// - /// The value returned from the action. - /// The connection to transact with. - /// A token that if cancelled will cause this transaction to be aborted or rolled-back. - /// The handler to execute while a transaction is pending. - /// The value of the awaited action. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] - public static ValueTask ExecuteTransactionAsync( - this DbConnection connection, - CancellationToken cancellationToken, - Func> action) - => connection.ExecuteTransactionAsync(action, IsolationLevel.Unspecified, cancellationToken); - - /// - /// Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - /// - /// The connection to transact with. - /// A token that if cancelled will cause this transaction to be aborted or rolled-back. - /// The handler to execute while a transaction is pending. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] - public static ValueTask ExecuteTransactionAsync( - this DbConnection connection, - CancellationToken cancellationToken, - Func action) - => connection.ExecuteTransactionAsync(action, IsolationLevel.Unspecified, cancellationToken); - - #endregion - - #endregion + /// + /// Begins a transaction before executing the action. + /// Commits if there are no exceptions, the 'Commit' value from the action is true and the optional cancellation token has not been cancelled. + /// Otherwise rolls-back the transaction. + /// + /// The value returned from the action. + /// The connection to transact with. + /// + /// The handler to execute while a transaction is pending. + /// Returning a 'Commit' value of true signals to commit the transaction. + /// + /// The isolation level for the transaction. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The value returned from the conditional action. + public static (bool Commit, T Value) ExecuteTransactionConditional( + this DbConnection connection, + Func conditionalAction, + IsolationLevel isolationLevel = IsolationLevel.Unspecified, + CancellationToken cancellationToken = default) + { + if (connection is null) throw new ArgumentNullException(nameof(connection)); + if (conditionalAction is null) throw new ArgumentNullException(nameof(conditionalAction)); + Contract.EndContractBlock(); + + cancellationToken.ThrowIfCancellationRequested(); + + connection.EnsureOpen(); + cancellationToken.ThrowIfCancellationRequested(); + + using DbTransaction transaction = connection.BeginTransaction(isolationLevel); + (bool Commit, T Value) result = conditionalAction(transaction); + cancellationToken.ThrowIfCancellationRequested(); + + if (result.Commit) transaction.Commit(); + return result; + } + + /// + public static async ValueTask<(bool Commit, T Value)> ExecuteTransactionConditionalAsync( + this DbConnection connection, + Func> conditionalAction, + IsolationLevel isolationLevel = IsolationLevel.Unspecified, + CancellationToken cancellationToken = default) + { + if (connection is null) throw new ArgumentNullException(nameof(connection)); + if (conditionalAction is null) throw new ArgumentNullException(nameof(conditionalAction)); + Contract.EndContractBlock(); + + if (connection.State != ConnectionState.Open) + await connection.EnsureOpenAsync(cancellationToken); + +#if NETSTANDARD2_0 + using DbTransaction transaction = connection.BeginTransaction(isolationLevel); +#else + await using DbTransaction transaction = await connection.BeginTransactionAsync(isolationLevel, cancellationToken); +#endif + (bool Commit, T Value) result = await conditionalAction(transaction, cancellationToken); + if (result.Commit) transaction.Commit(); + return result; + } + + /// + /// Begins a transaction before executing the action. + /// Commits if there are no exceptions, the conditional action returns true, and the optional cancellation token is not cancelled. + /// Otherwise rolls-back the transaction. + /// + /// The connection to transact with. + /// The handler to execute while a transaction is pending. Returning true signals to commit the transaction. + /// The isolation level for the transaction. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// True if committed. + public static bool ExecuteTransactionConditional( + this DbConnection connection, + Func conditionalAction, + IsolationLevel isolationLevel = IsolationLevel.Unspecified, + CancellationToken cancellationToken = default) + => connection.ExecuteTransactionConditional( + isolationLevel, cancellationToken, + t => (conditionalAction(t), true)).Commit; + + /// + /// Begins a transaction before executing the action. + /// Commits if there are no exceptions and the optional provided token is not cancelled. + /// Otherwise rolls-back the transaction. + /// + /// The value returned from the action. + /// The connection to transact with. + /// The handler to execute while a transaction is pending. + /// The isolation level for the transaction. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The value of the action. + public static T ExecuteTransaction( + this DbConnection connection, + Func action, + IsolationLevel isolationLevel = IsolationLevel.Unspecified, + CancellationToken cancellationToken = default) + => connection.ExecuteTransactionConditional( + isolationLevel, cancellationToken, + t => (true, action(t))).Value; + + /// + /// Begins a transaction before executing the action. + /// Commits if there are no exceptions and the optional provided token is not cancelled. + /// Otherwise rolls-back the transaction. + /// + /// The connection to transact with. + /// The handler to execute while a transaction is pending. + /// The isolation level for the transaction. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + public static void ExecuteTransaction( + this DbConnection connection, + Action action, + IsolationLevel isolationLevel = IsolationLevel.Unspecified, + CancellationToken cancellationToken = default) + => connection.ExecuteTransaction( + t => { action(t); return true; }, + isolationLevel, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions, the 'Commit' value from the action is true, and the optional provided token is not cancelled. Otherwise rolls-back the transaction. + /// + /// The value returned from the action. + /// The connection to transact with. + /// The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. + /// The isolation level for the transaction. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The value of the awaited action. + public static ValueTask<(bool Commit, T Value)> ExecuteTransactionConditionalAsync( + this DbConnection connection, + Func> conditionalAction, + IsolationLevel isolationLevel = IsolationLevel.Unspecified, + CancellationToken cancellationToken = default) + => connection.ExecuteTransactionConditionalAsync( + (db, _) => conditionalAction(db), + isolationLevel, cancellationToken); + + /// + /// Begins a transaction before executing the action. + /// Commits if there are no exceptions, the 'Commit' value from the action is true, and the optional provided token is not cancelled. + /// Otherwise rolls-back the transaction. + /// + /// The connection to transact with. + /// The handler to execute while a transaction is pending. Returning true signals to commit the transaction. + /// The isolation level for the transaction. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The value of the awaited action. + public static async ValueTask ExecuteTransactionConditionalAsync( + this DbConnection connection, + Func> conditionalAction, + IsolationLevel isolationLevel = IsolationLevel.Unspecified, + CancellationToken cancellationToken = default) + => (await connection + .ExecuteTransactionConditionalAsync( + async t => (await conditionalAction(t).ConfigureAwait(false), true), + isolationLevel, cancellationToken) + .ConfigureAwait(false)).Commit; + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. + /// + /// The value returned from the action. + /// The connection to transact with. + /// The handler to execute while a transaction is pending. + /// The isolation level for the transaction. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The value of the awaited action. + public static async ValueTask ExecuteTransactionAsync( + this DbConnection connection, + Func> action, + IsolationLevel isolationLevel = IsolationLevel.Unspecified, + CancellationToken cancellationToken = default) + => (await connection + .ExecuteTransactionConditionalAsync( + async t => (true, await action(t).ConfigureAwait(false)), + isolationLevel, cancellationToken) + .ConfigureAwait(false)).Value; + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. + /// + /// The connection to transact with. + /// The handler to execute while a transaction is pending. + /// The isolation level for the transaction. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + public static async ValueTask ExecuteTransactionAsync( + this DbConnection connection, + Func action, + IsolationLevel isolationLevel = IsolationLevel.Unspecified, + CancellationToken cancellationToken = default) + => await connection + .ExecuteTransactionAsync( + async c => { await action(c).ConfigureAwait(false); return true; }, + isolationLevel, cancellationToken) + .ConfigureAwait(false); + + #region Overloads + + #region No optional params + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions and 'Commit' value from the action is true. Otherwise rolls-back the transaction. + /// + /// The value returned from the action. + /// The connection to transact with. + /// The isolation level for the transaction. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. + /// The value returned from the conditional action. + public static (bool Commit, T Value) ExecuteTransactionConditional( + this DbConnection connection, + IsolationLevel isolationLevel, + CancellationToken cancellationToken, + Func conditionalAction) + => connection.ExecuteTransactionConditional(conditionalAction, isolationLevel, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions and the conditional action returns true. Otherwise rolls-back the transaction. + /// + /// The connection to transact with. + /// The isolation level for the transaction. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The handler to execute while a transaction is pending. Returning true signals to commit the transaction. + /// True if committed. + public static bool ExecuteTransactionConditional( + this DbConnection connection, + IsolationLevel isolationLevel, + CancellationToken cancellationToken, + Func conditionalAction) + => connection.ExecuteTransactionConditional(conditionalAction, isolationLevel, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. + /// + /// The value returned from the action. + /// The connection to transact with. + /// The isolation level for the transaction. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The handler to execute while a transaction is pending. + /// The value of the action. + public static T ExecuteTransaction( + this DbConnection connection, + IsolationLevel isolationLevel, + CancellationToken cancellationToken, + Func action) + => connection.ExecuteTransaction(action, isolationLevel, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. + /// + /// The connection to transact with. + /// The isolation level for the transaction. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The handler to execute while a transaction is pending. + public static void ExecuteTransaction( + this DbConnection connection, + IsolationLevel isolationLevel, + CancellationToken cancellationToken, + Action action) + => connection.ExecuteTransaction(action, isolationLevel, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions and the 'Commit' value from the action is true. Otherwise rolls-back the transaction. + /// + /// The value returned from the action. + /// The connection to transact with. + /// The isolation level for the transaction. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. + /// The value of the awaited action. + public static ValueTask<(bool Commit, T Value)> ExecuteTransactionConditionalAsync( + this DbConnection connection, + IsolationLevel isolationLevel, + CancellationToken cancellationToken, + Func> conditionalAction) + => connection.ExecuteTransactionConditionalAsync(conditionalAction, isolationLevel, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions and the value from the action is true. Otherwise rolls-back the transaction. + /// + /// The connection to transact with. + /// The isolation level for the transaction. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The handler to execute while a transaction is pending. Returning true signals to commit the transaction. + /// The value of the awaited action. + public static ValueTask ExecuteTransactionConditionalAsync( + this DbConnection connection, + IsolationLevel isolationLevel, + CancellationToken cancellationToken, + Func> conditionalAction) + => connection.ExecuteTransactionConditionalAsync(conditionalAction, isolationLevel, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. + /// + /// The value returned from the action. + /// The connection to transact with. + /// The isolation level for the transaction. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The handler to execute while a transaction is pending. + /// The value of the awaited action. + public static ValueTask ExecuteTransactionAsync( + this DbConnection connection, + IsolationLevel isolationLevel, + CancellationToken cancellationToken, + Func> action) + => connection.ExecuteTransactionAsync(action, isolationLevel, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. + /// + /// The connection to transact with. + /// The isolation level for the transaction. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The handler to execute while a transaction is pending. + public static ValueTask ExecuteTransactionAsync( + this DbConnection connection, + IsolationLevel isolationLevel, + CancellationToken cancellationToken, + Func action) + => connection.ExecuteTransactionAsync(action, isolationLevel, cancellationToken); + + #endregion + + #region Optional Token + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions and 'Commit' value from the action is true. Otherwise rolls-back the transaction. + /// + /// The value returned from the action. + /// The connection to transact with. + /// The isolation level for the transaction. + /// The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. + /// An optional token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The value returned from the conditional action. + public static (bool Commit, T Value) ExecuteTransactionConditional( + this DbConnection connection, + IsolationLevel isolationLevel, + Func conditionalAction, + CancellationToken cancellationToken = default) + => connection.ExecuteTransactionConditional(conditionalAction, isolationLevel, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions and the conditional action returns true. Otherwise rolls-back the transaction. + /// + /// The connection to transact with. + /// The isolation level for the transaction. + /// The handler to execute while a transaction is pending. Returning true signals to commit the transaction. + /// An optional token that if cancelled will cause this transaction to be aborted or rolled-back. + /// True if committed. + public static bool ExecuteTransactionConditional( + this DbConnection connection, + IsolationLevel isolationLevel, + Func conditionalAction, + CancellationToken cancellationToken = default) + => connection.ExecuteTransactionConditional(conditionalAction, isolationLevel, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. + /// + /// The value returned from the action. + /// The connection to transact with. + /// The isolation level for the transaction. + /// The handler to execute while a transaction is pending. + /// An optional token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The value of the action. + public static T ExecuteTransaction( + this DbConnection connection, + IsolationLevel isolationLevel, + Func action, + CancellationToken cancellationToken = default) + => connection.ExecuteTransaction(action, isolationLevel, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. + /// + /// The connection to transact with. + /// The isolation level for the transaction. + /// The handler to execute while a transaction is pending. + /// An optional token that if cancelled will cause this transaction to be aborted or rolled-back. + public static void ExecuteTransaction( + this DbConnection connection, + IsolationLevel isolationLevel, + Action action, + CancellationToken cancellationToken = default) + => connection.ExecuteTransaction(action, isolationLevel, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions and the 'Commit' value from the action is true. Otherwise rolls-back the transaction. + /// + /// The value returned from the action. + /// The connection to transact with. + /// The isolation level for the transaction. + /// The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. + /// An optional token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The value of the awaited action. + public static ValueTask<(bool Commit, T Value)> ExecuteTransactionConditionalAsync( + this DbConnection connection, + IsolationLevel isolationLevel, + Func> conditionalAction, + CancellationToken cancellationToken = default) + => connection.ExecuteTransactionConditionalAsync(conditionalAction, isolationLevel, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions and the value from the action is true. Otherwise rolls-back the transaction. + /// + /// The connection to transact with. + /// The isolation level for the transaction. + /// The handler to execute while a transaction is pending. Returning true signals to commit the transaction. + /// An optional token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The value of the awaited action. + public static ValueTask ExecuteTransactionConditionalAsync( + this DbConnection connection, + IsolationLevel isolationLevel, + Func> conditionalAction, + CancellationToken cancellationToken = default) + => connection.ExecuteTransactionConditionalAsync(conditionalAction, isolationLevel, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. + /// + /// The value returned from the action. + /// The connection to transact with. + /// The isolation level for the transaction. + /// The handler to execute while a transaction is pending. + /// An optional token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The value of the awaited action. + public static ValueTask ExecuteTransactionAsync( + this DbConnection connection, + IsolationLevel isolationLevel, + Func> action, + CancellationToken cancellationToken = default) + => connection.ExecuteTransactionAsync(action, isolationLevel, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. + /// + /// The connection to transact with. + /// The isolation level for the transaction. + /// The handler to execute while a transaction is pending. + /// An optional token that if cancelled will cause this transaction to be aborted or rolled-back. + public static ValueTask ExecuteTransactionAsync( + this DbConnection connection, + IsolationLevel isolationLevel, + Func action, + CancellationToken cancellationToken = default) + => connection.ExecuteTransactionAsync(action, isolationLevel, cancellationToken); + #endregion + + #region Unspecified Isolation Level + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions, the 'Commit' value from the action is true and the optional cancellation token has not been cancelled. Otherwise rolls-back the transaction. + /// + /// The value returned from the action. + /// The connection to transact with. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. + /// The value returned from the conditional action. + public static (bool Commit, T Value) ExecuteTransactionConditional( + this DbConnection connection, + CancellationToken cancellationToken, + Func conditionalAction) + => connection.ExecuteTransactionConditional(conditionalAction, IsolationLevel.Unspecified, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions, the conditional action returns true, and the optional cancellation token is not cancelled. Otherwise rolls-back the transaction. + /// + /// The connection to transact with. + /// An optional token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The handler to execute while a transaction is pending. Returning true signals to commit the transaction. + /// True if committed. + public static bool ExecuteTransactionConditional( + this DbConnection connection, + CancellationToken cancellationToken, + Func conditionalAction) + => connection.ExecuteTransactionConditional(conditionalAction, IsolationLevel.Unspecified, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. + /// + /// The value returned from the action. + /// The connection to transact with. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The handler to execute while a transaction is pending. + /// The value of the action. + public static T ExecuteTransaction( + this DbConnection connection, + CancellationToken cancellationToken, + Func action) + => connection.ExecuteTransaction(action, IsolationLevel.Unspecified, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. + /// + /// The connection to transact with. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The handler to execute while a transaction is pending. + public static void ExecuteTransaction( + this DbConnection connection, + CancellationToken cancellationToken, + Action action) + => connection.ExecuteTransaction(action, IsolationLevel.Unspecified, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions, the 'Commit' value from the action is true, and the optional provided token is not cancelled. Otherwise rolls-back the transaction. + /// + /// The value returned from the action. + /// The connection to transact with. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. + /// The value of the awaited action. + public static ValueTask<(bool Commit, T Value)> ExecuteTransactionConditionalAsync( + this DbConnection connection, + CancellationToken cancellationToken, + Func> conditionalAction) + => connection.ExecuteTransactionConditionalAsync(conditionalAction, IsolationLevel.Unspecified, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions, the value from the action is true, and the optional provided token is not cancelled. Otherwise rolls-back the transaction. + /// + /// The connection to transact with. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The handler to execute while a transaction is pending. Returning true signals to commit the transaction. + /// The value of the awaited action. + public static ValueTask ExecuteTransactionConditionalAsync( + this DbConnection connection, + CancellationToken cancellationToken, + Func> conditionalAction) + => connection.ExecuteTransactionConditionalAsync(conditionalAction, IsolationLevel.Unspecified, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. + /// + /// The value returned from the action. + /// The connection to transact with. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The handler to execute while a transaction is pending. + /// The value of the awaited action. + public static ValueTask ExecuteTransactionAsync( + this DbConnection connection, + CancellationToken cancellationToken, + Func> action) + => connection.ExecuteTransactionAsync(action, IsolationLevel.Unspecified, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. + /// + /// The connection to transact with. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The handler to execute while a transaction is pending. + public static ValueTask ExecuteTransactionAsync( + this DbConnection connection, + CancellationToken cancellationToken, + Func action) + => connection.ExecuteTransactionAsync(action, IsolationLevel.Unspecified, cancellationToken); + + #endregion + + #endregion } diff --git a/Source/Core/Extensions/_.cs b/Source/Core/Extensions/_.cs index b8581b3..c006949 100644 --- a/Source/Core/Extensions/_.cs +++ b/Source/Core/Extensions/_.cs @@ -1,11 +1,4 @@ -using Open.Database.Extensions.Core; -using System; -using System.Collections.Generic; -using System.Data; -using System.Diagnostics.Contracts; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; +using System.Linq.Expressions; namespace Open.Database.Extensions; @@ -14,39 +7,63 @@ namespace Open.Database.Extensions; /// public static partial class CoreExtensions { - internal static IEnumerable Concat(T first, ICollection remaining) - => (remaining == null || remaining.Count == 0) ? new T[] { first } : Enumerable.Repeat(first, 1).Concat(remaining); - - // https://stackoverflow.com/questions/17660097/is-it-possible-to-speed-this-method-up/17669142#17669142 - internal static Action BuildUntypedSetter(this PropertyInfo propertyInfo) - { - var targetType = propertyInfo.DeclaringType; - var exTarget = Expression.Parameter(targetType ?? throw new InvalidOperationException(), "t"); - var exValue = Expression.Parameter(typeof(object), "p"); - var methodInfo = propertyInfo.GetSetMethod(); - var exBody = Expression.Call(exTarget, methodInfo, - Expression.Convert(exValue, propertyInfo.PropertyType)); - var lambda = Expression.Lambda>(exBody, exTarget, exValue); - var action = lambda.Compile(); - return action; - } - - internal static object? DBNullValueToNull(object? value) - => value == DBNull.Value ? null : value; - - /// - /// Any values are yielded as null. - /// - /// The source enumerable. - public static IEnumerable DBNullToNull(this IEnumerable values) - { - return values is null - ? throw new ArgumentNullException(nameof(values)) - : DBNullToNullCore(values); + internal static IEnumerable Concat(T first, ICollection remaining) + => (remaining == null || remaining.Count == 0) ? [first] : remaining.Prepend(first); + + // https://stackoverflow.com/questions/17660097/is-it-possible-to-speed-this-method-up/17669142#17669142 + internal static Action BuildUntypedSetter(this PropertyInfo propertyInfo) + { + var targetType = propertyInfo.DeclaringType; + var exTarget = Expression.Parameter(targetType ?? throw new InvalidOperationException(), "t"); + var exValue = Expression.Parameter(typeof(object), "p"); + var methodInfo = propertyInfo.GetSetMethod(); + var exBody = Expression.Call(exTarget, methodInfo!, + Expression.Convert(exValue, propertyInfo.PropertyType)); + var lambda = Expression.Lambda>(exBody, exTarget, exValue); + return lambda.Compile(); + } + + internal static object? DBNullValueToNull(object? value) + => value == DBNull.Value ? null : value; + + /// + /// Copies the contents of the span + /// to the span + /// with any values converted to . + /// + public static Span CopyToDBNullAsNull(this ReadOnlySpan values, Span target) + { + int len = values.Length; + object?[] result = new object?[len]; + for (int i = 0; i < len; i++) + target[i] = DBNullValueToNull(values[i]); + + return result; + } + + /// + public static Span CopyToDBNullAsNull(this Span values, Span target) + => CopyToDBNullAsNull((ReadOnlySpan)values, target); + + /// + public static Span CopyToDBNullAsNull(this ReadOnlySpan values, Memory target) + => CopyToDBNullAsNull(values, target.Span); + + /// + public static Span CopyToDBNullAsNull(this Span values, Memory target) + => CopyToDBNullAsNull((ReadOnlySpan)values, target.Span); + + /// + /// Any values are yielded as null. + /// + /// The source enumerable. + public static IEnumerable DBNullToNull(this IEnumerable values) + { + return DBNullToNullCore(values ?? throw new ArgumentNullException(nameof(values))); static IEnumerable DBNullToNullCore(IEnumerable values) { - foreach (var v in values) + foreach (object? v in values) yield return DBNullValueToNull(v); } } @@ -56,64 +73,77 @@ internal static IEnumerable Concat(T first, ICollection remaining) /// /// The source values. public static object?[] DBNullToNullCopy(this object?[] values) - { - if (values is null) throw new ArgumentNullException(nameof(values)); - Contract.EndContractBlock(); + { + if (values is null) throw new ArgumentNullException(nameof(values)); + Contract.EndContractBlock(); - var len = values.Length; - var result = new object?[len]; - for (var i = 0; i < len; i++) - result[i] = DBNullValueToNull(values[i]); - return result; - } + object?[] result = new object?[values.Length]; + CopyToDBNullAsNull(values.AsSpan(), result.AsSpan()); + return result; + } /// /// Returns a copy of the contents of this span as an array with any values converted to null. /// /// - public static object?[] DBNullToNullCopy(this in ReadOnlySpan values) - { - var len = values.Length; - var result = new object?[len]; - for (var i = 0; i < len; i++) - result[i] = DBNullValueToNull(values[i]); - return result; - } - - /// - public static object?[] DBNullToNullCopy(this in Span values) - { - var len = values.Length; - var result = new object?[len]; - for (var i = 0; i < len; i++) - result[i] = DBNullValueToNull(values[i]); - return result; - } - - /// - /// Replaces any in the with null; - /// - /// The source values. - public static Span ReplaceDBNullWithNull(this Span values) - { - var len = values.Length; - for (var i = 0; i < len; i++) - { - ref var value = ref values[i]; - if (value == DBNull.Value) value = null; - } - return values; - } - - /// - public static object?[] ReplaceDBNullWithNull(this object?[] values) - { - if (values is null) throw new ArgumentNullException(nameof(values)); - Contract.EndContractBlock(); - - values.AsSpan().ReplaceDBNullWithNull(); - return values; - } + public static object?[] DBNullToNullCopy(this ReadOnlySpan values) + { + object?[] result = new object?[values.Length]; + CopyToDBNullAsNull(values, result.AsSpan()); + return result; + } + + /// + public static object?[] DBNullToNullCopy(this Span values) + => DBNullToNullCopy((ReadOnlySpan)values); + + /// + /// Replaces any in the with null; + /// + /// The source values. + public static Span ReplaceDBNullWithNull(this Span values) + => CopyToDBNullAsNull(values, values); + + /// + public static Memory ReplaceDBNullWithNull(this Memory values) + { + CopyToDBNullAsNull(values.Span, values.Span); + return values; + } + + /// + public static object?[] ReplaceDBNullWithNull(this object?[] values) + { + if (values is null) throw new ArgumentNullException(nameof(values)); + Contract.EndContractBlock(); + + values.AsSpan().ReplaceDBNullWithNull(); + return values; + } + + /// + public static List ReplaceDBNullWithNull(this List values) + { + if (values is null) throw new ArgumentNullException(nameof(values)); + Contract.EndContractBlock(); + +#if NET8_0_OR_GREATER + var span = System.Runtime.InteropServices.CollectionsMarshal.AsSpan(values); + for (int i = 0; i < span.Length; i++) + { + ref object? item = ref span[i]; + if (item == DBNull.Value) item = null; + } +#else + int count = values.Count; + for (int i = 0; i < count; i++) + { + if (values[i] == DBNull.Value) values[i] = null; + } +#endif + + return values; + } /// /// Generic enumerable extension for . @@ -121,10 +151,10 @@ internal static IEnumerable Concat(T first, ICollection remaining) /// The column collection. /// An enumerable of s. public static IEnumerable AsEnumerable(this DataColumnCollection columns) - { + { return columns is null - ? throw new ArgumentNullException(nameof(columns)) - : AsEnumerableCore(columns); + ? throw new ArgumentNullException(nameof(columns)) + : AsEnumerableCore(columns); static IEnumerable AsEnumerableCore(DataColumnCollection columns) { @@ -139,10 +169,10 @@ static IEnumerable AsEnumerableCore(DataColumnCollection columns) /// The row collection. /// An enumerable of s. public static IEnumerable AsEnumerable(this DataRowCollection rows) - { + { return rows is null - ? throw new ArgumentNullException(nameof(rows)) - : AsEnumerableCore(rows); + ? throw new ArgumentNullException(nameof(rows)) + : AsEnumerableCore(rows); static IEnumerable AsEnumerableCore(DataRowCollection rows) { @@ -153,15 +183,19 @@ static IEnumerable AsEnumerableCore(DataRowCollection rows) /// public static IEnumerable To(this DataTable table, IEnumerable<(string Field, string? Column)>? fieldMappingOverrides, bool clearSourceTable = false) where T : new() - => Transformer - .Create(fieldMappingOverrides) - .Results(table, clearSourceTable); + => Transformer + .Create(fieldMappingOverrides) + .Results(table, clearSourceTable); - /// - public static IEnumerable To(this DataTable table, params (string Field, string? Column)[] fieldMappingOverrides) where T : new() - => Transformer - .Create(fieldMappingOverrides) - .Results(table, false); + /// +#if NET8_0_OR_GREATER + public static IEnumerable To(this DataTable table, params IEnumerable<(string Field, string? Column)> fieldMappingOverrides) where T : new() +#else + public static IEnumerable To(this DataTable table, params (string Field, string? Column)[] fieldMappingOverrides) where T : new() +#endif + => Transformer + .Create(fieldMappingOverrides) + .Results(table, false); /// /// Loads all data into a queue before iterating (dequeuing) the results as type . @@ -172,25 +206,25 @@ static IEnumerable AsEnumerableCore(DataRowCollection rows) /// Clears the source table before providing the enumeration. /// An enumerable used to iterate the results. public static IEnumerable To(this DataTable table, IEnumerable>? fieldMappingOverrides, bool clearSourceTable = false) where T : new() - => To(table, fieldMappingOverrides?.Select(kvp => (kvp.Key, kvp.Value)), clearSourceTable); - - /// - /// Useful extension for dequeuing items from a queue. - /// Not thread safe but queueing/dequeuing items in between items is supported. - /// - /// Return type of the source queue - /// An enumerable of the items contained within the queue. - public static IEnumerable DequeueEach(this Queue source) - { - if (source is null) throw new ArgumentNullException(nameof(source)); - Contract.EndContractBlock(); - -#if NETSTANDARD2_1 - while (source.TryDequeue(out var a)) - yield return a; + => To(table, fieldMappingOverrides?.Select(kvp => (kvp.Key, kvp.Value)), clearSourceTable); + + /// + /// Useful extension for dequeuing items from a queue. + /// Not thread safe but queueing/dequeuing items in between items is supported. + /// + /// Return type of the source queue + /// An enumerable of the items contained within the queue. + public static IEnumerable DequeueEach(this Queue source) + { + if (source is null) throw new ArgumentNullException(nameof(source)); + Contract.EndContractBlock(); + +#if NETSTANDARD2_0 + while (source.Count != 0) + yield return source.Dequeue(); #else - while (source.Count != 0) - yield return source.Dequeue(); + while (source.TryDequeue(out T? a)) + yield return a; #endif - } + } } diff --git a/Source/Core/IDbConnectionFactory.cs b/Source/Core/IDbConnectionFactory.cs index 968f50d..069ed3e 100644 --- a/Source/Core/IDbConnectionFactory.cs +++ b/Source/Core/IDbConnectionFactory.cs @@ -1,5 +1,4 @@ -using System; -using System.Data; +using System.Data.Common; namespace Open.Database.Extensions; @@ -8,21 +7,21 @@ namespace Open.Database.Extensions; /// public interface IDbConnectionFactory { - /// - /// Creates a new connection ready for use. - /// - /// An IDbConnection. - IDbConnection Create(); + /// + /// Creates a new connection ready for use. + /// + /// An IDbConnection. + IDbConnection Create(); } /// /// The actual connection type. public interface IDbConnectionFactory : IDbConnectionFactory - where TConnection : IDbConnection + where TConnection : IDbConnection { - /// - /// An connection of type . - new TConnection Create(); + /// + /// An connection of type . + new TConnection Create(); } /// @@ -30,65 +29,85 @@ public interface IDbConnectionFactory : IDbConnectionFactory /// public static class DbConnectionFactoryExtensions { - class PoolFromFactory : IDbConnectionPool - { - private readonly IDbConnectionFactory _connectionFactory; - - public PoolFromFactory(IDbConnectionFactory connectionFactory) - { - _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory)); - } - - public IDbConnection Take() - => _connectionFactory.Create(); - - public void Give(IDbConnection connection) - => connection.Dispose(); - } - - class PoolFromFactory : IDbConnectionPool - where TConnection : IDbConnection - { - private readonly IDbConnectionFactory _connectionFactory; - - public PoolFromFactory(IDbConnectionFactory connectionFactory) - { - _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory)); - } - - public TConnection Take() - => _connectionFactory.Create(); - - IDbConnection IDbConnectionPool.Take() - => Take(); - - public void Give(IDbConnection connection) - => connection.Dispose(); - } - - /// - /// Provides a connection pool that simply creates from a connection factory and disposes when returned. - /// - /// The connection factory to generate connections from. - /// - public static IDbConnectionPool AsPool(this IDbConnectionFactory connectionFactory) - => new PoolFromFactory(connectionFactory); - - /// - /// Provides a connection pool that simply creates from a connection factory and disposes when returned. - /// - /// The connection factory to generate connections from. - /// - public static IDbConnectionPool AsPool(this IDbConnectionFactory connectionFactory) - where TConnection : IDbConnection - => new PoolFromFactory(connectionFactory); - - /// - /// Coerces a non-generic connection factory to a generic one. - /// - /// The source connection factory. - /// The generic version of the source factory. - public static IDbConnectionFactory AsGeneric(this IDbConnectionFactory connectionFactory) - => (connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory))) is IDbConnectionFactory p ? p - : new DbConnectionFactory(() => connectionFactory.Create()); + /// + /// A struct that represents a connection factory that can be used as a pool. + /// + public readonly struct ConnectionFactoryToPoolAdapter(Func factory) : IDbConnectionPool + { + /// + /// Constructs a connection factory to pool adapter. + /// + public ConnectionFactoryToPoolAdapter(IDbConnectionFactory factory) + : this(factory.Create) { } + + /// + public IDbConnection Take() + => factory(); + + /// + public void Give(IDbConnection connection) + => connection.Dispose(); + } + + /// /> + public readonly struct ConnectionFactoryToPoolAdapter(Func factory) : IDbConnectionPool + where TConnection : IDbConnection + { + /// + /// Constructs a connection factory to pool adapter. + /// + public ConnectionFactoryToPoolAdapter(IDbConnectionFactory factory) + : this(factory.Create) { } + + /// + public TConnection Take() + => factory(); + + IDbConnection IDbConnectionPool.Take() + => Take(); + + /// + public void Give(IDbConnection connection) + => connection.Dispose(); + } + + /// + /// Provides a connection pool that simply creates from a connection factory and disposes when returned. + /// + /// The connection factory to generate connections from. + /// An to handle this factory. + public static ConnectionFactoryToPoolAdapter AsPool(this IDbConnectionFactory connectionFactory) + => new (connectionFactory); + + /// + public static ConnectionFactoryToPoolAdapter AsPool(this Func connectionFactory) + => new(connectionFactory); + + /// + /// Provides a connection pool that simply creates from a connection factory and disposes when returned. + /// + /// The connection factory to generate connections from. + /// An to handle this factory. + public static ConnectionFactoryToPoolAdapter AsPool(this IDbConnectionFactory connectionFactory) + where TConnection : IDbConnection + => new (connectionFactory); + + /// + public static ConnectionFactoryToPoolAdapter AsPool(this Func connectionFactory) + where TConnection : IDbConnection + => new(connectionFactory); + + /// + /// Coerces a non-generic connection factory to a generic one. + /// + /// The source connection factory. + /// The generic version of the source factory. + public static IDbConnectionFactory AsGeneric(this IDbConnectionFactory connectionFactory) + { + if (connectionFactory is null) throw new ArgumentNullException(nameof(connectionFactory)); + Contract.EndContractBlock(); + + return connectionFactory is IDbConnectionFactory p ? p + : new DbConnectionFactory(connectionFactory.Create); + } } diff --git a/Source/Core/IDbConnectionPool.cs b/Source/Core/IDbConnectionPool.cs index d5409c8..eb0f963 100644 --- a/Source/Core/IDbConnectionPool.cs +++ b/Source/Core/IDbConnectionPool.cs @@ -1,7 +1,4 @@ -using System; -using System.Data; - -namespace Open.Database.Extensions; +namespace Open.Database.Extensions; /// /// A unifying common interface for creating/managing connections. Can easily be @@ -19,24 +16,24 @@ public interface IDbConnectionPool /// An . IDbConnection Take(); - /// - /// Gives the connection to the pool. - /// Depending on implementation, + /// + /// Gives the connection to the pool. + /// Depending on implementation, /// the pool could be full, /// and the connection disposed of immediately. - /// - /// The connection to be received by the pool. - void Give(IDbConnection connection); + /// + /// The connection to be received by the pool. + void Give(IDbConnection connection); } /// /// The actual connection type. public interface IDbConnectionPool : IDbConnectionPool - where TConnection : IDbConnection + where TConnection : IDbConnection { - /// - /// An connection of type . - new TConnection Take(); + /// + /// An connection of type . + new TConnection Take(); } /// @@ -44,30 +41,26 @@ public interface IDbConnectionPool : IDbConnectionPool /// public static class ConnectionPoolExtensions { - class GenericPool : IDbConnectionPool - { - private readonly IDbConnectionPool _source; + class GenericPool(IDbConnectionPool source) : IDbConnectionPool + { + private readonly IDbConnectionPool _source + = source ?? throw new ArgumentNullException(nameof(source)); - public GenericPool(IDbConnectionPool source) - { - _source = source ?? throw new ArgumentNullException(nameof(source)); - } + public IDbConnection Take() + => _source.Take(); - public IDbConnection Take() - => _source.Take(); + IDbConnection IDbConnectionPool.Take() + => _source.Take(); - IDbConnection IDbConnectionPool.Take() - => _source.Take(); + public void Give(IDbConnection connection) + => _source.Give(connection); + } - public void Give(IDbConnection connection) - => _source.Give(connection); - } - - /// - /// Converts a non-generic connection factory to a generic one. - /// - /// The source connection factory. - /// The generic version of the source factory. - public static IDbConnectionPool AsGeneric(this IDbConnectionPool connectionPool) - => connectionPool is IDbConnectionPool p ? p : new GenericPool(connectionPool); + /// + /// Converts a non-generic connection factory to a generic one. + /// + /// The source connection factory. + /// The generic version of the source factory. + public static IDbConnectionPool AsGeneric(this IDbConnectionPool connectionPool) + => connectionPool is IDbConnectionPool p ? p : new GenericPool(connectionPool); } diff --git a/Source/Core/IExecuteCommand.cs b/Source/Core/IExecuteCommand.cs index b75a30c..c563bc4 100644 --- a/Source/Core/IExecuteCommand.cs +++ b/Source/Core/IExecuteCommand.cs @@ -1,74 +1,70 @@ -using System; -using System.Data; -using System.Threading.Tasks; - -namespace Open.Database.Extensions; +namespace Open.Database.Extensions; /// /// Common interface used for expressive commands. /// public interface IExecuteCommand { - /// - /// Executes a reader on a command with a handler function. - /// - /// The handler function for each IDataRecord. - void Execute(Action action); + /// + /// Executes a reader on a command with a handler function. + /// + /// The handler function for each IDataRecord. + void Execute(Action action); - /// - /// Executes a reader on a command with a transform function. - /// - /// The return type of the transform function. - /// The transform function for each IDataRecord. - /// The result of the transform. - T Execute(Func transform); + /// + /// Executes a reader on a command with a transform function. + /// + /// The return type of the transform function. + /// The transform function for each IDataRecord. + /// The result of the transform. + T Execute(Func transform); - /// - /// Asynchronously executes a reader on a command with a handler function. - /// - /// The handler function for each IDataRecord. - ValueTask ExecuteAsync(Func handler); + /// + /// Asynchronously executes a reader on a command with a handler function. + /// + /// The handler function for each IDataRecord. + ValueTask ExecuteAsync(Func handler); - /// - /// Asynchronously executes a reader on a command with a transform function. - /// - /// The return type of the transform function. - /// The transform function for each IDataRecord. - /// The result of the transform. - ValueTask ExecuteAsync(Func> transform); + /// + /// Asynchronously executes a reader on a command with a transform function. + /// + /// The return type of the transform function. + /// The transform function for each IDataRecord. + /// The result of the transform. + ValueTask ExecuteAsync(Func> transform); } /// /// Common interface used for expressive commands. /// public interface IExecuteCommand : IExecuteCommand - where TCommand : IDbCommand + where TCommand : IDbCommand { - /// - /// Executes a reader on a command with a handler function. - /// - /// The handler function for each IDataRecord. - void Execute(Action action); + /// + /// Executes a reader on a command with a handler function. + /// + /// The handler function for each IDataRecord. + void Execute(Action action); - /// - /// Executes a reader on a command with a transform function. - /// - /// The return type of the transform function. - /// The transform function for each IDataRecord. - /// The result of the transform. - T Execute(Func transform); + /// + /// Executes a reader on a command with a transform function. + /// + /// The return type of the transform function. + /// The transform function for each IDataRecord. + /// The result of the transform. + T Execute(Func transform); - /// - /// Asynchronously executes a reader on a command with a handler function. - /// - /// The handler function for each IDataRecord. - ValueTask ExecuteAsync(Func handler); + /// + /// Asynchronously executes a reader on a command with a handler function. + /// + /// The handler function for each IDataRecord. + ValueTask ExecuteAsync(Func handler); - /// - /// Asynchronously executes a reader on a command with a transform function. - /// - /// The return type of the transform function. - /// The transform function for each IDataRecord. - /// The result of the transform. - ValueTask ExecuteAsync(Func> transform); + /// + /// Asynchronously executes a reader on a command with a transform function. + /// + /// The return type of the transform function. + /// The transform function for each IDataRecord. + /// The result of the transform. + ValueTask ExecuteAsync(Func> transform); } diff --git a/Source/Core/IExecuteReader.cs b/Source/Core/IExecuteReader.cs index 796bd8f..dccd454 100644 --- a/Source/Core/IExecuteReader.cs +++ b/Source/Core/IExecuteReader.cs @@ -1,48 +1,43 @@ -using System; -using System.Data; -using System.Threading; -using System.Threading.Tasks; - -namespace Open.Database.Extensions; +namespace Open.Database.Extensions; /// /// Common interface used for expressive commands when dealing with a data reader. /// public interface IExecuteReader { - /// - /// The cancellation token to use with supported methods. - /// - CancellationToken CancellationToken { get; } + /// + /// The cancellation token to use with supported methods. + /// + CancellationToken CancellationToken { get; } - /// - /// Executes a reader on a command with a handler function. - /// - /// The handler function for the data reader. - /// The command behavior for once the command the reader is complete. - void ExecuteReader(Action handler, CommandBehavior behavior = CommandBehavior.Default); - /// - /// Executes a reader on a command with a transform function. - /// - /// The return type of the transform function. - /// The transform function for each IDataRecord. - /// The command behavior for once the command the reader is complete. - /// The result of the transform. - T ExecuteReader(Func transform, CommandBehavior behavior = CommandBehavior.Default); + /// + /// Executes a reader on a command with a handler function. + /// + /// The handler function for the data reader. + /// The command behavior for once the command the reader is complete. + void ExecuteReader(Action handler, CommandBehavior behavior = CommandBehavior.Default); + /// + /// Executes a reader on a command with a transform function. + /// + /// The return type of the transform function. + /// The transform function for each IDataRecord. + /// The command behavior for once the command the reader is complete. + /// The result of the transform. + T ExecuteReader(Func transform, CommandBehavior behavior = CommandBehavior.Default); - /// - /// Executes a reader on a command with a handler function. - /// - /// The handler function for the data reader. - /// The command behavior for once the command the reader is complete. - ValueTask ExecuteReaderAsync(Func handler, CommandBehavior behavior = CommandBehavior.Default); + /// + /// Executes a reader on a command with a handler function. + /// + /// The handler function for the data reader. + /// The command behavior for once the command the reader is complete. + ValueTask ExecuteReaderAsync(Func handler, CommandBehavior behavior = CommandBehavior.Default); - /// - /// Executes a reader on a command with a handler function. - /// - /// The transform function for each IDataRecord. - /// The command behavior for once the command the reader is complete. - ValueTask ExecuteReaderAsync(Func> transform, CommandBehavior behavior = CommandBehavior.Default); + /// + /// Executes a reader on a command with a handler function. + /// + /// The transform function for each IDataRecord. + /// The command behavior for once the command the reader is complete. + ValueTask ExecuteReaderAsync(Func> transform, CommandBehavior behavior = CommandBehavior.Default); } /// @@ -50,49 +45,47 @@ public interface IExecuteReader /// /// The type of the data reader. public interface IExecuteReader : IExecuteReader - where TReader : IDataReader + where TReader : IDataReader { - /// - /// Executes a reader on a command with a handler function. - /// - /// The handler function for the data reader. - /// The command behavior for once the command the reader is complete. - void ExecuteReader(Action handler, CommandBehavior behavior = CommandBehavior.Default); - /// - /// Executes a reader on a command with a transform function. - /// - /// The return type of the transform function. - /// The transform function for each IDataRecord. - /// The command behavior for once the command the reader is complete. - /// The result of the transform. - T ExecuteReader(Func transform, CommandBehavior behavior = CommandBehavior.Default); + /// + /// Executes a reader on a command with a handler function. + /// + /// The handler function for the data reader. + /// The command behavior for once the command the reader is complete. + void ExecuteReader(Action handler, CommandBehavior behavior = CommandBehavior.Default); + /// + /// Executes a reader on a command with a transform function. + /// + /// The return type of the transform function. + /// The transform function for each IDataRecord. + /// The command behavior for once the command the reader is complete. + /// The result of the transform. + T ExecuteReader(Func transform, CommandBehavior behavior = CommandBehavior.Default); - /// - /// Executes a reader on a command with a handler function. - /// - /// The handler function for the data reader. - /// The command behavior for once the command the reader is complete. - ValueTask ExecuteReaderAsync(Func handler, CommandBehavior behavior = CommandBehavior.Default); + /// + /// Executes a reader on a command with a handler function. + /// + /// The handler function for the data reader. + /// The command behavior for once the command the reader is complete. + ValueTask ExecuteReaderAsync(Func handler, CommandBehavior behavior = CommandBehavior.Default); - /// - /// Executes a reader on a command with a handler function. - /// - /// The handler function for the data reader. - /// The command behavior for once the command the reader is complete. - ValueTask ExecuteReaderAsync(Func> handler, CommandBehavior behavior = CommandBehavior.Default); + /// + /// Executes a reader on a command with a handler function. + /// + /// The handler function for the data reader. + /// The command behavior for once the command the reader is complete. + ValueTask ExecuteReaderAsync(Func> handler, CommandBehavior behavior = CommandBehavior.Default); } /// public interface IExecuteReaderAsync : IExecuteReader { - /// - /// Indicates if reader.ReadAsync() will be used in favor of reader.Read(). - /// - bool UseAsyncRead { get; } + /// + /// Indicates if reader.ReadAsync() will be used in favor of reader.Read(). + /// + bool UseAsyncRead { get; } } /// public interface IExecuteReaderAsync : IExecuteReaderAsync, IExecuteReader - where TReader : IDataReader -{ -} + where TReader : IDataReader; diff --git a/Source/Core/Open.Database.Extensions.Core.csproj b/Source/Core/Open.Database.Extensions.Core.csproj index 32ae733..b39b33b 100644 --- a/Source/Core/Open.Database.Extensions.Core.csproj +++ b/Source/Core/Open.Database.Extensions.Core.csproj @@ -1,53 +1,21 @@ - + - Open.Database.Extensions - netstandard2.0; netstandard2.1 - latest - enable - electricessence - © electricessence (Oren F.) All rights reserved. - MIT - https://github.com/Open-NET-Libraries/Open.Database.Extensions - https://github.com/Open-NET-Libraries/Open.Database.Extensions Useful set of utilities and abstractions for simplifying modern data-access operations and ensuring DI compatibility. ado;ado extensions;sql;connection factory;extensions; - true - git - 7.0.0 - - true - true - snupkg - logo.png - latest - True - README.md - - - - Documentation.xml - - + - - - + + - - - True - - - - True - \ - + + + diff --git a/Source/Core/_Imports.cs b/Source/Core/_Imports.cs new file mode 100644 index 0000000..b7d7d39 --- /dev/null +++ b/Source/Core/_Imports.cs @@ -0,0 +1,10 @@ +global using Open.Database.Extensions.Core; +global using System.Buffers; +global using System.Collections; +global using System.Collections.Immutable; +global using System.Data; +global using System.Data.Common; +global using System.Diagnostics.CodeAnalysis; +global using System.Diagnostics.Contracts; +global using System.Reflection; +global using System.Runtime.CompilerServices; diff --git a/Source/Dataflow/AsSourceBlock.cs b/Source/Dataflow/AsSourceBlock.cs index 6c3f987..ffcce98 100644 --- a/Source/Dataflow/AsSourceBlock.cs +++ b/Source/Dataflow/AsSourceBlock.cs @@ -1,11 +1,4 @@ -using System; -using System.Buffers; -using System.Collections.Generic; -using System.Data; -using System.Threading; -using System.Threading.Tasks.Dataflow; - -namespace Open.Database.Extensions; +namespace Open.Database.Extensions; /// /// Extensions for pipelining data with Dataflow blocks. @@ -25,7 +18,7 @@ static BufferBlock GetBufferBlock(DataflowBlockOptions? options = null) /// The source block containing the results. public static IReceivableSourceBlock AsSourceBlock(this IDataReader reader, DataflowBlockOptions? options = null) { - var buffer = GetBufferBlock(options); + BufferBlock buffer = GetBufferBlock(options); ToTargetBlock(reader, buffer, true); return buffer; } @@ -38,11 +31,11 @@ public static IReceivableSourceBlock AsSourceBlock(this IDataReader re /// The array pool to acquire buffers from. /// Optional DataflowBlockOptions for configuring the source block. /// The source block containing the results. - public static IReceivableSourceBlock AsSourceBlock(this IDataReader reader, - ArrayPool arrayPool, + public static IReceivableSourceBlock AsSourceBlock(this IDataReader reader, + ArrayPool arrayPool, DataflowBlockOptions? options = null) { - var buffer = GetBufferBlock(options); + BufferBlock buffer = GetBufferBlock(options); ToTargetBlock(reader, buffer, true, arrayPool); return buffer; } @@ -60,7 +53,7 @@ public static IReceivableSourceBlock AsSourceBlock(this IDataReader reader Func transform, DataflowBlockOptions? options = null) { - var buffer = GetBufferBlock(options); + BufferBlock buffer = GetBufferBlock(options); ToTargetBlock(reader, buffer, true, transform); return buffer; } @@ -77,7 +70,7 @@ public static IReceivableSourceBlock AsSourceBlock(this IDataReader reader DataflowBlockOptions? options = null) where T : new() { - var buffer = GetBufferBlock(options); + BufferBlock buffer = GetBufferBlock(options); ToTargetBlock(reader, buffer, true); return buffer; } @@ -96,7 +89,7 @@ public static IReceivableSourceBlock AsSourceBlock(this IDataReader reader DataflowBlockOptions? options = null) where T : new() { - var buffer = GetBufferBlock(options); + BufferBlock buffer = GetBufferBlock(options); ToTargetBlock(reader, buffer, true, fieldMappingOverrides); return buffer; } @@ -113,7 +106,7 @@ public static IReceivableSourceBlock AsSourceBlock(this IDataReader reader public static IReceivableSourceBlock AsSourceBlock(this IDbCommand command, DataflowBlockOptions? options = null) { - var buffer = GetBufferBlock(options); + BufferBlock buffer = GetBufferBlock(options); ToTargetBlock(command, buffer, true); return buffer; } @@ -128,11 +121,11 @@ public static IReceivableSourceBlock AsSourceBlock(this IDbCommand com /// The array pool to acquire buffers from. /// Optional DataflowBlockOptions for configuring the source block. /// The source block containing the results. - public static IReceivableSourceBlock AsSourceBlock(this IDbCommand command, - ArrayPool arrayPool, + public static IReceivableSourceBlock AsSourceBlock(this IDbCommand command, + ArrayPool arrayPool, DataflowBlockOptions? options = null) { - var buffer = GetBufferBlock(options); + BufferBlock buffer = GetBufferBlock(options); ToTargetBlock(command, buffer, true, arrayPool); return buffer; } @@ -152,7 +145,7 @@ public static IReceivableSourceBlock AsSourceBlock(this IDbCommand command Func transform, DataflowBlockOptions? options = null) { - var buffer = GetBufferBlock(options); + BufferBlock buffer = GetBufferBlock(options); ToTargetBlock(command, buffer, true, transform); return buffer; } @@ -171,7 +164,7 @@ public static IReceivableSourceBlock AsSourceBlock(this IDbCommand command DataflowBlockOptions? options = null) where T : new() { - var buffer = GetBufferBlock(options); + BufferBlock buffer = GetBufferBlock(options); ToTargetBlock(command, buffer, true); return buffer; } @@ -192,7 +185,7 @@ public static IReceivableSourceBlock AsSourceBlock(this IDbCommand command DataflowBlockOptions? options = null) where T : new() { - var buffer = GetBufferBlock(options); + BufferBlock buffer = GetBufferBlock(options); ToTargetBlock(command, buffer, true, fieldMappingOverrides); return buffer; } @@ -209,7 +202,7 @@ public static IReceivableSourceBlock AsSourceBlock(this IDbCommand command public static IReceivableSourceBlock AsSourceBlock(this IExecuteReader command, DataflowBlockOptions? options = null) { - var buffer = GetBufferBlock(options); + BufferBlock buffer = GetBufferBlock(options); ToTargetBlock(command, buffer, true); return buffer; } @@ -224,11 +217,11 @@ public static IReceivableSourceBlock AsSourceBlock(this IExecuteReader /// The array pool to acquire buffers from. /// Optional DataflowBlockOptions for configuring the source block. /// The source block containing the results. - public static IReceivableSourceBlock AsSourceBlock(this IExecuteReader command, - ArrayPool arrayPool, + public static IReceivableSourceBlock AsSourceBlock(this IExecuteReader command, + ArrayPool arrayPool, DataflowBlockOptions? options = null) { - var buffer = GetBufferBlock(options); + BufferBlock buffer = GetBufferBlock(options); ToTargetBlock(command, buffer, true, arrayPool); return buffer; } @@ -248,7 +241,7 @@ public static IReceivableSourceBlock AsSourceBlock(this IExecuteReader com Func transform, DataflowBlockOptions? options = null) { - var buffer = GetBufferBlock(options); + BufferBlock buffer = GetBufferBlock(options); ToTargetBlock(command, buffer, true, transform); return buffer; } @@ -267,7 +260,7 @@ public static IReceivableSourceBlock AsSourceBlock(this IExecuteReader com DataflowBlockOptions? options = null) where T : new() { - var buffer = GetBufferBlock(options); + BufferBlock buffer = GetBufferBlock(options); ToTargetBlock(command, buffer, true); return buffer; } @@ -288,7 +281,7 @@ public static IReceivableSourceBlock AsSourceBlock(this IExecuteReader com DataflowBlockOptions? options = null) where T : new() { - var buffer = GetBufferBlock(options); + BufferBlock buffer = GetBufferBlock(options); ToTargetBlock(command, buffer, true, fieldMappingOverrides); return buffer; } @@ -305,7 +298,7 @@ public static IReceivableSourceBlock AsSourceBlockAsync(this IDataRead DataflowBlockOptions? options = null, CancellationToken cancellationToken = default) { - var buffer = GetBufferBlock(options); + BufferBlock buffer = GetBufferBlock(options); _ = ToTargetBlockAsync(reader, buffer, true, cancellationToken); return buffer; } @@ -319,12 +312,12 @@ public static IReceivableSourceBlock AsSourceBlockAsync(this IDataRead /// Optional DataflowBlockOptions for configuring the source block. /// An optional cancellation token. /// The source block containing the results. - public static IReceivableSourceBlock AsSourceBlockAsync(this IDataReader reader, - ArrayPool arrayPool, + public static IReceivableSourceBlock AsSourceBlockAsync(this IDataReader reader, + ArrayPool arrayPool, DataflowBlockOptions? options = null, CancellationToken cancellationToken = default) { - var buffer = GetBufferBlock(options); + BufferBlock buffer = GetBufferBlock(options); _ = ToTargetBlockAsync(reader, buffer, true, arrayPool, cancellationToken); return buffer; } @@ -344,7 +337,7 @@ public static IReceivableSourceBlock AsSourceBlockAsync(this IDataReader r DataflowBlockOptions? options = null, CancellationToken cancellationToken = default) { - var buffer = GetBufferBlock(options); + BufferBlock buffer = GetBufferBlock(options); _ = ToTargetBlockAsync(reader, buffer, true, transform, cancellationToken); return buffer; } @@ -363,7 +356,7 @@ public static IReceivableSourceBlock AsSourceBlockAsync(this IDataReader r CancellationToken cancellationToken = default) where T : new() { - var buffer = GetBufferBlock(options); + BufferBlock buffer = GetBufferBlock(options); _ = ToTargetBlockAsync(reader, buffer, true, cancellationToken); return buffer; } @@ -384,7 +377,7 @@ public static IReceivableSourceBlock AsSourceBlockAsync(this IDataReader r CancellationToken cancellationToken = default) where T : new() { - var buffer = GetBufferBlock(options); + BufferBlock buffer = GetBufferBlock(options); _ = ToTargetBlockAsync(reader, buffer, true, fieldMappingOverrides, cancellationToken); return buffer; } @@ -402,7 +395,7 @@ public static IReceivableSourceBlock AsSourceBlockAsync(this IDbComman DataflowBlockOptions? options = null, CancellationToken cancellationToken = default) { - var buffer = GetBufferBlock(options); + BufferBlock buffer = GetBufferBlock(options); _ = ToTargetBlockAsync(command, buffer, true, cancellationToken); return buffer; } @@ -417,12 +410,12 @@ public static IReceivableSourceBlock AsSourceBlockAsync(this IDbComman /// Optional DataflowBlockOptions for configuring the source block. /// An optional cancellation token. /// The source block containing the results. - public static IReceivableSourceBlock AsSourceBlockAsync(this IDbCommand command, - ArrayPool arrayPool, + public static IReceivableSourceBlock AsSourceBlockAsync(this IDbCommand command, + ArrayPool arrayPool, DataflowBlockOptions? options = null, CancellationToken cancellationToken = default) { - var buffer = GetBufferBlock(options); + BufferBlock buffer = GetBufferBlock(options); _ = ToTargetBlockAsync(command, buffer, true, arrayPool, cancellationToken); return buffer; } @@ -443,7 +436,7 @@ public static IReceivableSourceBlock AsSourceBlockAsync(this IDbCommand co DataflowBlockOptions? options = null, CancellationToken cancellationToken = default) { - var buffer = GetBufferBlock(options); + BufferBlock buffer = GetBufferBlock(options); _ = ToTargetBlockAsync(command, buffer, true, transform, cancellationToken); return buffer; } @@ -464,7 +457,7 @@ public static IReceivableSourceBlock AsSourceBlockAsync(this IDbCommand co CancellationToken cancellationToken = default) where T : new() { - var buffer = GetBufferBlock(options); + BufferBlock buffer = GetBufferBlock(options); _ = ToTargetBlockAsync(command, buffer, true, cancellationToken); return buffer; } @@ -487,7 +480,7 @@ public static IReceivableSourceBlock AsSourceBlockAsync(this IDbCommand co CancellationToken cancellationToken = default) where T : new() { - var buffer = GetBufferBlock(options); + BufferBlock buffer = GetBufferBlock(options); _ = ToTargetBlockAsync(command, buffer, true, fieldMappingOverrides, cancellationToken); return buffer; } @@ -503,7 +496,7 @@ public static IReceivableSourceBlock AsSourceBlockAsync(this IDbCommand co public static IReceivableSourceBlock AsSourceBlockAsync(this IExecuteReader command, DataflowBlockOptions? options = null) { - var buffer = GetBufferBlock(options); + BufferBlock buffer = GetBufferBlock(options); _ = ToTargetBlockAsync(command, buffer, true); return buffer; } @@ -517,11 +510,11 @@ public static IReceivableSourceBlock AsSourceBlockAsync(this IExecuteR /// The array pool to acquire buffers from. /// Optional DataflowBlockOptions for configuring the source block. /// The source block containing the results. - public static IReceivableSourceBlock AsSourceBlockAsync(this IExecuteReader command, - ArrayPool arrayPool, + public static IReceivableSourceBlock AsSourceBlockAsync(this IExecuteReader command, + ArrayPool arrayPool, DataflowBlockOptions? options = null) { - var buffer = GetBufferBlock(options); + BufferBlock buffer = GetBufferBlock(options); _ = ToTargetBlockAsync(command, buffer, true, arrayPool); return buffer; } @@ -540,7 +533,7 @@ public static IReceivableSourceBlock AsSourceBlockAsync(this IExecuteReade Func transform, DataflowBlockOptions? options = null) { - var buffer = GetBufferBlock(options); + BufferBlock buffer = GetBufferBlock(options); _ = ToTargetBlockAsync(command, buffer, true, transform); return buffer; } @@ -558,7 +551,7 @@ public static IReceivableSourceBlock AsSourceBlockAsync(this IExecuteReade DataflowBlockOptions? options = null) where T : new() { - var buffer = GetBufferBlock(options); + BufferBlock buffer = GetBufferBlock(options); _ = ToTargetBlockAsync(command, buffer, true); return buffer; } @@ -578,7 +571,7 @@ public static IReceivableSourceBlock AsSourceBlockAsync(this IExecuteReade DataflowBlockOptions? options = null) where T : new() { - var buffer = GetBufferBlock(options); + BufferBlock buffer = GetBufferBlock(options); _ = ToTargetBlockAsync(command, buffer, true, fieldMappingOverrides); return buffer; } diff --git a/Source/Dataflow/Open.Database.Extensions.Dataflow.csproj b/Source/Dataflow/Open.Database.Extensions.Dataflow.csproj index 703cda3..b934f87 100644 --- a/Source/Dataflow/Open.Database.Extensions.Dataflow.csproj +++ b/Source/Dataflow/Open.Database.Extensions.Dataflow.csproj @@ -1,49 +1,20 @@  - Open.Database.Extensions.Dataflow - netstandard2.0; netstandard2.1 - latest - enable - electricessence - © electricessence (Oren F.) All rights reserved. - MIT - https://github.com/Open-NET-Libraries/Open.Database.Extensions - https://github.com/Open-NET-Libraries/Open.Database.Extensions Useful set of utilities and abstractions for simplifying modern data-access operations with Dataflow and ensuring DI compatibility. ado;ado extensions;dataflow;data;database - git - true - 7.0.0 - - true - true - snupkg - logo.png - latest - True - README.md - - Documentation.xml - + + + - - - + - - True - - - - True - \ - + diff --git a/Source/Dataflow/ToTargetBlock.cs b/Source/Dataflow/ToTargetBlock.cs index bb82456..ab3a476 100644 --- a/Source/Dataflow/ToTargetBlock.cs +++ b/Source/Dataflow/ToTargetBlock.cs @@ -1,15 +1,4 @@ -using Open.Database.Extensions.Dataflow; -using System; -using System.Buffers; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; -using System.Diagnostics.Contracts; -using System.Threading; -using System.Threading.Tasks; -using System.Threading.Tasks.Dataflow; - -namespace Open.Database.Extensions; +namespace Open.Database.Extensions; /// /// Extensions for pipelining data with Dataflow blocks. @@ -34,8 +23,8 @@ public static long ToTargetBlock(this IDataReader reader, try { - var fieldCount = reader.FieldCount; - var total = 0; + int fieldCount = reader.FieldCount; + int total = 0; while (reader.Read() && target.Post(reader.GetValues(fieldCount))) total++; if (complete) target.Complete(); return total; @@ -57,9 +46,9 @@ public static long ToTargetBlock(this IDataReader reader, /// The array pool to acquire buffers from. /// The number of records processed. public static long ToTargetBlock(this IDataReader reader, - ITargetBlock target, + ITargetBlock target, bool complete, - ArrayPool arrayPool) + ArrayPool arrayPool) { if (reader is null) throw new ArgumentNullException(nameof(reader)); if (target is null) throw new ArgumentNullException(nameof(target)); @@ -68,8 +57,8 @@ public static long ToTargetBlock(this IDataReader reader, try { - var fieldCount = reader.FieldCount; - var total = 0; + int fieldCount = reader.FieldCount; + int total = 0; while (reader.Read() && target.Post(reader.GetValues(fieldCount, arrayPool))) total++; if (complete) target.Complete(); return total; @@ -103,7 +92,7 @@ public static long ToTargetBlock(this IDataReader reader, try { - var total = 0; + int total = 0; while (reader.Read() && target.Post(transform(reader))) total++; if (complete) target.Complete(); return total; @@ -181,6 +170,7 @@ public static long ToTargetBlock(this IDbCommand command, complete = false; target.Fault(ex); } + throw; } finally @@ -202,9 +192,9 @@ public static long ToTargetBlock(this IDbCommand command, /// The array pool to acquire buffers from. /// The number of records processed. public static long ToTargetBlock(this IDbCommand command, - ITargetBlock target, + ITargetBlock target, bool complete, - ArrayPool arrayPool) + ArrayPool arrayPool) { if (command is null) throw new ArgumentNullException(nameof(command)); if (target is null) throw new ArgumentNullException(nameof(target)); @@ -223,6 +213,7 @@ public static long ToTargetBlock(this IDbCommand command, complete = false; target.Fault(ex); } + throw; } finally @@ -266,6 +257,7 @@ public static long ToTargetBlock(this IDbCommand command, complete = false; target.Fault(ex); } + throw; } finally @@ -307,6 +299,7 @@ public static long ToTargetBlock(this IDbCommand command, complete = false; target.Fault(ex); } + throw; } finally @@ -350,6 +343,7 @@ public static long ToTargetBlock(this IDbCommand command, complete = false; target.Fault(ex); } + throw; } finally @@ -389,6 +383,7 @@ public static long ToTargetBlock(this IExecuteReader command, complete = false; target.Fault(ex); } + throw; } finally @@ -410,9 +405,9 @@ public static long ToTargetBlock(this IExecuteReader command, /// The array pool to acquire buffers from. /// The number of records processed. public static long ToTargetBlock(this IExecuteReader command, - ITargetBlock target, + ITargetBlock target, bool complete, - ArrayPool arrayPool) + ArrayPool arrayPool) { if (command is null) throw new ArgumentNullException(nameof(command)); if (target is null) throw new ArgumentNullException(nameof(target)); @@ -431,6 +426,7 @@ public static long ToTargetBlock(this IExecuteReader command, complete = false; target.Fault(ex); } + throw; } finally @@ -474,6 +470,7 @@ public static long ToTargetBlock(this IExecuteReader command, complete = false; target.Fault(ex); } + throw; } finally @@ -515,6 +512,7 @@ public static long ToTargetBlock(this IExecuteReader command, complete = false; target.Fault(ex); } + throw; } finally @@ -558,6 +556,7 @@ public static long ToTargetBlock(this IExecuteReader command, complete = false; target.Fault(ex); } + throw; } finally @@ -587,8 +586,8 @@ public static async ValueTask ToTargetBlockAsync(this IDataReader reader, try { - var fieldCount = reader.FieldCount; - var total = 0; + int fieldCount = reader.FieldCount; + int total = 0; if (reader is DbDataReader r) { while ( @@ -607,6 +606,7 @@ await r.ReadAsync(cancellationToken).ConfigureAwait(false) total++; } } + if (complete) target.Complete(); return total; } @@ -628,9 +628,9 @@ await r.ReadAsync(cancellationToken).ConfigureAwait(false) /// An optional cancellation token. /// The number of records processed. public static async ValueTask ToTargetBlockAsync(this IDataReader reader, - ITargetBlock target, + ITargetBlock target, bool complete, - ArrayPool arrayPool, + ArrayPool arrayPool, CancellationToken cancellationToken = default) { if (reader is null) throw new ArgumentNullException(nameof(reader)); @@ -640,8 +640,8 @@ public static async ValueTask ToTargetBlockAsync(this IDataReader reader, try { - var fieldCount = reader.FieldCount; - var total = 0; + int fieldCount = reader.FieldCount; + int total = 0; if (reader is DbDataReader r) { while ( @@ -660,6 +660,7 @@ await r.ReadAsync(cancellationToken).ConfigureAwait(false) total++; } } + if (complete) target.Complete(); return total; } @@ -694,7 +695,7 @@ public static async ValueTask ToTargetBlockAsync(this IDataReader reade try { - var total = 0; + int total = 0; if (reader is DbDataReader r) { while ( @@ -713,6 +714,7 @@ await r.ReadAsync(cancellationToken).ConfigureAwait(false) total++; } } + if (complete) target.Complete(); return total; } @@ -780,6 +782,7 @@ public static async ValueTask ToTargetBlockAsync(this IDbCommand command, { if (command is null) throw new ArgumentNullException(nameof(command)); if (target is null) throw new ArgumentNullException(nameof(target)); + if (command.Connection is null) throw new InvalidOperationException("Command has no connection."); Contract.EndContractBlock(); if (!command.Connection.State.HasFlag(ConnectionState.Open)) @@ -797,6 +800,7 @@ public static async ValueTask ToTargetBlockAsync(this IDbCommand command, complete = false; target.Fault(ex); } + throw; } finally @@ -818,14 +822,15 @@ public static async ValueTask ToTargetBlockAsync(this IDbCommand command, /// An optional cancellation token. /// The number of records processed. public static async ValueTask ToTargetBlockAsync(this IDbCommand command, - ITargetBlock target, + ITargetBlock target, bool complete, - ArrayPool arrayPool, + ArrayPool arrayPool, CancellationToken cancellationToken = default) { if (command is null) throw new ArgumentNullException(nameof(command)); if (target is null) throw new ArgumentNullException(nameof(target)); if (arrayPool is null) throw new ArgumentNullException(nameof(arrayPool)); + if (command.Connection is null) throw new InvalidOperationException("Command has no connection."); Contract.EndContractBlock(); if (!command.Connection.State.HasFlag(ConnectionState.Open)) @@ -843,6 +848,7 @@ public static async ValueTask ToTargetBlockAsync(this IDbCommand command, complete = false; target.Fault(ex); } + throw; } finally @@ -872,6 +878,7 @@ public static async ValueTask ToTargetBlockAsync(this IDbCommand comman { if (command is null) throw new ArgumentNullException(nameof(command)); if (target is null) throw new ArgumentNullException(nameof(target)); + if (command.Connection is null) throw new InvalidOperationException("Command has no connection."); Contract.EndContractBlock(); if (!command.Connection.State.HasFlag(ConnectionState.Open)) @@ -889,6 +896,7 @@ public static async ValueTask ToTargetBlockAsync(this IDbCommand comman complete = false; target.Fault(ex); } + throw; } finally @@ -917,6 +925,7 @@ public static async ValueTask ToTargetBlockAsync(this IDbCommand comman { if (command is null) throw new ArgumentNullException(nameof(command)); if (target is null) throw new ArgumentNullException(nameof(target)); + if (command.Connection is null) throw new InvalidOperationException("Command has no connection."); Contract.EndContractBlock(); if (!command.Connection.State.HasFlag(ConnectionState.Open)) @@ -925,10 +934,10 @@ public static async ValueTask ToTargetBlockAsync(this IDbCommand comman try { var dbc = command as DbCommand; - var state = dbc == null ? command.Connection.EnsureOpen() : await dbc.Connection.EnsureOpenAsync(cancellationToken).ConfigureAwait(false); - var behavior = CommandBehavior.SingleResult; + ConnectionState state = dbc == null ? command.Connection.EnsureOpen() : await dbc.Connection!.EnsureOpenAsync(cancellationToken).ConfigureAwait(false); + CommandBehavior behavior = CommandBehavior.SingleResult; if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var reader = dbc == null ? command.ExecuteReader(behavior) : await dbc.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(false); + using IDataReader reader = dbc == null ? command.ExecuteReader(behavior) : await dbc.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(false); return await ToTargetBlockAsync(reader, target, false, cancellationToken).ConfigureAwait(false); } catch (Exception ex) @@ -938,6 +947,7 @@ public static async ValueTask ToTargetBlockAsync(this IDbCommand comman complete = false; target.Fault(ex); } + throw; } finally @@ -968,6 +978,7 @@ public static async ValueTask ToTargetBlockAsync(this IDbCommand comman { if (command is null) throw new ArgumentNullException(nameof(command)); if (target is null) throw new ArgumentNullException(nameof(target)); + if (command.Connection is null) throw new InvalidOperationException("Command has no connection."); Contract.EndContractBlock(); if (!command.Connection.State.HasFlag(ConnectionState.Open)) @@ -976,10 +987,10 @@ public static async ValueTask ToTargetBlockAsync(this IDbCommand comman try { var dbc = command as DbCommand; - var state = dbc == null ? command.Connection.EnsureOpen() : await dbc.Connection.EnsureOpenAsync(cancellationToken).ConfigureAwait(false); - var behavior = CommandBehavior.SingleResult; + ConnectionState state = dbc == null ? command.Connection.EnsureOpen() : await dbc.Connection!.EnsureOpenAsync(cancellationToken).ConfigureAwait(false); + CommandBehavior behavior = CommandBehavior.SingleResult; if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; - using var reader = dbc == null ? command.ExecuteReader(behavior) : await dbc.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(false); + using IDataReader reader = dbc == null ? command.ExecuteReader(behavior) : await dbc.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(false); return await ToTargetBlockAsync(reader, target, false, fieldMappingOverrides, cancellationToken).ConfigureAwait(false); } catch (Exception ex) @@ -989,6 +1000,7 @@ public static async ValueTask ToTargetBlockAsync(this IDbCommand comman complete = false; target.Fault(ex); } + throw; } finally @@ -1028,6 +1040,7 @@ public static async ValueTask ToTargetBlockAsync(this IExecuteReader comma complete = false; target.Fault(ex); } + throw; } finally @@ -1048,9 +1061,9 @@ public static async ValueTask ToTargetBlockAsync(this IExecuteReader comma /// The array pool to acquire buffers from. /// The number of records processed. public static async ValueTask ToTargetBlockAsync(this IExecuteReader command, - ITargetBlock target, + ITargetBlock target, bool complete, - ArrayPool arrayPool) + ArrayPool arrayPool) { if (command is null) throw new ArgumentNullException(nameof(command)); if (target is null) throw new ArgumentNullException(nameof(target)); @@ -1070,6 +1083,7 @@ public static async ValueTask ToTargetBlockAsync(this IExecuteReader comma complete = false; target.Fault(ex); } + throw; } finally @@ -1112,6 +1126,7 @@ public static async ValueTask ToTargetBlockAsync(this IExecuteReader co complete = false; target.Fault(ex); } + throw; } finally @@ -1153,6 +1168,7 @@ public static async ValueTask ToTargetBlockAsync(this IExecuteReader co complete = false; target.Fault(ex); } + throw; } finally @@ -1196,6 +1212,7 @@ public static async ValueTask ToTargetBlockAsync(this IExecuteReader co complete = false; target.Fault(ex); } + throw; } finally diff --git a/Source/Dataflow/Transformer.cs b/Source/Dataflow/Transformer.cs index c879512..2b5c894 100644 --- a/Source/Dataflow/Transformer.cs +++ b/Source/Dataflow/Transformer.cs @@ -1,28 +1,14 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Data; -using System.Diagnostics.Contracts; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Threading.Tasks.Dataflow; +namespace Open.Database.Extensions; -namespace Open.Database.Extensions.Dataflow; - -internal class Transformer : Core.Transformer - where T : new() +/// +internal class Transformer(IEnumerable<(string Field, string? Column)>? fieldMappingOverrides = null) + : Core.Transformer(fieldMappingOverrides) + where T : new() { - /// - public Transformer(IEnumerable<(string Field, string? Column)>? fieldMappingOverrides = null) - : base(fieldMappingOverrides) - { - } - /// /// Static utility for creating a Transformer . /// - /// + /// An optional override map of field names to column names where the keys are the property names, and values are the column names. public static new Transformer Create(IEnumerable<(string Field, string? Column)>? fieldMappingOverrides = null) => new(fieldMappingOverrides); @@ -44,15 +30,15 @@ internal long PipeResultsTo( if (target is null) throw new ArgumentNullException(nameof(target)); Contract.EndContractBlock(); - var columns = reader.GetMatchingOrdinals(ColumnNames, true); + (string Name, int Ordinal)[] columns = reader.GetMatchingOrdinals(ColumnNames, true); var ordinals = columns.Select(m => m.Ordinal).ToImmutableArray(); var names = columns.Select(m => m.Name).ToImmutableArray(); var processor = new Processor(this, names); - var transform = processor.Transform; - var columnCount = columns.Length; + Func transform = processor.Transform; + int columnCount = columns.Length; - var transformBlock = new TransformBlock( + var transformBlock = new TransformBlock( a => { try @@ -108,15 +94,15 @@ internal async ValueTask PipeResultsToAsync( if (target is null) throw new ArgumentNullException(nameof(target)); Contract.EndContractBlock(); - var columns = reader.GetMatchingOrdinals(ColumnNames, true); + (string Name, int Ordinal)[] columns = reader.GetMatchingOrdinals(ColumnNames, true); var ordinals = columns.Select(m => m.Ordinal).ToImmutableArray(); var names = columns.Select(m => m.Name).ToImmutableArray(); var processor = new Processor(this, names); - var transform = processor.Transform; - var columnCount = columns.Length; + Func transform = processor.Transform; + int columnCount = columns.Length; - var transformBlock = new TransformBlock( + var transformBlock = new TransformBlock( a => { try diff --git a/Source/Dataflow/_Imports.cs b/Source/Dataflow/_Imports.cs new file mode 100644 index 0000000..979d9b8 --- /dev/null +++ b/Source/Dataflow/_Imports.cs @@ -0,0 +1,6 @@ +global using System.Buffers; +global using System.Collections.Immutable; +global using System.Data; +global using System.Data.Common; +global using System.Diagnostics.Contracts; +global using System.Threading.Tasks.Dataflow; \ No newline at end of file diff --git a/Source/Directory.Build.props b/Source/Directory.Build.props new file mode 100644 index 0000000..4100f8d --- /dev/null +++ b/Source/Directory.Build.props @@ -0,0 +1,49 @@ + + + + Open.Database.Extensions + + 9.0.3 + debug + + netstandard2.0; netstandard2.1; net8.0; net9.0; + latest + enable + true + + electricessence + © electricessence (Oren F.) All rights reserved. + MIT + https://github.com/Open-NET-Libraries/Open.Database.Extensions + https://github.com/Open-NET-Libraries/Open.Database.Extensions + + true + git + true + true + snupkg + True + latest + true + + README.md + logo.png + + + + + True + \ + + + True + \ + + + + + + $(NoWarn);nullable + + + \ No newline at end of file diff --git a/Source/Extensions/Open.Database.Extensions.csproj b/Source/Extensions/Open.Database.Extensions.csproj index 017ea60..1bdb35e 100644 --- a/Source/Extensions/Open.Database.Extensions.csproj +++ b/Source/Extensions/Open.Database.Extensions.csproj @@ -1,43 +1,15 @@  - Open.Database.Extensions - netstandard2.0; netstandard2.1 - latest - enable - electricessence - © electricessence (Oren F.) All rights reserved. - MIT - https://github.com/Open-NET-Libraries/Open.Database.Extensions - https://github.com/Open-NET-Libraries/Open.Database.Extensions - Useful set of utilities and abstractions for simplifying modern data-access operations and ensuring DI compatibility. This now is a meta-package containing a collection of extensions. + + Useful set of utilities and abstractions for simplifying modern data-access operations and ensuring DI compatibility. + This is simply a meta-package that includes Open.Database.Extensions.Core. + ado;ado extensions;sql;connection factory;extensions; - true - git - 7.0.0 - - true - true - snupkg - logo.png - README.md - - - - - - - - True - - - - True - \ - - - + + + diff --git a/Source/MSSqlClient/Constants.cs b/Source/MSSqlClient/Constants.cs new file mode 100644 index 0000000..3d803cd --- /dev/null +++ b/Source/MSSqlClient/Constants.cs @@ -0,0 +1,11 @@ +namespace Open.Database.Extensions; + +internal static class ConnectionTimeout +{ + public const ushort DEFAULT_SECONDS = 30; +} + +internal static class CommandTimeout +{ + public const ushort DEFAULT_SECONDS = 60; +} diff --git a/Source/MSSqlClient/ExpressiveSqlCommand.cs b/Source/MSSqlClient/ExpressiveSqlCommand.cs new file mode 100644 index 0000000..f7308fb --- /dev/null +++ b/Source/MSSqlClient/ExpressiveSqlCommand.cs @@ -0,0 +1,90 @@ +namespace Open.Database.Extensions; + +/// +/// A specialized for SqlClient abstraction for executing commands on a database using best practices and simplified expressive syntax. +/// +public class ExpressiveSqlCommand : ExpressiveDbCommandBase +{ + /// Constructs a . + /// + public ExpressiveSqlCommand( + IDbConnectionPool connectionPool, + CommandType type, + string command, + IEnumerable? @params = null) + : base(connectionPool, type, command, @params) + { + } + + /// Constructs a . + /// + public ExpressiveSqlCommand( + IDbConnectionFactory connFactory, + CommandType type, + string command, + IEnumerable? @params = null) + : base(connFactory, type, command, @params) + { + } + + /// Constructs a . + /// + public ExpressiveSqlCommand( + SqlConnection connection, + IDbTransaction? transaction, + CommandType type, + string command, + IEnumerable? @params = null) + : base(connection, transaction, type, command, @params) + { + } + + /// Constructs a . + /// + public ExpressiveSqlCommand( + SqlConnection connection, + CommandType type, + string command, + IEnumerable? @params = null) + : base(connection, type, command, @params) + { + } + + /// Constructs a . + /// + public ExpressiveSqlCommand( + IDbTransaction transaction, + CommandType type, + string command, + IEnumerable? @params = null) + : base(transaction, type, command, @params) + { + } + + /// + /// Handles adding the list of parameters to a new command. + /// + protected override void AddParams(SqlCommand command) + { + if (command is null) throw new System.ArgumentNullException(nameof(command)); + Contract.EndContractBlock(); + + foreach (Param p in Params) + { + SqlParameter np; + if (p.Value is null) + { + np = command.CreateParameter(); + np.ParameterName = p.Name; + } + else + { + np = command.Parameters.AddWithValue(p.Name, p.Value); + } + + np.Direction = p.Direction; + if (p.Type.HasValue) + np.SqlDbType = p.Type.Value; + } + } +} diff --git a/Source/MSSqlClient/Extensions/Command.AddParameter.cs b/Source/MSSqlClient/Extensions/Command.AddParameter.cs new file mode 100644 index 0000000..5cb8d7d --- /dev/null +++ b/Source/MSSqlClient/Extensions/Command.AddParameter.cs @@ -0,0 +1,82 @@ +namespace Open.Database.Extensions; + +/// +/// SqlClient extensions for building a command and retrieving data using best practices. +/// +public static partial class SqlCommandExtensions +{ + /// + /// Shortcut for adding command parameter. + /// + /// The command to add a parameter to. + /// The name of the parameter. + /// The value of the parameter. + /// The DbType of the parameter. + /// The direction of the parameter. + /// The created IDbDataParameter. + public static SqlParameter AddParameter( + this SqlCommand target, + string name, + object? value, + SqlDbType type, + ParameterDirection direction = ParameterDirection.Input) + { + if (target is null) throw new ArgumentNullException(nameof(target)); + Contract.EndContractBlock(); + + SqlParameter p = AddParameterType(target, name, type, direction); + p.Value = value; + return p; + } + + /// + /// Shortcut for adding command a typed (non-input) parameter. + /// + /// The command to add a parameter to. + /// The name of the parameter. + /// The SqlDbType of the parameter. + /// The direction of the parameter. + /// The created IDbDataParameter. + public static SqlParameter AddParameterType( + this SqlCommand target, + string? name, + SqlDbType type, + ParameterDirection direction = ParameterDirection.Input) + { + if (target is null) throw new ArgumentNullException(nameof(target)); + if (direction != ParameterDirection.ReturnValue && name == null) + throw new ArgumentNullException(nameof(name), "Parameter names can only be null for a return parameter."); + else if (name != null && string.IsNullOrWhiteSpace(name)) + throw new ArgumentException("Parameter names cannot be empty or white space.", nameof(name)); + Contract.EndContractBlock(); + + SqlParameter c = target.CreateParameter(); + c.ParameterName = name; + c.SqlDbType = type; + c.Direction = direction; + target.Parameters.Add(c); + return c; + } + + /// + /// Shortcut for adding command a typed (non-input) parameter. + /// + /// The command to add a parameter to. + /// The name of the parameter. + /// The SqlDbType of the parameter. + /// The created IDbDataParameter. + public static SqlParameter AddParameterType(this IDbCommand target, + string name, SqlDbType type) + => AddParameterType((SqlCommand)target, name, type); + + /// + /// Shortcut for adding command a typed return parameter. + /// + /// The command to add a parameter to. + /// The SqlDbType of the parameter. + /// The name of the parameter. + /// The created IDbDataParameter. + public static SqlParameter AddReturnParameter(this SqlCommand target, + SqlDbType type, string? name = null) + => AddParameterType(target, name, type, ParameterDirection.ReturnValue); +} diff --git a/Source/MSSqlClient/Extensions/Command.ExecuteReader.cs b/Source/MSSqlClient/Extensions/Command.ExecuteReader.cs new file mode 100644 index 0000000..47caedf --- /dev/null +++ b/Source/MSSqlClient/Extensions/Command.ExecuteReader.cs @@ -0,0 +1,159 @@ +using System.Data.Common; +using System.Runtime.CompilerServices; + +namespace Open.Database.Extensions; + +/// +/// SqlClient extensions for building a command and retrieving data using best practices. +/// +public static partial class SqlCommandExtensions +{ + #region Connection.EnsureOpen Shortcuts. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ConnectionState EnsureOpen(this IDbCommand command) + { +#if DEBUG + if (command.Connection is null) throw new InvalidOperationException("Cannot execute a command with a null connection."); +#endif + return command.Connection!.EnsureOpen(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ValueTask EnsureOpenAsync(this IDbCommand command, CancellationToken cancellationToken) + { +#if DEBUG + if (command.Connection is null) throw new ArgumentException("Cannot execute a command with a null connection."); +#endif + return command.Connection!.EnsureOpenAsync(cancellationToken); + } + #endregion + + /// The to generate a reader from. + /// The handler function for the . + /// The behavior to use with the data reader. + /// + public static void ExecuteReader(this SqlCommand command, Action handler, CommandBehavior behavior = CommandBehavior.Default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (handler is null) throw new ArgumentNullException(nameof(handler)); + Contract.EndContractBlock(); + + ConnectionState state = command.EnsureOpen(); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; + using SqlDataReader reader = command.ExecuteReader(behavior); + handler(reader); + } + + /// The to generate a reader from. + /// The transform function for each . + /// The behavior to use with the data reader. + /// + public static T ExecuteReader(this SqlCommand command, Func transform, CommandBehavior behavior = CommandBehavior.Default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + ConnectionState state = command.EnsureOpen(); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; + using SqlDataReader reader = command.ExecuteReader(behavior); + return transform(reader); + } + + /// The to generate a reader from. + /// The handler function for the . + /// The behavior to use with the data reader. + /// The cancellation token. + /// + public static async ValueTask ExecuteReaderAsync(this SqlCommand command, + Func handler, + CommandBehavior behavior = CommandBehavior.Default, + CancellationToken cancellationToken = default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (handler is null) throw new ArgumentNullException(nameof(handler)); + Contract.EndContractBlock(); + + ConnectionState state = await command.EnsureOpenAsync(cancellationToken).ConfigureAwait(false); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; +#if NET472 +#else + await +#endif + using SqlDataReader reader = await command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(false); + await handler(reader).ConfigureAwait(false); + } + + /// + public static async ValueTask ExecuteReaderAsync(this SqlCommand command, + Action handler, + CommandBehavior behavior = CommandBehavior.Default, + CancellationToken cancellationToken = default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (handler is null) throw new ArgumentNullException(nameof(handler)); + Contract.EndContractBlock(); + + ConnectionState state = await command + .EnsureOpenAsync(cancellationToken) + .ConfigureAwait(false); + + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; +#if NET472 +#else + await +#endif + using SqlDataReader reader = await command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(false); + handler(reader); + } + + /// The to generate a reader from. + /// The transform function the . + /// The behavior to use with the data reader. + /// The cancellation token. + /// /> + public static async ValueTask ExecuteReaderAsync(this SqlCommand command, + Func transform, + CommandBehavior behavior = CommandBehavior.Default, + CancellationToken cancellationToken = default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + ConnectionState state = await command + .EnsureOpenAsync(cancellationToken) + .ConfigureAwait(false); + + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; +#if NET472 +#else + await +#endif + using SqlDataReader reader = await command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(false); + return transform(reader); + } + + /// + public static async ValueTask ExecuteReaderAsync(this SqlCommand command, + Func> transform, + CommandBehavior behavior = CommandBehavior.Default, + CancellationToken cancellationToken = default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + ConnectionState state = await command + .EnsureOpenAsync(cancellationToken) + .ConfigureAwait(false); + + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; +#if NET472 +#else + await +#endif + using SqlDataReader reader = await command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(false); + return await transform(reader); + } +} diff --git a/Source/MSSqlClient/Extensions/Connection.CreateCommand.cs b/Source/MSSqlClient/Extensions/Connection.CreateCommand.cs new file mode 100644 index 0000000..9897802 --- /dev/null +++ b/Source/MSSqlClient/Extensions/Connection.CreateCommand.cs @@ -0,0 +1,55 @@ +namespace Open.Database.Extensions; + +/// +/// Extensions for SqlConnections. +/// +public static partial class SqlConnectionExtensions +{ + const string EmptyOrWhiteSpace = "Command is empty or whitespace."; + + /// + /// Shortcut for creating an SqlCommand from any SqlConnection. + /// + /// The connection to create a command from. + /// The command type. Text, StoredProcedure, or TableDirect. + /// The command text or stored procedure name to use. + /// The number of seconds to wait before the command times out. + /// The created SqlCommand. + public static SqlCommand CreateCommand(this SqlConnection connection, + CommandType type, string commandText, int secondsTimeout = CommandTimeout.DEFAULT_SECONDS) + { + if (connection is null) throw new ArgumentNullException(nameof(connection)); + if (commandText is null) throw new ArgumentNullException(nameof(commandText)); + if (string.IsNullOrWhiteSpace(commandText)) throw new ArgumentException(EmptyOrWhiteSpace, nameof(commandText)); + Contract.EndContractBlock(); + + SqlCommand command = connection.CreateCommand(); + command.CommandType = type; + command.CommandText = commandText; + command.CommandTimeout = secondsTimeout; + + return command; + } + + /// + /// Shortcut for creating an text SqlCommand from any SqlConnection. + /// + /// The connection to create a command from. + /// The command text or stored procedure name to use. + /// The number of seconds to wait before the command times out. + /// The created SqlCommand. + public static SqlCommand CreateTextCommand(this SqlConnection connection, + string commandText, int secondsTimeout = CommandTimeout.DEFAULT_SECONDS) + => connection.CreateCommand(CommandType.Text, commandText, secondsTimeout); + + /// + /// Shortcut for creating a stored procedure SqlCommand from any SqlConnection. + /// + /// The connection to create a command from. + /// The command text or stored procedure name to use. + /// The number of seconds to wait before the command times out. + /// The created SqlCommand. + public static SqlCommand CreateStoredProcedureCommand(this SqlConnection connection, + string procedureName, int secondsTimeout = CommandTimeout.DEFAULT_SECONDS) + => connection.CreateCommand(CommandType.StoredProcedure, procedureName, secondsTimeout); +} diff --git a/Source/MSSqlClient/Extensions/ConnectionProvider.Command.cs b/Source/MSSqlClient/Extensions/ConnectionProvider.Command.cs new file mode 100644 index 0000000..e7d0467 --- /dev/null +++ b/Source/MSSqlClient/Extensions/ConnectionProvider.Command.cs @@ -0,0 +1,125 @@ +namespace Open.Database.Extensions; + +/// +/// Core non-DB-specific extensions for acquiring and operating on different connection factories. +/// +public static partial class SqlConnectionExtensions +{ + /// + /// Creates an ExpressiveSqlCommand for subsequent configuration and execution. + /// + /// The connection to execute the command on. + /// The command text or stored procedure name to use. + /// The command type. Default = CommandType.Text. + /// The resultant ExpressiveSqlCommand. + public static ExpressiveSqlCommand Command( + this SqlConnection connection, + string command, CommandType type = CommandType.Text) + => new(connection, type, command); + + /// + /// Creates an ExpressiveSqlCommand for subsequent configuration and execution. + /// + /// The transaction to execute the command on. + /// The command text or stored procedure name to use. + /// The command type. Default = CommandType.Text. + /// The resultant ExpressiveSqlCommand. + public static ExpressiveSqlCommand Command( + this SqlTransaction transaction, + string command, CommandType type = CommandType.Text) + => new(transaction, type, command); + + /// + /// Creates an ExpressiveSqlCommand with command type set to StoredProcedure for subsequent configuration and execution. + /// + /// The connection to execute the command on. + /// The stored procedure name to use. + /// The resultant ExpressiveSqlCommand. + public static ExpressiveSqlCommand StoredProcedure( + this SqlConnection connection, + string procedureName) + => new(connection, CommandType.StoredProcedure, procedureName); + + /// + /// Creates an ExpressiveSqlCommand with command type set to StoredProcedure for subsequent configuration and execution. + /// + /// The transaction to execute the command on. + /// The stored procedure name to use. + /// The resultant ExpressiveSqlCommand. + public static ExpressiveSqlCommand StoredProcedure( + this SqlTransaction transaction, + string procedureName) + => new(transaction, CommandType.StoredProcedure, procedureName); + + /// + /// Creates an ExpressiveSqlCommand for subsequent configuration and execution. + /// + /// The connection factory to generate connections and subsequently commands from. + /// The command text or stored procedure name to use. + /// The command type. Default = CommandType.Text. + /// The resultant ExpressiveSqlCommand. + public static ExpressiveSqlCommand Command( + this IDbConnectionFactory connectionSource, + string command, + CommandType type = CommandType.Text) + => new(connectionSource, type, command); + + /// + /// Creates an ExpressiveSqlCommand with command type set to StoredProcedure for subsequent configuration and execution. + /// + /// The connection factory to generate connections and subsequently commands from. + /// The stored procedure name to use. + /// The resultant ExpressiveSqlCommand. + public static ExpressiveSqlCommand StoredProcedure( + this IDbConnectionFactory connectionSource, + string procedureName) + => new(connectionSource, CommandType.StoredProcedure, procedureName); + + /// + /// Creates an ExpressiveSqlCommand for subsequent configuration and execution. + /// + /// The connection pool to take connections from. + /// The command text or stored procedure name to use. + /// The command type. Default = CommandType.Text. + /// The resultant ExpressiveSqlCommand. + public static ExpressiveSqlCommand Command( + this IDbConnectionPool connectionSource, + string command, + CommandType type = CommandType.Text) + => new(connectionSource, type, command); + + /// + /// Creates an ExpressiveSqlCommand with command type set to StoredProcedure for subsequent configuration and execution. + /// + /// The connection pool to take connections from. + /// The stored procedure name to use. + /// The resultant ExpressiveSqlCommand. + public static ExpressiveSqlCommand StoredProcedure( + this IDbConnectionPool connectionSource, + string procedureName) + => new(connectionSource, CommandType.StoredProcedure, procedureName); + + /// + /// Creates an ExpressiveSqlCommand for subsequent configuration and execution. + /// + /// The connection factory to generate a commands from. + /// The command text or stored procedure name to use. + /// The command type. Default = CommandType.Text. + /// The resultant ExpressiveSqlCommand. + public static ExpressiveSqlCommand Command( + this Func connectionSource, + string command, + CommandType type = CommandType.Text) + => Command(DbConnectionFactory.Create(connectionSource), command, type); + + /// + /// Creates an ExpressiveSqlCommand with command type set to StoredProcedure for subsequent configuration and execution. + /// + /// The connection factory to generate a commands from. + /// The stored procedure name to use. + /// The resultant ExpressiveSqlCommand. + public static ExpressiveSqlCommand StoredProcedure( + this Func connectionSource, + string procedureName) + => StoredProcedure(DbConnectionFactory.Create(connectionSource), procedureName); +} diff --git a/Source/MSSqlClient/Extensions/Transaction.CreateCommand.cs b/Source/MSSqlClient/Extensions/Transaction.CreateCommand.cs new file mode 100644 index 0000000..3d4476a --- /dev/null +++ b/Source/MSSqlClient/Extensions/Transaction.CreateCommand.cs @@ -0,0 +1,50 @@ +namespace Open.Database.Extensions; + +public static partial class SqlTransactionExtensions +{ + /// + /// Shortcut for creating an SqlCommand from any SqlTransaction. + /// + /// The transaction to create a command from. + /// The command type. Text, StoredProcedure, or TableDirect. + /// The command text or stored procedure name to use. + /// The number of seconds to wait before the command times out. + /// The created SqlCommand. + public static SqlCommand CreateCommand(this SqlTransaction transaction, + CommandType type, string commandText, int secondsTimeout = CommandTimeout.DEFAULT_SECONDS) + { + if (transaction is null) throw new ArgumentNullException(nameof(transaction)); + if (commandText is null) throw new ArgumentNullException(nameof(commandText)); + if (string.IsNullOrWhiteSpace(commandText)) throw new ArgumentException("Command is empty or whitespace.", nameof(commandText)); + Contract.EndContractBlock(); + + SqlCommand command = transaction + .Connection + .CreateCommand(type, commandText, secondsTimeout); + + command.Transaction = transaction; + return command; + } + + /// + /// Shortcut for creating a text SqlCommand from any SqlTransaction. + /// + /// The transaction to create a command from. + /// The command text or stored procedure name to use. + /// The number of seconds to wait before the command times out. + /// The created SqlCommand. + public static SqlCommand CreateTextCommand(this SqlTransaction transaction, + string procedureName, int secondsTimeout = CommandTimeout.DEFAULT_SECONDS) + => transaction.CreateCommand(CommandType.Text, procedureName, secondsTimeout); + + /// + /// Shortcut for creating a stored procedure SqlCommand from any SqlTransaction. + /// + /// The transaction to create a command from. + /// The command text or stored procedure name to use. + /// The number of seconds to wait before the command times out. + /// The created SqlCommand. + public static SqlCommand CreateStoredProcedureCommand(this SqlTransaction transaction, + string procedureName, int secondsTimeout = CommandTimeout.DEFAULT_SECONDS) + => transaction.CreateCommand(CommandType.StoredProcedure, procedureName, secondsTimeout); +} diff --git a/Source/MSSqlClient/Extensions/Transaction.cs b/Source/MSSqlClient/Extensions/Transaction.cs new file mode 100644 index 0000000..b7f5e05 --- /dev/null +++ b/Source/MSSqlClient/Extensions/Transaction.cs @@ -0,0 +1,564 @@ +namespace Open.Database.Extensions; + +// NOTE: This is simply a copy/paste of th IDb and Db extensions but replacing types with their Sql versions. +// Why? To ensure the Sql types are propagated through the type flow. + +/// +/// System.Data.SqlClient specific extensions for database transactions. +/// +[SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] +public static partial class SqlTransactionExtensions +{ + /// + /// Begins a transaction before executing the action. + /// Commits if there are no exceptions, the 'Commit' value from the action is true and the optional cancellation token has not been cancelled. + /// Otherwise rolls-back the transaction. + /// + /// The value returned from the action. + /// The connection to transact with. + /// + /// The handler to execute while a transaction is pending. + /// Returning a 'Commit' value of true signals to commit the transaction. + /// + /// The isolation level for the transaction. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The value returned from the conditional action. + public static (bool Commit, T Value) ExecuteTransactionConditional( + this SqlConnection connection, + Func conditionalAction, + IsolationLevel isolationLevel = IsolationLevel.Unspecified, + CancellationToken cancellationToken = default) + { + if (connection is null) throw new ArgumentNullException(nameof(connection)); + if (conditionalAction is null) throw new ArgumentNullException(nameof(conditionalAction)); + Contract.EndContractBlock(); + + cancellationToken.ThrowIfCancellationRequested(); + + connection.EnsureOpen(); + cancellationToken.ThrowIfCancellationRequested(); + + using SqlTransaction transaction = connection.BeginTransaction(isolationLevel); + (bool Commit, T Value) result = conditionalAction(transaction); + cancellationToken.ThrowIfCancellationRequested(); + + if (result.Commit) transaction.Commit(); + return result; + } + + /// + public static async ValueTask<(bool Commit, T Value)> ExecuteTransactionConditionalAsync( + this SqlConnection connection, + Func> conditionalAction, + IsolationLevel isolationLevel = IsolationLevel.Unspecified, + CancellationToken cancellationToken = default) + { + if (connection is null) throw new ArgumentNullException(nameof(connection)); + if (conditionalAction is null) throw new ArgumentNullException(nameof(conditionalAction)); + Contract.EndContractBlock(); + + if (connection.State != ConnectionState.Open) + await connection.EnsureOpenAsync(cancellationToken); + + using SqlTransaction transaction = connection.BeginTransaction(isolationLevel); + (bool Commit, T Value) result = await conditionalAction(transaction, cancellationToken); + if (result.Commit) transaction.Commit(); + return result; + } + + /// + /// Begins a transaction before executing the action. + /// Commits if there are no exceptions, the conditional action returns true, and the optional cancellation token is not cancelled. + /// Otherwise rolls-back the transaction. + /// + /// The connection to transact with. + /// The handler to execute while a transaction is pending. Returning true signals to commit the transaction. + /// The isolation level for the transaction. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// True if committed. + public static bool ExecuteTransactionConditional( + this SqlConnection connection, + Func conditionalAction, + IsolationLevel isolationLevel = IsolationLevel.Unspecified, + CancellationToken cancellationToken = default) + => connection.ExecuteTransactionConditional( + isolationLevel, cancellationToken, + t => (conditionalAction(t), true)).Commit; + + /// + /// Begins a transaction before executing the action. + /// Commits if there are no exceptions and the optional provided token is not cancelled. + /// Otherwise rolls-back the transaction. + /// + /// The value returned from the action. + /// The connection to transact with. + /// The handler to execute while a transaction is pending. + /// The isolation level for the transaction. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The value of the action. + public static T ExecuteTransaction( + this SqlConnection connection, + Func action, + IsolationLevel isolationLevel = IsolationLevel.Unspecified, + CancellationToken cancellationToken = default) + => connection.ExecuteTransactionConditional( + isolationLevel, cancellationToken, + t => (true, action(t))).Value; + + /// + /// Begins a transaction before executing the action. + /// Commits if there are no exceptions and the optional provided token is not cancelled. + /// Otherwise rolls-back the transaction. + /// + /// The connection to transact with. + /// The handler to execute while a transaction is pending. + /// The isolation level for the transaction. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + public static void ExecuteTransaction( + this SqlConnection connection, + Action action, + IsolationLevel isolationLevel = IsolationLevel.Unspecified, + CancellationToken cancellationToken = default) + => connection.ExecuteTransaction( + t => { action(t); return true; }, + isolationLevel, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions, the 'Commit' value from the action is true, and the optional provided token is not cancelled. Otherwise rolls-back the transaction. + /// + /// The value returned from the action. + /// The connection to transact with. + /// The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. + /// The isolation level for the transaction. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The value of the awaited action. + public static ValueTask<(bool Commit, T Value)> ExecuteTransactionConditionalAsync( + this SqlConnection connection, + Func> conditionalAction, + IsolationLevel isolationLevel = IsolationLevel.Unspecified, + CancellationToken cancellationToken = default) + => connection.ExecuteTransactionConditionalAsync( + (db, _) => conditionalAction(db), + isolationLevel, cancellationToken); + + /// + /// Begins a transaction before executing the action. + /// Commits if there are no exceptions, the 'Commit' value from the action is true, and the optional provided token is not cancelled. + /// Otherwise rolls-back the transaction. + /// + /// The connection to transact with. + /// The handler to execute while a transaction is pending. Returning true signals to commit the transaction. + /// The isolation level for the transaction. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The value of the awaited action. + public static async ValueTask ExecuteTransactionConditionalAsync( + this SqlConnection connection, + Func> conditionalAction, + IsolationLevel isolationLevel = IsolationLevel.Unspecified, + CancellationToken cancellationToken = default) + => (await connection + .ExecuteTransactionConditionalAsync( + async t => (await conditionalAction(t).ConfigureAwait(false), true), + isolationLevel, cancellationToken) + .ConfigureAwait(false)).Commit; + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. + /// + /// The value returned from the action. + /// The connection to transact with. + /// The handler to execute while a transaction is pending. + /// The isolation level for the transaction. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The value of the awaited action. + public static async ValueTask ExecuteTransactionAsync( + this SqlConnection connection, + Func> action, + IsolationLevel isolationLevel = IsolationLevel.Unspecified, + CancellationToken cancellationToken = default) + => (await connection + .ExecuteTransactionConditionalAsync( + async t => (true, await action(t).ConfigureAwait(false)), + isolationLevel, cancellationToken) + .ConfigureAwait(false)).Value; + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. + /// + /// The connection to transact with. + /// The handler to execute while a transaction is pending. + /// The isolation level for the transaction. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + public static async ValueTask ExecuteTransactionAsync( + this SqlConnection connection, + Func action, + IsolationLevel isolationLevel = IsolationLevel.Unspecified, + CancellationToken cancellationToken = default) + => await connection + .ExecuteTransactionAsync( + async c => { await action(c).ConfigureAwait(false); return true; }, + isolationLevel, cancellationToken) + .ConfigureAwait(false); + + #region Overloads + + #region No optional params + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions and 'Commit' value from the action is true. Otherwise rolls-back the transaction. + /// + /// The value returned from the action. + /// The connection to transact with. + /// The isolation level for the transaction. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. + /// The value returned from the conditional action. + public static (bool Commit, T Value) ExecuteTransactionConditional( + this SqlConnection connection, + IsolationLevel isolationLevel, + CancellationToken cancellationToken, + Func conditionalAction) + => connection.ExecuteTransactionConditional(conditionalAction, isolationLevel, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions and the conditional action returns true. Otherwise rolls-back the transaction. + /// + /// The connection to transact with. + /// The isolation level for the transaction. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The handler to execute while a transaction is pending. Returning true signals to commit the transaction. + /// True if committed. + public static bool ExecuteTransactionConditional( + this SqlConnection connection, + IsolationLevel isolationLevel, + CancellationToken cancellationToken, + Func conditionalAction) + => connection.ExecuteTransactionConditional(conditionalAction, isolationLevel, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. + /// + /// The value returned from the action. + /// The connection to transact with. + /// The isolation level for the transaction. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The handler to execute while a transaction is pending. + /// The value of the action. + public static T ExecuteTransaction( + this SqlConnection connection, + IsolationLevel isolationLevel, + CancellationToken cancellationToken, + Func action) + => connection.ExecuteTransaction(action, isolationLevel, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. + /// + /// The connection to transact with. + /// The isolation level for the transaction. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The handler to execute while a transaction is pending. + public static void ExecuteTransaction( + this SqlConnection connection, + IsolationLevel isolationLevel, + CancellationToken cancellationToken, + Action action) + => connection.ExecuteTransaction(action, isolationLevel, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions and the 'Commit' value from the action is true. Otherwise rolls-back the transaction. + /// + /// The value returned from the action. + /// The connection to transact with. + /// The isolation level for the transaction. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. + /// The value of the awaited action. + public static ValueTask<(bool Commit, T Value)> ExecuteTransactionConditionalAsync( + this SqlConnection connection, + IsolationLevel isolationLevel, + CancellationToken cancellationToken, + Func> conditionalAction) + => connection.ExecuteTransactionConditionalAsync(conditionalAction, isolationLevel, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions and the value from the action is true. Otherwise rolls-back the transaction. + /// + /// The connection to transact with. + /// The isolation level for the transaction. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The handler to execute while a transaction is pending. Returning true signals to commit the transaction. + /// The value of the awaited action. + public static ValueTask ExecuteTransactionConditionalAsync( + this SqlConnection connection, + IsolationLevel isolationLevel, + CancellationToken cancellationToken, + Func> conditionalAction) + => connection.ExecuteTransactionConditionalAsync(conditionalAction, isolationLevel, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. + /// + /// The value returned from the action. + /// The connection to transact with. + /// The isolation level for the transaction. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The handler to execute while a transaction is pending. + /// The value of the awaited action. + public static ValueTask ExecuteTransactionAsync( + this SqlConnection connection, + IsolationLevel isolationLevel, + CancellationToken cancellationToken, + Func> action) + => connection.ExecuteTransactionAsync(action, isolationLevel, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. + /// + /// The connection to transact with. + /// The isolation level for the transaction. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The handler to execute while a transaction is pending. + public static ValueTask ExecuteTransactionAsync( + this SqlConnection connection, + IsolationLevel isolationLevel, + CancellationToken cancellationToken, + Func action) + => connection.ExecuteTransactionAsync(action, isolationLevel, cancellationToken); + + #endregion + + #region Optional Token + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions and 'Commit' value from the action is true. Otherwise rolls-back the transaction. + /// + /// The value returned from the action. + /// The connection to transact with. + /// The isolation level for the transaction. + /// The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. + /// An optional token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The value returned from the conditional action. + public static (bool Commit, T Value) ExecuteTransactionConditional( + this SqlConnection connection, + IsolationLevel isolationLevel, + Func conditionalAction, + CancellationToken cancellationToken = default) + => connection.ExecuteTransactionConditional(conditionalAction, isolationLevel, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions and the conditional action returns true. Otherwise rolls-back the transaction. + /// + /// The connection to transact with. + /// The isolation level for the transaction. + /// The handler to execute while a transaction is pending. Returning true signals to commit the transaction. + /// An optional token that if cancelled will cause this transaction to be aborted or rolled-back. + /// True if committed. + public static bool ExecuteTransactionConditional( + this SqlConnection connection, + IsolationLevel isolationLevel, + Func conditionalAction, + CancellationToken cancellationToken = default) + => connection.ExecuteTransactionConditional(conditionalAction, isolationLevel, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. + /// + /// The value returned from the action. + /// The connection to transact with. + /// The isolation level for the transaction. + /// The handler to execute while a transaction is pending. + /// An optional token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The value of the action. + public static T ExecuteTransaction( + this SqlConnection connection, + IsolationLevel isolationLevel, + Func action, + CancellationToken cancellationToken = default) + => connection.ExecuteTransaction(action, isolationLevel, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. + /// + /// The connection to transact with. + /// The isolation level for the transaction. + /// The handler to execute while a transaction is pending. + /// An optional token that if cancelled will cause this transaction to be aborted or rolled-back. + public static void ExecuteTransaction( + this SqlConnection connection, + IsolationLevel isolationLevel, + Action action, + CancellationToken cancellationToken = default) + => connection.ExecuteTransaction(action, isolationLevel, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions and the 'Commit' value from the action is true. Otherwise rolls-back the transaction. + /// + /// The value returned from the action. + /// The connection to transact with. + /// The isolation level for the transaction. + /// The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. + /// An optional token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The value of the awaited action. + public static ValueTask<(bool Commit, T Value)> ExecuteTransactionConditionalAsync( + this SqlConnection connection, + IsolationLevel isolationLevel, + Func> conditionalAction, + CancellationToken cancellationToken = default) + => connection.ExecuteTransactionConditionalAsync(conditionalAction, isolationLevel, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions and the value from the action is true. Otherwise rolls-back the transaction. + /// + /// The connection to transact with. + /// The isolation level for the transaction. + /// The handler to execute while a transaction is pending. Returning true signals to commit the transaction. + /// An optional token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The value of the awaited action. + public static ValueTask ExecuteTransactionConditionalAsync( + this SqlConnection connection, + IsolationLevel isolationLevel, + Func> conditionalAction, + CancellationToken cancellationToken = default) + => connection.ExecuteTransactionConditionalAsync(conditionalAction, isolationLevel, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. + /// + /// The value returned from the action. + /// The connection to transact with. + /// The isolation level for the transaction. + /// The handler to execute while a transaction is pending. + /// An optional token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The value of the awaited action. + public static ValueTask ExecuteTransactionAsync( + this SqlConnection connection, + IsolationLevel isolationLevel, + Func> action, + CancellationToken cancellationToken = default) + => connection.ExecuteTransactionAsync(action, isolationLevel, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. + /// + /// The connection to transact with. + /// The isolation level for the transaction. + /// The handler to execute while a transaction is pending. + /// An optional token that if cancelled will cause this transaction to be aborted or rolled-back. + public static ValueTask ExecuteTransactionAsync( + this SqlConnection connection, + IsolationLevel isolationLevel, + Func action, + CancellationToken cancellationToken = default) + => connection.ExecuteTransactionAsync(action, isolationLevel, cancellationToken); + #endregion + + #region Unspecified Isolation Level + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions, the 'Commit' value from the action is true and the optional cancellation token has not been cancelled. Otherwise rolls-back the transaction. + /// + /// The value returned from the action. + /// The connection to transact with. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. + /// The value returned from the conditional action. + public static (bool Commit, T Value) ExecuteTransactionConditional( + this SqlConnection connection, + CancellationToken cancellationToken, + Func conditionalAction) + => connection.ExecuteTransactionConditional(conditionalAction, IsolationLevel.Unspecified, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions, the conditional action returns true, and the optional cancellation token is not cancelled. Otherwise rolls-back the transaction. + /// + /// The connection to transact with. + /// An optional token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The handler to execute while a transaction is pending. Returning true signals to commit the transaction. + /// True if committed. + public static bool ExecuteTransactionConditional( + this SqlConnection connection, + CancellationToken cancellationToken, + Func conditionalAction) + => connection.ExecuteTransactionConditional(conditionalAction, IsolationLevel.Unspecified, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. + /// + /// The value returned from the action. + /// The connection to transact with. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The handler to execute while a transaction is pending. + /// The value of the action. + public static T ExecuteTransaction( + this SqlConnection connection, + CancellationToken cancellationToken, + Func action) + => connection.ExecuteTransaction(action, IsolationLevel.Unspecified, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. + /// + /// The connection to transact with. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The handler to execute while a transaction is pending. + public static void ExecuteTransaction( + this SqlConnection connection, + CancellationToken cancellationToken, + Action action) + => connection.ExecuteTransaction(action, IsolationLevel.Unspecified, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions, the 'Commit' value from the action is true, and the optional provided token is not cancelled. Otherwise rolls-back the transaction. + /// + /// The value returned from the action. + /// The connection to transact with. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. + /// The value of the awaited action. + public static ValueTask<(bool Commit, T Value)> ExecuteTransactionConditionalAsync( + this SqlConnection connection, + CancellationToken cancellationToken, + Func> conditionalAction) + => connection.ExecuteTransactionConditionalAsync(conditionalAction, IsolationLevel.Unspecified, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions, the value from the action is true, and the optional provided token is not cancelled. Otherwise rolls-back the transaction. + /// + /// The connection to transact with. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The handler to execute while a transaction is pending. Returning true signals to commit the transaction. + /// The value of the awaited action. + public static ValueTask ExecuteTransactionConditionalAsync( + this SqlConnection connection, + CancellationToken cancellationToken, + Func> conditionalAction) + => connection.ExecuteTransactionConditionalAsync(conditionalAction, IsolationLevel.Unspecified, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. + /// + /// The value returned from the action. + /// The connection to transact with. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The handler to execute while a transaction is pending. + /// The value of the awaited action. + public static ValueTask ExecuteTransactionAsync( + this SqlConnection connection, + CancellationToken cancellationToken, + Func> action) + => connection.ExecuteTransactionAsync(action, IsolationLevel.Unspecified, cancellationToken); + + /// + /// Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. + /// + /// The connection to transact with. + /// A token that if cancelled will cause this transaction to be aborted or rolled-back. + /// The handler to execute while a transaction is pending. + public static ValueTask ExecuteTransactionAsync( + this SqlConnection connection, + CancellationToken cancellationToken, + Func action) + => connection.ExecuteTransactionAsync(action, IsolationLevel.Unspecified, cancellationToken); + + #endregion + + #endregion +} diff --git a/Source/MSSqlClient/Open.Database.Extensions.MSSqlClient.csproj b/Source/MSSqlClient/Open.Database.Extensions.MSSqlClient.csproj new file mode 100644 index 0000000..d868b6b --- /dev/null +++ b/Source/MSSqlClient/Open.Database.Extensions.MSSqlClient.csproj @@ -0,0 +1,21 @@ + + + + net472; net8.0; net9.0; + Useful set of utilities and abstractions for simplifying modern SQL Client data-access operations and ensuring DI compatibility. + ado;ado extensions;sql;connection factory;extensions; + + + + + + + + + + + + + + + diff --git a/Source/MSSqlClient/SqlConnectionFactory.cs b/Source/MSSqlClient/SqlConnectionFactory.cs new file mode 100644 index 0000000..2ef378c --- /dev/null +++ b/Source/MSSqlClient/SqlConnectionFactory.cs @@ -0,0 +1,23 @@ +namespace Open.Database.Extensions; + +/// +/// Default SqlConnectionFactory for generating SqlConnections. +/// +public class SqlConnectionFactory : DbConnectionFactory +{ + /// + /// Default injectable connection factory constructor. + /// + /// The factory that generates the connections. + public SqlConnectionFactory(Func factory) : base(factory) + { + } + + /// + /// Default injectable connection factory constructor that accepts a connection string. + /// + /// Required connection string value. + public SqlConnectionFactory(string connectionString) : base(() => new SqlConnection(connectionString)) + { + } +} diff --git a/Source/MSSqlClient/_Imports.cs b/Source/MSSqlClient/_Imports.cs new file mode 100644 index 0000000..01be406 --- /dev/null +++ b/Source/MSSqlClient/_Imports.cs @@ -0,0 +1,4 @@ +global using Microsoft.Data.SqlClient; +global using System.Data; +global using System.Diagnostics.CodeAnalysis; +global using System.Diagnostics.Contracts; diff --git a/Source/SqlClient/.editorconfig b/Source/SqlClient/.editorconfig new file mode 100644 index 0000000..bc3c465 --- /dev/null +++ b/Source/SqlClient/.editorconfig @@ -0,0 +1,4 @@ +[*.cs] + +# CS0618: Type or member is obsolete +dotnet_diagnostic.CS0618.severity = silent \ No newline at end of file diff --git a/Source/SqlClient/Documentation.xml b/Source/SqlClient/Documentation.xml deleted file mode 100644 index d3641e2..0000000 --- a/Source/SqlClient/Documentation.xml +++ /dev/null @@ -1,573 +0,0 @@ - - - - Open.Database.Extensions.SqlClient - - - - - A specialized for SqlClient abstraction for executing commands on a database using best practices and simplified expressive syntax. - - - - Constructs a . - - - - Constructs a . - - - - Constructs a . - - - - Constructs a . - - - - Constructs a . - - - - - Handles adding the list of parameters to a new command. - - - - - SqlClient extensions for building a command and retrieving data using best practices. - - - - - Shortcut for adding command parameter. - - The command to add a parameter to. - The name of the parameter. - The value of the parameter. - The DbType of the parameter. - The direction of the parameter. - The created IDbDataParameter. - - - - Shortcut for adding command a typed (non-input) parameter. - - The command to add a parameter to. - The name of the parameter. - The SqlDbType of the parameter. - The direction of the parameter. - The created IDbDataParameter. - - - - Shortcut for adding command a typed (non-input) parameter. - - The command to add a parameter to. - The name of the parameter. - The SqlDbType of the parameter. - The created IDbDataParameter. - - - - Shortcut for adding command a typed return parameter. - - The command to add a parameter to. - The SqlDbType of the parameter. - The name of the parameter. - The created IDbDataParameter. - - - - Extensions for SqlConnections. - - - Core non-DB-specific extensions for acquiring and operating on different connection factories. - - - - - Shortcut for creating an SqlCommand from any SqlConnection. - - The connection to create a command from. - The command type. Text, StoredProcedure, or TableDirect. - The command text or stored procedure name to use. - The number of seconds to wait before the command times out. - The created SqlCommand. - - - - Shortcut for creating an text SqlCommand from any SqlConnection. - - The connection to create a command from. - The command text or stored procedure name to use. - The number of seconds to wait before the command times out. - The created SqlCommand. - - - - Shortcut for creating a stored procedure SqlCommand from any SqlConnection. - - The connection to create a command from. - The command text or stored procedure name to use. - The number of seconds to wait before the command times out. - The created SqlCommand. - - - - Creates an ExpressiveSqlCommand for subsequent configuration and execution. - - The connection to execute the command on. - The command text or stored procedure name to use. - The command type. Default = CommandType.Text. - The resultant ExpressiveSqlCommand. - - - - Creates an ExpressiveSqlCommand for subsequent configuration and execution. - - The transaction to execute the command on. - The command text or stored procedure name to use. - The command type. Default = CommandType.Text. - The resultant ExpressiveSqlCommand. - - - - Creates an ExpressiveSqlCommand with command type set to StoredProcedure for subsequent configuration and execution. - - The connection to execute the command on. - The stored procedure name to use. - The resultant ExpressiveSqlCommand. - - - - Creates an ExpressiveSqlCommand with command type set to StoredProcedure for subsequent configuration and execution. - - The transaction to execute the command on. - The stored procedure name to use. - The resultant ExpressiveSqlCommand. - - - - Creates an ExpressiveSqlCommand for subsequent configuration and execution. - - The connection factory to generate connections and subsequently commands from. - The command text or stored procedure name to use. - The command type. Default = CommandType.Text. - The resultant ExpressiveSqlCommand. - - - - Creates an ExpressiveSqlCommand with command type set to StoredProcedure for subsequent configuration and execution. - - The connection factory to generate connections and subsequently commands from. - The stored procedure name to use. - The resultant ExpressiveSqlCommand. - - - - Creates an ExpressiveSqlCommand for subsequent configuration and execution. - - The connection pool to take connections from. - The command text or stored procedure name to use. - The command type. Default = CommandType.Text. - The resultant ExpressiveSqlCommand. - - - - Creates an ExpressiveSqlCommand with command type set to StoredProcedure for subsequent configuration and execution. - - The connection pool to take connections from. - The stored procedure name to use. - The resultant ExpressiveSqlCommand. - - - - Creates an ExpressiveSqlCommand for subsequent configuration and execution. - - The connection factory to generate a commands from. - The command text or stored procedure name to use. - The command type. Default = CommandType.Text. - The resultant ExpressiveSqlCommand. - - - - Creates an ExpressiveSqlCommand with command type set to StoredProcedure for subsequent configuration and execution. - - The connection factory to generate a commands from. - The stored procedure name to use. - The resultant ExpressiveSqlCommand. - - - - System.Data.SqlClient specific extensions for database transactions. - - - - - Shortcut for creating an SqlCommand from any SqlTransaction. - - The transaction to create a command from. - The command type. Text, StoredProcedure, or TableDirect. - The command text or stored procedure name to use. - The number of seconds to wait before the command times out. - The created SqlCommand. - - - - Shortcut for creating a text SqlCommand from any SqlTransaction. - - The transaction to create a command from. - The command text or stored procedure name to use. - The number of seconds to wait before the command times out. - The created SqlCommand. - - - - Shortcut for creating a stored procedure SqlCommand from any SqlTransaction. - - The transaction to create a command from. - The command text or stored procedure name to use. - The number of seconds to wait before the command times out. - The created SqlCommand. - - - - Begins a transaction before executing the action. Commits if there are no exceptions, the 'Commit' value from the action is true and the optional cancellation token has not been cancelled. Otherwise rolls-back the transaction. - - The value returned from the action. - The connection to transact with. - The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. - The isolation level for the transaction. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The value returned from the conditional action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions, the conditional action returns true, and the optional cancellation token is not cancelled. Otherwise rolls-back the transaction. - - The connection to transact with. - The handler to execute while a transaction is pending. Returning true signals to commit the transaction. - The isolation level for the transaction. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - True if committed. - - - - Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - - The value returned from the action. - The connection to transact with. - The handler to execute while a transaction is pending. - The isolation level for the transaction. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The value of the action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - - The connection to transact with. - The handler to execute while a transaction is pending. - The isolation level for the transaction. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - - - - Begins a transaction before executing the action. Commits if there are no exceptions, the 'Commit' value from the action is true, and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - - The value returned from the action. - The connection to transact with. - The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. - The isolation level for the transaction. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The value of the awaited action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions, the 'Commit' value from the action is true, and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - - The connection to transact with. - The handler to execute while a transaction is pending. Returning true signals to commit the transaction. - The isolation level for the transaction. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The value of the awaited action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - - The value returned from the action. - The connection to transact with. - The handler to execute while a transaction is pending. - The isolation level for the transaction. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The value of the awaited action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - - The connection to transact with. - The handler to execute while a transaction is pending. - The isolation level for the transaction. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - - - - Begins a transaction before executing the action. Commits if there are no exceptions and 'Commit' value from the action is true. Otherwise rolls-back the transaction. - - The value returned from the action. - The connection to transact with. - The isolation level for the transaction. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. - The value returned from the conditional action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions and the conditional action returns true. Otherwise rolls-back the transaction. - - The connection to transact with. - The isolation level for the transaction. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The handler to execute while a transaction is pending. Returning true signals to commit the transaction. - True if committed. - - - - Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. - - The value returned from the action. - The connection to transact with. - The isolation level for the transaction. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The handler to execute while a transaction is pending. - The value of the action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. - - The connection to transact with. - The isolation level for the transaction. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The handler to execute while a transaction is pending. - - - - Begins a transaction before executing the action. Commits if there are no exceptions and the 'Commit' value from the action is true. Otherwise rolls-back the transaction. - - The value returned from the action. - The connection to transact with. - The isolation level for the transaction. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. - The value of the awaited action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions and the value from the action is true. Otherwise rolls-back the transaction. - - The connection to transact with. - The isolation level for the transaction. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The handler to execute while a transaction is pending. Returning true signals to commit the transaction. - The value of the awaited action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. - - The value returned from the action. - The connection to transact with. - The isolation level for the transaction. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The handler to execute while a transaction is pending. - The value of the awaited action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. - - The connection to transact with. - The isolation level for the transaction. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The handler to execute while a transaction is pending. - - - - Begins a transaction before executing the action. Commits if there are no exceptions and 'Commit' value from the action is true. Otherwise rolls-back the transaction. - - The value returned from the action. - The connection to transact with. - The isolation level for the transaction. - The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. - An optional token that if cancelled will cause this transaction to be aborted or rolled-back. - The value returned from the conditional action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions and the conditional action returns true. Otherwise rolls-back the transaction. - - The connection to transact with. - The isolation level for the transaction. - The handler to execute while a transaction is pending. Returning true signals to commit the transaction. - An optional token that if cancelled will cause this transaction to be aborted or rolled-back. - True if committed. - - - - Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. - - The value returned from the action. - The connection to transact with. - The isolation level for the transaction. - The handler to execute while a transaction is pending. - An optional token that if cancelled will cause this transaction to be aborted or rolled-back. - The value of the action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. - - The connection to transact with. - The isolation level for the transaction. - The handler to execute while a transaction is pending. - An optional token that if cancelled will cause this transaction to be aborted or rolled-back. - - - - Begins a transaction before executing the action. Commits if there are no exceptions and the 'Commit' value from the action is true. Otherwise rolls-back the transaction. - - The value returned from the action. - The connection to transact with. - The isolation level for the transaction. - The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. - An optional token that if cancelled will cause this transaction to be aborted or rolled-back. - The value of the awaited action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions and the value from the action is true. Otherwise rolls-back the transaction. - - The connection to transact with. - The isolation level for the transaction. - The handler to execute while a transaction is pending. Returning true signals to commit the transaction. - An optional token that if cancelled will cause this transaction to be aborted or rolled-back. - The value of the awaited action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. - - The value returned from the action. - The connection to transact with. - The isolation level for the transaction. - The handler to execute while a transaction is pending. - An optional token that if cancelled will cause this transaction to be aborted or rolled-back. - The value of the awaited action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions. Otherwise rolls-back the transaction. - - The connection to transact with. - The isolation level for the transaction. - The handler to execute while a transaction is pending. - An optional token that if cancelled will cause this transaction to be aborted or rolled-back. - - - - Begins a transaction before executing the action. Commits if there are no exceptions, the 'Commit' value from the action is true and the optional cancellation token has not been cancelled. Otherwise rolls-back the transaction. - - The value returned from the action. - The connection to transact with. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. - The value returned from the conditional action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions, the conditional action returns true, and the optional cancellation token is not cancelled. Otherwise rolls-back the transaction. - - The connection to transact with. - An optional token that if cancelled will cause this transaction to be aborted or rolled-back. - The handler to execute while a transaction is pending. Returning true signals to commit the transaction. - True if committed. - - - - Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - - The value returned from the action. - The connection to transact with. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The handler to execute while a transaction is pending. - The value of the action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - - The connection to transact with. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The handler to execute while a transaction is pending. - - - - Begins a transaction before executing the action. Commits if there are no exceptions, the 'Commit' value from the action is true, and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - - The value returned from the action. - The connection to transact with. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. - The value of the awaited action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions, the value from the action is true, and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - - The connection to transact with. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The handler to execute while a transaction is pending. Returning true signals to commit the transaction. - The value of the awaited action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - - The value returned from the action. - The connection to transact with. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The handler to execute while a transaction is pending. - The value of the awaited action. - - - - Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. - - The connection to transact with. - A token that if cancelled will cause this transaction to be aborted or rolled-back. - The handler to execute while a transaction is pending. - - - - Default SqlConnectionFactory for generating SqlConnections. - - - - - Default injectable connection factory constructor. - - The factory that generates the connections. - - - - Default injectable connection factory constructor that accepts a connection string. - - Required connection string value. - - - diff --git a/Source/SqlClient/ExpressiveSqlCommand.cs b/Source/SqlClient/ExpressiveSqlCommand.cs index a5799ba..f7308fb 100644 --- a/Source/SqlClient/ExpressiveSqlCommand.cs +++ b/Source/SqlClient/ExpressiveSqlCommand.cs @@ -1,9 +1,4 @@ -using System.Collections.Generic; -using System.Data; -using System.Data.SqlClient; -using System.Diagnostics.Contracts; - -namespace Open.Database.Extensions; +namespace Open.Database.Extensions; /// /// A specialized for SqlClient abstraction for executing commands on a database using best practices and simplified expressive syntax. @@ -74,12 +69,20 @@ protected override void AddParams(SqlCommand command) if (command is null) throw new System.ArgumentNullException(nameof(command)); Contract.EndContractBlock(); - foreach (var p in Params) + foreach (Param p in Params) { - var np = command - .Parameters - .AddWithValue(p.Name, p.Value); + SqlParameter np; + if (p.Value is null) + { + np = command.CreateParameter(); + np.ParameterName = p.Name; + } + else + { + np = command.Parameters.AddWithValue(p.Name, p.Value); + } + np.Direction = p.Direction; if (p.Type.HasValue) np.SqlDbType = p.Type.Value; } diff --git a/Source/SqlClient/Extensions/Command.AddParameter.cs b/Source/SqlClient/Extensions/Command.AddParameter.cs index c7f1718..5cb8d7d 100644 --- a/Source/SqlClient/Extensions/Command.AddParameter.cs +++ b/Source/SqlClient/Extensions/Command.AddParameter.cs @@ -1,11 +1,4 @@ -using System; -using System.Data; -using System.Data.SqlClient; -using System.Diagnostics.Contracts; -// ReSharper disable UnusedMember.Global -// ReSharper disable MemberCanBePrivate.Global - -namespace Open.Database.Extensions; +namespace Open.Database.Extensions; /// /// SqlClient extensions for building a command and retrieving data using best practices. @@ -21,13 +14,17 @@ public static partial class SqlCommandExtensions /// The DbType of the parameter. /// The direction of the parameter. /// The created IDbDataParameter. - public static SqlParameter AddParameter(this SqlCommand target, - string name, object value, SqlDbType type, ParameterDirection direction = ParameterDirection.Input) + public static SqlParameter AddParameter( + this SqlCommand target, + string name, + object? value, + SqlDbType type, + ParameterDirection direction = ParameterDirection.Input) { if (target is null) throw new ArgumentNullException(nameof(target)); Contract.EndContractBlock(); - var p = AddParameterType(target, name, type, direction); + SqlParameter p = AddParameterType(target, name, type, direction); p.Value = value; return p; } @@ -40,8 +37,11 @@ public static SqlParameter AddParameter(this SqlCommand target, /// The SqlDbType of the parameter. /// The direction of the parameter. /// The created IDbDataParameter. - public static SqlParameter AddParameterType(this SqlCommand target, - string? name, SqlDbType type, ParameterDirection direction = ParameterDirection.Input) + public static SqlParameter AddParameterType( + this SqlCommand target, + string? name, + SqlDbType type, + ParameterDirection direction = ParameterDirection.Input) { if (target is null) throw new ArgumentNullException(nameof(target)); if (direction != ParameterDirection.ReturnValue && name == null) @@ -50,7 +50,7 @@ public static SqlParameter AddParameterType(this SqlCommand target, throw new ArgumentException("Parameter names cannot be empty or white space.", nameof(name)); Contract.EndContractBlock(); - var c = target.CreateParameter(); + SqlParameter c = target.CreateParameter(); c.ParameterName = name; c.SqlDbType = type; c.Direction = direction; diff --git a/Source/SqlClient/Extensions/Command.ExecuteReader.cs b/Source/SqlClient/Extensions/Command.ExecuteReader.cs new file mode 100644 index 0000000..1c7bd24 --- /dev/null +++ b/Source/SqlClient/Extensions/Command.ExecuteReader.cs @@ -0,0 +1,159 @@ +using System.Data.Common; +using System.Runtime.CompilerServices; + +namespace Open.Database.Extensions; + +/// +/// SqlClient extensions for building a command and retrieving data using best practices. +/// +public static partial class SqlCommandExtensions +{ + #region Connection.EnsureOpen Shortcuts. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ConnectionState EnsureOpen(this IDbCommand command) + { +#if DEBUG + if (command.Connection is null) throw new InvalidOperationException("Cannot execute a command with a null connection."); +#endif + return command.Connection!.EnsureOpen(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ValueTask EnsureOpenAsync(this IDbCommand command, CancellationToken cancellationToken) + { +#if DEBUG + if (command.Connection is null) throw new ArgumentException("Cannot execute a command with a null connection."); +#endif + return command.Connection!.EnsureOpenAsync(cancellationToken); + } + #endregion + + /// The to generate a reader from. + /// The handler function for the . + /// The behavior to use with the data reader. + /// + public static void ExecuteReader(this SqlCommand command, Action handler, CommandBehavior behavior = CommandBehavior.Default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (handler is null) throw new ArgumentNullException(nameof(handler)); + Contract.EndContractBlock(); + + ConnectionState state = command.EnsureOpen(); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; + using SqlDataReader reader = command.ExecuteReader(behavior); + handler(reader); + } + + /// The to generate a reader from. + /// The transform function for each . + /// The behavior to use with the data reader. + /// + public static T ExecuteReader(this SqlCommand command, Func transform, CommandBehavior behavior = CommandBehavior.Default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + ConnectionState state = command.EnsureOpen(); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; + using SqlDataReader reader = command.ExecuteReader(behavior); + return transform(reader); + } + + /// The to generate a reader from. + /// The handler function for the . + /// The behavior to use with the data reader. + /// The cancellation token. + /// + public static async ValueTask ExecuteReaderAsync(this SqlCommand command, + Func handler, + CommandBehavior behavior = CommandBehavior.Default, + CancellationToken cancellationToken = default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (handler is null) throw new ArgumentNullException(nameof(handler)); + Contract.EndContractBlock(); + + ConnectionState state = await command.EnsureOpenAsync(cancellationToken).ConfigureAwait(false); + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; +#if NETSTANDARD2_0 +#else + await +#endif + using SqlDataReader reader = await command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(false); + await handler(reader).ConfigureAwait(false); + } + + /// + public static async ValueTask ExecuteReaderAsync(this SqlCommand command, + Action handler, + CommandBehavior behavior = CommandBehavior.Default, + CancellationToken cancellationToken = default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (handler is null) throw new ArgumentNullException(nameof(handler)); + Contract.EndContractBlock(); + + ConnectionState state = await command + .EnsureOpenAsync(cancellationToken) + .ConfigureAwait(false); + + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; +#if NETSTANDARD2_0 +#else + await +#endif + using SqlDataReader reader = await command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(false); + handler(reader); + } + + /// The to generate a reader from. + /// The transform function the . + /// The behavior to use with the data reader. + /// The cancellation token. + /// /> + public static async ValueTask ExecuteReaderAsync(this SqlCommand command, + Func transform, + CommandBehavior behavior = CommandBehavior.Default, + CancellationToken cancellationToken = default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + ConnectionState state = await command + .EnsureOpenAsync(cancellationToken) + .ConfigureAwait(false); + + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; +#if NETSTANDARD2_0 +#else + await +#endif + using SqlDataReader reader = await command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(false); + return transform(reader); + } + + /// + public static async ValueTask ExecuteReaderAsync(this SqlCommand command, + Func> transform, + CommandBehavior behavior = CommandBehavior.Default, + CancellationToken cancellationToken = default) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + if (transform is null) throw new ArgumentNullException(nameof(transform)); + Contract.EndContractBlock(); + + ConnectionState state = await command + .EnsureOpenAsync(cancellationToken) + .ConfigureAwait(false); + + if (state == ConnectionState.Closed) behavior |= CommandBehavior.CloseConnection; +#if NETSTANDARD2_0 +#else + await +#endif + using SqlDataReader reader = await command.ExecuteReaderAsync(behavior, cancellationToken).ConfigureAwait(false); + return await transform(reader); + } +} diff --git a/Source/SqlClient/Extensions/Connection.CreateCommand.cs b/Source/SqlClient/Extensions/Connection.CreateCommand.cs index 54cf90b..9897802 100644 --- a/Source/SqlClient/Extensions/Connection.CreateCommand.cs +++ b/Source/SqlClient/Extensions/Connection.CreateCommand.cs @@ -1,11 +1,4 @@ -using System; -using System.Data; -using System.Data.SqlClient; -using System.Diagnostics.Contracts; -// ReSharper disable UnusedMember.Global -// ReSharper disable MemberCanBePrivate.Global - -namespace Open.Database.Extensions; +namespace Open.Database.Extensions; /// /// Extensions for SqlConnections. @@ -30,7 +23,7 @@ public static SqlCommand CreateCommand(this SqlConnection connection, if (string.IsNullOrWhiteSpace(commandText)) throw new ArgumentException(EmptyOrWhiteSpace, nameof(commandText)); Contract.EndContractBlock(); - var command = connection.CreateCommand(); + SqlCommand command = connection.CreateCommand(); command.CommandType = type; command.CommandText = commandText; command.CommandTimeout = secondsTimeout; diff --git a/Source/SqlClient/Extensions/ConnectionProvider.Command.cs b/Source/SqlClient/Extensions/ConnectionProvider.Command.cs index bbabec7..e7d0467 100644 --- a/Source/SqlClient/Extensions/ConnectionProvider.Command.cs +++ b/Source/SqlClient/Extensions/ConnectionProvider.Command.cs @@ -1,8 +1,4 @@ -using System; -using System.Data; -using System.Data.SqlClient; - -namespace Open.Database.Extensions; +namespace Open.Database.Extensions; /// /// Core non-DB-specific extensions for acquiring and operating on different connection factories. diff --git a/Source/SqlClient/Extensions/Transaction.CreateCommand.cs b/Source/SqlClient/Extensions/Transaction.CreateCommand.cs index ace5386..3d4476a 100644 --- a/Source/SqlClient/Extensions/Transaction.CreateCommand.cs +++ b/Source/SqlClient/Extensions/Transaction.CreateCommand.cs @@ -1,11 +1,4 @@ -using System; -using System.Data; -using System.Data.SqlClient; -using System.Diagnostics.Contracts; -// ReSharper disable UnusedMember.Global -// ReSharper disable MemberCanBePrivate.Global - -namespace Open.Database.Extensions; +namespace Open.Database.Extensions; public static partial class SqlTransactionExtensions { @@ -25,7 +18,7 @@ public static SqlCommand CreateCommand(this SqlTransaction transaction, if (string.IsNullOrWhiteSpace(commandText)) throw new ArgumentException("Command is empty or whitespace.", nameof(commandText)); Contract.EndContractBlock(); - var command = transaction + SqlCommand command = transaction .Connection .CreateCommand(type, commandText, secondsTimeout); diff --git a/Source/SqlClient/Extensions/Transaction.cs b/Source/SqlClient/Extensions/Transaction.cs index 584d437..b7f5e05 100644 --- a/Source/SqlClient/Extensions/Transaction.cs +++ b/Source/SqlClient/Extensions/Transaction.cs @@ -1,13 +1,4 @@ -using System; -using System.Data; -using System.Data.SqlClient; -using System.Diagnostics.Contracts; -using System.Threading; -using System.Threading.Tasks; -// ReSharper disable UnusedMember.Global -// ReSharper disable MemberCanBePrivate.Global - -namespace Open.Database.Extensions; +namespace Open.Database.Extensions; // NOTE: This is simply a copy/paste of th IDb and Db extensions but replacing types with their Sql versions. // Why? To ensure the Sql types are propagated through the type flow. @@ -15,14 +6,20 @@ namespace Open.Database.Extensions; /// /// System.Data.SqlClient specific extensions for database transactions. /// +[SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] public static partial class SqlTransactionExtensions { /// - /// Begins a transaction before executing the action. Commits if there are no exceptions, the 'Commit' value from the action is true and the optional cancellation token has not been cancelled. Otherwise rolls-back the transaction. + /// Begins a transaction before executing the action. + /// Commits if there are no exceptions, the 'Commit' value from the action is true and the optional cancellation token has not been cancelled. + /// Otherwise rolls-back the transaction. /// /// The value returned from the action. /// The connection to transact with. - /// The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. + /// + /// The handler to execute while a transaction is pending. + /// Returning a 'Commit' value of true signals to commit the transaction. + /// /// The isolation level for the transaction. /// A token that if cancelled will cause this transaction to be aborted or rolled-back. /// The value returned from the conditional action. @@ -38,32 +35,41 @@ public static (bool Commit, T Value) ExecuteTransactionConditional( cancellationToken.ThrowIfCancellationRequested(); - var success = false; - SqlTransaction? transaction = null; - connection.EnsureOpen(); cancellationToken.ThrowIfCancellationRequested(); - try - { - transaction = connection.BeginTransaction(isolationLevel); - var result = conditionalAction(transaction); - cancellationToken.ThrowIfCancellationRequested(); - success = result.Commit; - return result; - } - finally - { - if (transaction != null) // Just in case acquiring a transaction fails. - { - if (success) transaction.Commit(); - else transaction.Rollback(); - } - } + using SqlTransaction transaction = connection.BeginTransaction(isolationLevel); + (bool Commit, T Value) result = conditionalAction(transaction); + cancellationToken.ThrowIfCancellationRequested(); + + if (result.Commit) transaction.Commit(); + return result; + } + + /// + public static async ValueTask<(bool Commit, T Value)> ExecuteTransactionConditionalAsync( + this SqlConnection connection, + Func> conditionalAction, + IsolationLevel isolationLevel = IsolationLevel.Unspecified, + CancellationToken cancellationToken = default) + { + if (connection is null) throw new ArgumentNullException(nameof(connection)); + if (conditionalAction is null) throw new ArgumentNullException(nameof(conditionalAction)); + Contract.EndContractBlock(); + + if (connection.State != ConnectionState.Open) + await connection.EnsureOpenAsync(cancellationToken); + + using SqlTransaction transaction = connection.BeginTransaction(isolationLevel); + (bool Commit, T Value) result = await conditionalAction(transaction, cancellationToken); + if (result.Commit) transaction.Commit(); + return result; } /// - /// Begins a transaction before executing the action. Commits if there are no exceptions, the conditional action returns true, and the optional cancellation token is not cancelled. Otherwise rolls-back the transaction. + /// Begins a transaction before executing the action. + /// Commits if there are no exceptions, the conditional action returns true, and the optional cancellation token is not cancelled. + /// Otherwise rolls-back the transaction. /// /// The connection to transact with. /// The handler to execute while a transaction is pending. Returning true signals to commit the transaction. @@ -76,10 +82,13 @@ public static bool ExecuteTransactionConditional( IsolationLevel isolationLevel = IsolationLevel.Unspecified, CancellationToken cancellationToken = default) => connection.ExecuteTransactionConditional( - isolationLevel, cancellationToken, t => (conditionalAction(t), true)).Commit; + isolationLevel, cancellationToken, + t => (conditionalAction(t), true)).Commit; /// - /// Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. + /// Begins a transaction before executing the action. + /// Commits if there are no exceptions and the optional provided token is not cancelled. + /// Otherwise rolls-back the transaction. /// /// The value returned from the action. /// The connection to transact with. @@ -93,10 +102,13 @@ public static T ExecuteTransaction( IsolationLevel isolationLevel = IsolationLevel.Unspecified, CancellationToken cancellationToken = default) => connection.ExecuteTransactionConditional( - isolationLevel, cancellationToken, t => (true, action(t))).Value; + isolationLevel, cancellationToken, + t => (true, action(t))).Value; /// - /// Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. + /// Begins a transaction before executing the action. + /// Commits if there are no exceptions and the optional provided token is not cancelled. + /// Otherwise rolls-back the transaction. /// /// The connection to transact with. /// The handler to execute while a transaction is pending. @@ -107,7 +119,9 @@ public static void ExecuteTransaction( Action action, IsolationLevel isolationLevel = IsolationLevel.Unspecified, CancellationToken cancellationToken = default) - => connection.ExecuteTransaction(t => { action(t); return true; }, isolationLevel, cancellationToken); + => connection.ExecuteTransaction( + t => { action(t); return true; }, + isolationLevel, cancellationToken); /// /// Begins a transaction before executing the action. Commits if there are no exceptions, the 'Commit' value from the action is true, and the optional provided token is not cancelled. Otherwise rolls-back the transaction. @@ -118,48 +132,19 @@ public static void ExecuteTransaction( /// The isolation level for the transaction. /// A token that if cancelled will cause this transaction to be aborted or rolled-back. /// The value of the awaited action. - public static async ValueTask<(bool Commit, T Value)> ExecuteTransactionConditionalAsync( + public static ValueTask<(bool Commit, T Value)> ExecuteTransactionConditionalAsync( this SqlConnection connection, Func> conditionalAction, IsolationLevel isolationLevel = IsolationLevel.Unspecified, CancellationToken cancellationToken = default) - { - if (connection is null) throw new ArgumentNullException(nameof(connection)); - if (conditionalAction is null) throw new ArgumentNullException(nameof(conditionalAction)); - Contract.EndContractBlock(); - - cancellationToken.ThrowIfCancellationRequested(); - - var success = false; - SqlTransaction? transaction = null; - - // Only await if needed... - if (connection.State != ConnectionState.Open) - { - await connection.EnsureOpenAsync(cancellationToken).ConfigureAwait(true); // If the task is cancelled, awaiting will throw. - cancellationToken.ThrowIfCancellationRequested(); - } - - try - { - transaction = connection.BeginTransaction(isolationLevel); - var result = await conditionalAction(transaction).ConfigureAwait(false); // If the task is cancelled, awaiting will throw. - cancellationToken.ThrowIfCancellationRequested(); - success = result.Commit; - return result; - } - finally - { - if (transaction != null) // Just in case acquiring a transaction fails. - { - if (success) transaction.Commit(); - else transaction.Rollback(); - } - } - } + => connection.ExecuteTransactionConditionalAsync( + (db, _) => conditionalAction(db), + isolationLevel, cancellationToken); /// - /// Begins a transaction before executing the action. Commits if there are no exceptions, the 'Commit' value from the action is true, and the optional provided token is not cancelled. Otherwise rolls-back the transaction. + /// Begins a transaction before executing the action. + /// Commits if there are no exceptions, the 'Commit' value from the action is true, and the optional provided token is not cancelled. + /// Otherwise rolls-back the transaction. /// /// The connection to transact with. /// The handler to execute while a transaction is pending. Returning true signals to commit the transaction. @@ -171,8 +156,11 @@ public static async ValueTask ExecuteTransactionConditionalAsync( Func> conditionalAction, IsolationLevel isolationLevel = IsolationLevel.Unspecified, CancellationToken cancellationToken = default) - => (await connection.ExecuteTransactionConditionalAsync( - async t => (await conditionalAction(t).ConfigureAwait(false), true), isolationLevel, cancellationToken).ConfigureAwait(false)).Commit; + => (await connection + .ExecuteTransactionConditionalAsync( + async t => (await conditionalAction(t).ConfigureAwait(false), true), + isolationLevel, cancellationToken) + .ConfigureAwait(false)).Commit; /// /// Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. @@ -188,7 +176,11 @@ public static async ValueTask ExecuteTransactionAsync( Func> action, IsolationLevel isolationLevel = IsolationLevel.Unspecified, CancellationToken cancellationToken = default) - => (await connection.ExecuteTransactionConditionalAsync(async t => (true, await action(t).ConfigureAwait(false)), isolationLevel, cancellationToken).ConfigureAwait(false)).Value; + => (await connection + .ExecuteTransactionConditionalAsync( + async t => (true, await action(t).ConfigureAwait(false)), + isolationLevel, cancellationToken) + .ConfigureAwait(false)).Value; /// /// Begins a transaction before executing the action. Commits if there are no exceptions and the optional provided token is not cancelled. Otherwise rolls-back the transaction. @@ -202,7 +194,11 @@ public static async ValueTask ExecuteTransactionAsync( Func action, IsolationLevel isolationLevel = IsolationLevel.Unspecified, CancellationToken cancellationToken = default) - => await connection.ExecuteTransactionAsync(async c => { await action(c).ConfigureAwait(false); return true; }, isolationLevel, cancellationToken).ConfigureAwait(false); + => await connection + .ExecuteTransactionAsync( + async c => { await action(c).ConfigureAwait(false); return true; }, + isolationLevel, cancellationToken) + .ConfigureAwait(false); #region Overloads @@ -216,7 +212,6 @@ public static async ValueTask ExecuteTransactionAsync( /// A token that if cancelled will cause this transaction to be aborted or rolled-back. /// The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. /// The value returned from the conditional action. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] public static (bool Commit, T Value) ExecuteTransactionConditional( this SqlConnection connection, IsolationLevel isolationLevel, @@ -232,7 +227,6 @@ public static (bool Commit, T Value) ExecuteTransactionConditional( /// A token that if cancelled will cause this transaction to be aborted or rolled-back. /// The handler to execute while a transaction is pending. Returning true signals to commit the transaction. /// True if committed. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] public static bool ExecuteTransactionConditional( this SqlConnection connection, IsolationLevel isolationLevel, @@ -249,7 +243,6 @@ public static bool ExecuteTransactionConditional( /// A token that if cancelled will cause this transaction to be aborted or rolled-back. /// The handler to execute while a transaction is pending. /// The value of the action. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] public static T ExecuteTransaction( this SqlConnection connection, IsolationLevel isolationLevel, @@ -264,7 +257,6 @@ public static T ExecuteTransaction( /// The isolation level for the transaction. /// A token that if cancelled will cause this transaction to be aborted or rolled-back. /// The handler to execute while a transaction is pending. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] public static void ExecuteTransaction( this SqlConnection connection, IsolationLevel isolationLevel, @@ -281,7 +273,6 @@ public static void ExecuteTransaction( /// A token that if cancelled will cause this transaction to be aborted or rolled-back. /// The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. /// The value of the awaited action. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] public static ValueTask<(bool Commit, T Value)> ExecuteTransactionConditionalAsync( this SqlConnection connection, IsolationLevel isolationLevel, @@ -297,7 +288,6 @@ public static void ExecuteTransaction( /// A token that if cancelled will cause this transaction to be aborted or rolled-back. /// The handler to execute while a transaction is pending. Returning true signals to commit the transaction. /// The value of the awaited action. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] public static ValueTask ExecuteTransactionConditionalAsync( this SqlConnection connection, IsolationLevel isolationLevel, @@ -314,7 +304,6 @@ public static ValueTask ExecuteTransactionConditionalAsync( /// A token that if cancelled will cause this transaction to be aborted or rolled-back. /// The handler to execute while a transaction is pending. /// The value of the awaited action. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] public static ValueTask ExecuteTransactionAsync( this SqlConnection connection, IsolationLevel isolationLevel, @@ -329,7 +318,6 @@ public static ValueTask ExecuteTransactionAsync( /// The isolation level for the transaction. /// A token that if cancelled will cause this transaction to be aborted or rolled-back. /// The handler to execute while a transaction is pending. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] public static ValueTask ExecuteTransactionAsync( this SqlConnection connection, IsolationLevel isolationLevel, @@ -472,7 +460,6 @@ public static ValueTask ExecuteTransactionAsync( /// A token that if cancelled will cause this transaction to be aborted or rolled-back. /// The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. /// The value returned from the conditional action. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] public static (bool Commit, T Value) ExecuteTransactionConditional( this SqlConnection connection, CancellationToken cancellationToken, @@ -486,7 +473,6 @@ public static (bool Commit, T Value) ExecuteTransactionConditional( /// An optional token that if cancelled will cause this transaction to be aborted or rolled-back. /// The handler to execute while a transaction is pending. Returning true signals to commit the transaction. /// True if committed. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] public static bool ExecuteTransactionConditional( this SqlConnection connection, CancellationToken cancellationToken, @@ -501,7 +487,6 @@ public static bool ExecuteTransactionConditional( /// A token that if cancelled will cause this transaction to be aborted or rolled-back. /// The handler to execute while a transaction is pending. /// The value of the action. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] public static T ExecuteTransaction( this SqlConnection connection, CancellationToken cancellationToken, @@ -514,7 +499,6 @@ public static T ExecuteTransaction( /// The connection to transact with. /// A token that if cancelled will cause this transaction to be aborted or rolled-back. /// The handler to execute while a transaction is pending. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] public static void ExecuteTransaction( this SqlConnection connection, CancellationToken cancellationToken, @@ -529,7 +513,6 @@ public static void ExecuteTransaction( /// A token that if cancelled will cause this transaction to be aborted or rolled-back. /// The handler to execute while a transaction is pending. Returning a 'Commit' value of true signals to commit the transaction. /// The value of the awaited action. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] public static ValueTask<(bool Commit, T Value)> ExecuteTransactionConditionalAsync( this SqlConnection connection, CancellationToken cancellationToken, @@ -543,7 +526,6 @@ public static void ExecuteTransaction( /// A token that if cancelled will cause this transaction to be aborted or rolled-back. /// The handler to execute while a transaction is pending. Returning true signals to commit the transaction. /// The value of the awaited action. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] public static ValueTask ExecuteTransactionConditionalAsync( this SqlConnection connection, CancellationToken cancellationToken, @@ -558,7 +540,6 @@ public static ValueTask ExecuteTransactionConditionalAsync( /// A token that if cancelled will cause this transaction to be aborted or rolled-back. /// The handler to execute while a transaction is pending. /// The value of the awaited action. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] public static ValueTask ExecuteTransactionAsync( this SqlConnection connection, CancellationToken cancellationToken, @@ -571,7 +552,6 @@ public static ValueTask ExecuteTransactionAsync( /// The connection to transact with. /// A token that if cancelled will cause this transaction to be aborted or rolled-back. /// The handler to execute while a transaction is pending. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1068:CancellationToken parameters must come last", Justification = "Overload for easier consumption.")] public static ValueTask ExecuteTransactionAsync( this SqlConnection connection, CancellationToken cancellationToken, diff --git a/Source/SqlClient/Open.Database.Extensions.SqlClient.csproj b/Source/SqlClient/Open.Database.Extensions.SqlClient.csproj index 4aa83e3..c45f6b5 100644 --- a/Source/SqlClient/Open.Database.Extensions.SqlClient.csproj +++ b/Source/SqlClient/Open.Database.Extensions.SqlClient.csproj @@ -1,49 +1,28 @@  - Open.Database.Extensions.SqlClient - netstandard2.0; netstandard2.1 - latest - enable - electricessence - © electricessence (Oren F.) All rights reserved. - MIT - https://github.com/Open-NET-Libraries/Open.Database.Extensions - https://github.com/Open-NET-Libraries/Open.Database.Extensions Useful set of utilities and abstractions for simplifying modern SQL Client data-access operations and ensuring DI compatibility. ado;ado extensions;sql;connection factory;extensions; - true - git - 7.0.0 - - true - true - snupkg - logo.png - latest - True - README.md - - Documentation.xml - + + + + + + + + + + + - - - + - - True - - - - True - \ - + diff --git a/Source/SqlClient/SqlConnectionFactory.cs b/Source/SqlClient/SqlConnectionFactory.cs index 7855f4d..2ef378c 100644 --- a/Source/SqlClient/SqlConnectionFactory.cs +++ b/Source/SqlClient/SqlConnectionFactory.cs @@ -1,8 +1,4 @@ -using System; -using System.Data.SqlClient; -// ReSharper disable UnusedMember.Global - -namespace Open.Database.Extensions; +namespace Open.Database.Extensions; /// /// Default SqlConnectionFactory for generating SqlConnections. diff --git a/Source/SqlClient/_Imports.cs b/Source/SqlClient/_Imports.cs new file mode 100644 index 0000000..63cf649 --- /dev/null +++ b/Source/SqlClient/_Imports.cs @@ -0,0 +1,4 @@ +global using System.Data; +global using System.Data.SqlClient; +global using System.Diagnostics.CodeAnalysis; +global using System.Diagnostics.Contracts; \ No newline at end of file diff --git a/Tests/Open.Database.Extensions.Core.Tests/Basic.cs b/Tests/Open.Database.Extensions.Core.Tests/Basic.cs index b3140eb..35319f3 100644 --- a/Tests/Open.Database.Extensions.Core.Tests/Basic.cs +++ b/Tests/Open.Database.Extensions.Core.Tests/Basic.cs @@ -1,8 +1,3 @@ -using NSubstitute; -using System; -using System.Data; -using Xunit; - namespace Open.Database.Extensions.Core.Tests; public class Basic @@ -10,13 +5,13 @@ public class Basic [Fact] public void ExpressiveCommandValidation() { - var factory = DbConnectionFactory.Create(() => - { - var conn = Substitute.For(); - return conn; - }); + var factory = DbConnectionFactory.Create(() => Substitute.For()); Assert.Throws(() => factory.Command(null)); Assert.Throws(() => factory.Command(string.Empty)); } + + [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Compile test.")] + static async Task AmbiguityValidation(IDbCommand command) + => await command.Connection.EnsureOpenAsync(); } diff --git a/Tests/Open.Database.Extensions.Core.Tests/IDataReaderToChannelObjectArrayExtensionTests.cs b/Tests/Open.Database.Extensions.Core.Tests/IDataReaderToChannelObjectArrayExtensionTests.cs new file mode 100644 index 0000000..54f8a51 --- /dev/null +++ b/Tests/Open.Database.Extensions.Core.Tests/IDataReaderToChannelObjectArrayExtensionTests.cs @@ -0,0 +1,215 @@ +using System.Data.Common; + +namespace Open.Database.Extensions.Tests; + +#nullable enable + +[ExcludeFromCodeCoverage] +public static class IDataReaderToChannelObjectArrayExtensionTests +{ + internal class TestRecord + { + public string? A { get; set; } + public string? B { get; set; } + public int C { get; set; } + public string? D { get; set; } + } + + [Fact] + public static async Task WithValidParameters_WritesDataToChannel() + { + // Arrange + IDataReader mockReader = Substitute.For(); + + mockReader.Read().Returns(true, false); + mockReader.FieldCount.Returns(1); + mockReader.GetValue(Arg.Any()).Returns("test"); + + // Act + long result = await mockReader.ToChannel(true).ReadAll(static _ => { }); + + // Assert + Assert.Equal(1, result); + } + + [Fact] + public static async Task WithValidParametersAndTransform_WritesTransformedDataToChannel() + { + // Arrange + IDataReader mockReader = Substitute.For(); + + mockReader.Read().Returns(true, false); + mockReader.FieldCount.Returns(1); + mockReader.GetValue(Arg.Any()).Returns("test"); + + static string transform(IDataRecord record) => (string)record.GetValue(0); + + // Act + long result = await mockReader.ToChannel(true, transform).ReadAll(static _ => { }); + + // Assert + Assert.Equal(1, result); + } + + [Fact] + public static async Task WithValidParameters_NoRecords() + { + // Arrange + var command = Substitute.For(); + var connection = Substitute.For(); + var reader = Substitute.For(); + + command.Connection.Returns(connection); + connection.State.Returns(ConnectionState.Open); + command.ExecuteReader(Arg.Any()) + .Returns(reader); + + reader.Read().Returns(false); // Simulate no records + + // Act + long result = await command.ToChannel(true).ReadAll(static _ => { }); + + // Assert + Assert.Equal(0, result); // Assuming the reader has no records + } + + [Fact] + public static async Task WithValidParameters_WithRecords() + { + // Arrange + var reader = Substitute.For(); + + reader.FieldCount.Returns(4); // Simulate valid FieldCount + reader.Read().Returns(true, true, false); // Simulate 2 records then no more records + + reader + .When(x => x.GetValues(Arg.Any())) + .Do(callInfo => + { + object[] values = callInfo.Arg(); + values[0] = "Value1"; + values[1] = "Value2"; + values[2] = 0; + values[3] = DBNull.Value; + }); + + int count = 0; + await foreach (object[] record in reader.ToChannel(true).ReadAllAsync()) + { + count++; + Assert.Equal("Value1", record[0]); + Assert.Equal("Value2", record[1]); + Assert.Equal(0, record[2]); + Assert.Equal(DBNull.Value, record[3]); + } + + Assert.Equal(2, count); + } + + [Fact] + public static async Task DbDataReader_WithValidParameters_WithRecords() + { + // Arrange + var reader = Substitute.For(); + + reader.FieldCount.Returns(4); // Simulate valid FieldCount + reader.ReadAsync().Returns(true, true, false); // Simulate 2 records then no more records + + reader + .When(x => x.GetValues(Arg.Any())) + .Do(callInfo => + { + object[] values = callInfo.Arg(); + values[0] = "Value1"; + values[1] = "Value2"; + values[2] = 0; + values[3] = DBNull.Value; + }); + + int count = 0; + await foreach (object[] record in reader.ToChannelAsync(true).ReadAllAsync()) + { + count++; + Assert.Equal("Value1", record[0]); + Assert.Equal("Value2", record[1]); + Assert.Equal(0, record[2]); + Assert.Equal(DBNull.Value, record[3]); + } + + Assert.Equal(2, count); + } + + [Fact] + public static async Task WithValidParameters_WithTestRecords() + { + // Arrange + var reader = Substitute.For(); + + reader.FieldCount.Returns(4); // Simulate valid FieldCount + reader.Read().Returns(true, true, false); // Simulate 2 records then no more records + reader.GetName(0).Returns("A"); + reader.GetName(1).Returns("B"); + reader.GetName(2).Returns("C"); + reader.GetName(3).Returns("D"); + + reader + .When(x => x.GetValues(Arg.Any())) + .Do(callInfo => + { + object[] values = callInfo.Arg(); + values[0] = "Value1"; + values[1] = "Value2"; + values[2] = 0; + values[3] = DBNull.Value; + }); + + int count = 0; + await foreach (var record in reader.ToChannel(true).ReadAllAsync()) + { + count++; + Assert.Equal("Value1", record.A); + Assert.Equal("Value2", record.B); + Assert.Equal(0, record.C); + Assert.Null(record.D); + } + + Assert.Equal(2, count); + } + + [Fact] + public static async Task DbDataReader_WithValidParameters_WithTestRecords() + { + // Arrange + var reader = Substitute.For(); + + reader.FieldCount.Returns(4); // Simulate valid FieldCount + reader.ReadAsync().Returns(true, true, false); // Simulate 2 records then no more records + reader.GetName(0).Returns("A"); + reader.GetName(1).Returns("B"); + reader.GetName(2).Returns("C"); + reader.GetName(3).Returns("D"); + + reader + .When(x => x.GetValues(Arg.Any())) + .Do(callInfo => + { + object[] values = callInfo.Arg(); + values[0] = "Value1"; + values[1] = "Value2"; + values[2] = 0; + values[3] = DBNull.Value; + }); + + int count = 0; + await foreach (var record in reader.ToChannelAsync(true).ReadAllAsync()) + { + count++; + Assert.Equal("Value1", record.A); + Assert.Equal("Value2", record.B); + Assert.Equal(0, record.C); + Assert.Null(record.D); + } + + Assert.Equal(2, count); + } +} \ No newline at end of file diff --git a/Tests/Open.Database.Extensions.Core.Tests/IDbCommandToChannelObjectArrayExtensionTests.cs b/Tests/Open.Database.Extensions.Core.Tests/IDbCommandToChannelObjectArrayExtensionTests.cs new file mode 100644 index 0000000..40f0c40 --- /dev/null +++ b/Tests/Open.Database.Extensions.Core.Tests/IDbCommandToChannelObjectArrayExtensionTests.cs @@ -0,0 +1,95 @@ +namespace Open.Database.Extensions.Tests; + +[ExcludeFromCodeCoverage] +public static class IDbCommandToChannelObjectArrayExtensionTests +{ + [Fact] + public static async Task WithValidParameters_NoRecords() + { + // Arrange + var command = Substitute.For(); + var connection = Substitute.For(); + var reader = Substitute.For(); + var c = Channel.CreateUnbounded(); + var cWriter = c.Writer; + var cReader = c.Reader; + var cancellationToken = new CancellationToken(); + + command.Connection.Returns(connection); + connection.State.Returns(ConnectionState.Open); + command.ExecuteReader(Arg.Any()) + .Returns(reader); + + reader.Read().Returns(false); // Simulate no records + + // Act + long result = await command.ToChannel(cWriter, true, cancellationToken); + + // Assert + Assert.Equal(0, result); // Assuming the reader has no records + + await Task.Yield(); + Assert.True(cReader.Completion.IsCompleted); + } + + [Fact] + public static async Task WithValidParameters_WithRecords() + { + // Arrange + var command = Substitute.For(); + var connection = Substitute.For(); + var reader = Substitute.For(); + + command.Connection.Returns(connection); + connection.State.Returns(ConnectionState.Open); + command.ExecuteReader(Arg.Any()) + .Returns(reader); + + reader.FieldCount.Returns(4); // Simulate valid FieldCount + reader.Read().Returns(true, true, false); // Simulate 2 records then no more records + + reader + .When(x => x.GetValues(Arg.Any())) + .Do(callInfo => + { + object[] values = callInfo.Arg(); + values[0] = "Value1"; + values[1] = "Value2"; + values[2] = 0; + values[3] = DBNull.Value; + }); + + int count = 0; + await foreach (object[] record in command.ToChannel(true).ReadAllAsync()) + { + count++; + Assert.Equal("Value1", record[0]); + Assert.Equal("Value2", record[1]); + Assert.Equal(0, record[2]); + Assert.Equal(DBNull.Value, record[3]); + } + + Assert.Equal(2, count); + } + + [Fact] + public static async Task WithNullCommand_ThrowsArgumentNullException() + { + // Arrange + IDbCommand command = null; + var writer = Channel.CreateUnbounded().Writer; + + // Act & Assert + await Assert.ThrowsAsync(async () => await command.ToChannel(writer, true)); + } + + [Fact] + public static async Task WithNullWriter_ThrowsArgumentNullException() + { + // Arrange + var command = Substitute.For(); + + // Act & Assert + await Assert.ThrowsAsync(async () => await command.ToChannel(null, true)); + } +} \ No newline at end of file diff --git a/Tests/Open.Database.Extensions.Core.Tests/Open.Database.Extensions.Core.Tests.csproj b/Tests/Open.Database.Extensions.Core.Tests/Open.Database.Extensions.Core.Tests.csproj index e17ed59..b548e45 100644 --- a/Tests/Open.Database.Extensions.Core.Tests/Open.Database.Extensions.Core.Tests.csproj +++ b/Tests/Open.Database.Extensions.Core.Tests/Open.Database.Extensions.Core.Tests.csproj @@ -1,27 +1,32 @@  - net6.0 - + net9.0 false + true - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + + + + + + diff --git a/Tests/Open.Database.Extensions.Core.Tests/ToChannelExtensionTests.Contracts.cs b/Tests/Open.Database.Extensions.Core.Tests/ToChannelExtensionTests.Contracts.cs new file mode 100644 index 0000000..f66c2f5 --- /dev/null +++ b/Tests/Open.Database.Extensions.Core.Tests/ToChannelExtensionTests.Contracts.cs @@ -0,0 +1,243 @@ +using System.Buffers; +using System.Data.Common; + +namespace Open.Database.Extensions.Tests; + +[ExcludeFromCodeCoverage] +public static class ToChannelExtensionsContractTests +{ + [Fact] + public static void ToChannel_IDataReader_NullReader_ThrowsArgumentNullException() + { + // Arrange + IDataReader reader = null; + + // Act & Assert + Assert.Throws(() => reader.ToChannel(true)); + } + + [Fact] + public static async Task ToChannelT_IDataReader_NullReader_ThrowsArgumentNullException() + { + // Arrange + IDataReader reader = null; + + // Act & Assert + await Assert.ThrowsAsync( + async () => await reader.ToChannel(Channel.CreateUnbounded().Writer, true)); + + Assert.Throws( + () => reader.ToChannel(true)); + } + + [Fact] + public static async Task ToChannelT_IDataReader_NullTarget_ThrowsArgumentNullException() + { + // Arrange + IDataReader reader = Substitute.For(); + + // Act & Assert + await Assert.ThrowsAsync( + async () => await reader.ToChannel(null, true)); + } + + [Fact] + public static void ToChannel_IDataReader_NullReaderWithArrayPool_ThrowsArgumentNullException() + { + // Arrange + IDataReader reader = null; + + // Act & Assert + Assert.Throws( + () => reader.ToChannel(true, ArrayPool.Shared)); + } + + [Fact] + public static void ToChannel_IDataReader_NullReaderWithTransform_ThrowsArgumentNullException() + { + // Arrange + IDataReader reader = null; + Func transform = null; + + // Act & Assert + Assert.Throws( + () => reader.ToChannel(true, transform)); + } + + [Fact] + public static void ToChannel_IDataReader_NullTransform_ThrowsArgumentNullException() + { + // Arrange + IDataReader reader = Substitute.For(); + Func transform = null; + + // Act & Assert + Assert.Throws( + () => reader.ToChannel(true, transform)); + } + + [Fact] + public static void ToChannel_IDbCommand_NullCommand_ThrowsArgumentNullException() + { + // Arrange + IDbCommand command = null; + + // Act & Assert + Assert.Throws( + () => command.ToChannel(true)); + } + + [Fact] + public static void ToChannel_IDbCommand_NullArrayPool_ThrowsArgumentNullException() + { + // Arrange + IDbCommand command = Substitute.For(); + ArrayPool arrayPool = null; + + // Act & Assert + Assert.Throws( + () => command.ToChannel(true, arrayPool)); + } + + [Fact] + public static void ToChannel_IDbCommand_NullTransform_ThrowsArgumentNullException() + { + // Arrange + IDbCommand command = Substitute.For(); + Func transform = null; + + // Act & Assert + Assert.Throws( + () => command.ToChannel(true, transform)); + } + + [Fact] + public static void ToChannel_IExecuteReader_NullCommand_ThrowsArgumentNullException() + { + // Arrange + IExecuteReader command = null; + + // Act & Assert + Assert.Throws( + () => command.ToChannel(true)); + } + + [Fact] + public static void ToChannel_IExecuteReader_NullArrayPool_ThrowsArgumentNullException() + { + // Arrange + IExecuteReader command = Substitute.For(); + ArrayPool arrayPool = null; + + // Act & Assert + Assert.Throws( + () => command.ToChannel(true, arrayPool)); + } + + [Fact] + public static void ToChannel_IExecuteReader_NullTransform_ThrowsArgumentNullException() + { + // Arrange + IExecuteReader command = Substitute.For(); + Func transform = null; + + // Act & Assert + Assert.Throws( + () => command.ToChannel(true, transform)); + } + +#if !NETSTANDARD2_0 + [Fact] + public static void ToChannelAsync_DbDataReader_NullReader_ThrowsArgumentNullException() + { + // Arrange + DbDataReader reader = null; + + // Act & Assert + Assert.Throws( + () => reader.ToChannelAsync(true)); + } + + [Fact] + public static void ToChannelAsync_DbDataReader_NullArrayPool_ThrowsArgumentNullException() + { + // Arrange + DbDataReader reader = Substitute.For(); + ArrayPool arrayPool = null; + + // Act & Assert + Assert.Throws( + () => reader.ToChannelAsync(true, arrayPool)); + } + + [Fact] + public static void ToChannelAsync_DbDataReader_NullTransform_ThrowsArgumentNullException() + { + // Arrange + DbDataReader reader = Substitute.For(); + Func transform = null; + + // Act & Assert + Assert.Throws( + () => reader.ToChannelAsync(true, transform)); + } + + [Fact] + public static void ToChannelAsync_DbCommand_NullCommand_ThrowsArgumentNullException() + { + // Arrange + DbCommand command = null; + + // Act & Assert + Assert.Throws( + () => command.ToChannelAsync(true)); + } + + [Fact] + public static void ToChannelAsync_DbCommand_NullArrayPool_ThrowsArgumentNullException() + { + // Arrange + DbCommand command = Substitute.For(); + ArrayPool arrayPool = null; + + // Act & Assert + Assert.Throws( + () => command.ToChannelAsync(true, arrayPool)); + } + + [Fact] + public static void ToChannelAsync_DbCommand_NullTransform_ThrowsArgumentNullException() + { + // Arrange + DbCommand command = Substitute.For(); + Func transform = null; + + // Act & Assert + Assert.Throws( + () => command.ToChannelAsync(true, transform)); + } + + [Fact] + public static void ToChannelAsync_IExecuteReaderAsync_NullCommand_ThrowsArgumentNullException() + { + // Arrange + IExecuteReaderAsync command = null; + + // Act & Assert + Assert.Throws( + () => command.ToChannelAsync(true)); + } + + [Fact] + public static void ToChannelAsync_IExecuteReaderAsync_NullTransform_ThrowsArgumentNullException() + { + // Arrange + IExecuteReaderAsync command = Substitute.For(); + Func transform = null; + + // Act & Assert + Assert.Throws( + () => command.ToChannelAsync(true, transform)); + } +#endif +} diff --git a/Tests/Open.Database.Extensions.Core.Tests/_Imports.cs b/Tests/Open.Database.Extensions.Core.Tests/_Imports.cs new file mode 100644 index 0000000..62211fa --- /dev/null +++ b/Tests/Open.Database.Extensions.Core.Tests/_Imports.cs @@ -0,0 +1,6 @@ +global using System.Data; +global using System.Diagnostics.CodeAnalysis; +global using System.Threading.Channels; +global using NSubstitute; +global using Xunit; +global using Open.ChannelExtensions; \ No newline at end of file