├── .editorconfig ├── .gitignore ├── AsyncDataAdapter.Microsoft.Data.SqlClient ├── AsyncDataAdapter.Microsoft.Data.SqlClient.csproj └── Data │ ├── BatchingMSqlDataAdapter.cs │ └── MSSqlAsyncDataAdapter.cs ├── AsyncDataAdapter.System.Data.SqlClient ├── AsyncDataAdapter.System.Data.SqlClient.csproj └── Data │ ├── BatchingSqlDataAdapter.cs │ └── SqlAsyncDataAdapter.cs ├── AsyncDataAdapter.Tests ├── .gitignore ├── AsyncDataAdapter.Tests.csproj ├── FakeDb │ ├── AsyncMode.cs │ ├── FakeDbCommand.cs │ ├── FakeDbCommandBuilder.cs │ ├── FakeDbConnection.cs │ ├── FakeDbDataAdapter.cs │ ├── FakeDbDataReader.cs │ ├── FakeDbDelays.cs │ ├── FakeDbParameter.cs │ ├── FakeDbParameterCollection.cs │ ├── FakeDbProviderFactory.cs │ ├── FakeDbTransaction.cs │ └── ProxiedFakeDb │ │ └── ProxiedFakeDbDataAdapter.cs ├── Properties │ └── AssemblyInfo.cs ├── ProxyDataAdapter │ ├── AsynchronousProxyDataAdapterTests.cs │ ├── ReentrancyDetectionTests.cs │ └── SynchronousProxyDataAdapterTests.cs ├── ReflectionTest.cs ├── SingleMethodTests │ ├── Fill1Test.cs │ ├── Fill2Test.cs │ ├── Fill3Test.cs │ ├── Fill4Test.cs │ ├── Fill5Test.cs │ ├── FillSchema1Test.cs │ ├── FillSchema2Test.cs │ ├── FillSchema3Test.cs │ ├── Update1Test.cs │ ├── Update2Test.cs │ ├── Update3Test.cs │ ├── Update4Test.cs │ └── Utility │ │ ├── DbDataAdapterMethodOverloads.cs │ │ └── SingleMethodTest.cs ├── SqlServer │ ├── MicrosoftDataSqlTests.cs │ ├── SqlDataAdapterTest.cs │ └── SystemDataSqlTests.cs ├── TestConfiguration.cs └── TestUtility │ ├── DataTableMethods.cs │ ├── FakeDbCommandBuilderTests.cs │ ├── FakeDbDataReaderTests.cs │ ├── RandomDataGenerator.cs │ ├── RandomDataGeneratorTests.cs │ └── TestTable.cs ├── AsyncDataAdapter.msbuild ├── AsyncDataAdapter.sln ├── AsyncDataAdapter ├── AsyncDataAdapter.csproj ├── AsyncDataAdapter.snk └── Data │ ├── Common │ ├── AdaDataReaderContainer.cs │ ├── AdaDbSchemaRow.cs │ ├── AdaDbSchemaTable.cs │ ├── AdaSchemaMapping.cs │ ├── BatchCommandInfo.cs │ ├── DataTables.cs │ └── Interfaces │ │ ├── IAdaSchemaMappingAdapter.cs │ │ ├── IAsyncDataAdapter.cs │ │ ├── IBatchingAdapter.cs │ │ └── IFullDbDataAdapter.cs │ ├── Core │ ├── BatchExecute.UpdatedRowStatus.cs │ ├── BatchExecute.cs │ ├── BatchingAdapter.cs │ ├── Connections.cs │ ├── FillAsync.cs │ ├── FillLoadDataRowChunk.cs │ ├── FillMapping.cs │ ├── FillSchemaAsync.cs │ ├── ICanUpdateAsync.cs │ ├── ParameterInputOutput.cs │ ├── UpdateAsync.cs │ └── UpdateRowAsync.cs │ ├── DbCommandBuilder │ ├── IAsyncDbCommandBuilder.cs │ └── ProxyDbCommandBuilder.cs │ ├── Internal │ ├── Utility.cs │ └── Validation.cs │ ├── Reflection │ ├── DataColumnReflection.cs │ ├── ReflectedMethods.cs │ ├── ReflectedMethods.out.cs │ ├── ReflectedMethods.tt │ ├── ReflectedMethods.ttold │ ├── ReflectedProperty.cs │ ├── Reflection.cs │ └── RowUpdatedEventArgs.cs │ └── _DataAdapter │ ├── AsyncDbDataAdapter.cs │ ├── ProxyDataAdapter.FillError.cs │ ├── ProxyDataAdapter.cs │ ├── ProxyDbDataAdapter.DataAdapter.cs │ ├── ProxyDbDataAdapter.DbCommandBuilder.cs │ ├── ProxyDbDataAdapter.DbDataAdapter.cs │ ├── ProxyDbDataAdapter.FillAsync.cs │ ├── ProxyDbDataAdapter.FillError.cs │ ├── ProxyDbDataAdapter.FillSchemaAsync.cs │ ├── ProxyDbDataAdapter.IAsyncDbDataAdapter.cs │ ├── ProxyDbDataAdapter.UpdateAsync.cs │ └── ProxyDbDataAdapter.cs ├── DbSetup.sql ├── LICENSE ├── Properties.xml ├── README.md ├── Tools └── NuGet.exe └── appveyor.yml /.editorconfig: -------------------------------------------------------------------------------- 1 | # To learn more about .editorconfig see https://aka.ms/editorconfigdocs 2 | ############################### 3 | # Core EditorConfig Options # 4 | ############################### 5 | # All files 6 | [*] 7 | indent_style = space 8 | # Code files 9 | 10 | [*.csproj] 11 | indent_size = 2 12 | indent_style = space 13 | insert_final_newline = true 14 | charset = utf-8 15 | 16 | [*.{cs,csx,vb,vbx}] 17 | indent_size = 4 18 | insert_final_newline = true 19 | charset = utf-8 20 | ############################### 21 | # .NET Coding Conventions # 22 | ############################### 23 | [*.{cs,vb}] 24 | # Organize usings 25 | dotnet_sort_system_directives_first = true 26 | # this. preferences 27 | dotnet_style_qualification_for_field = true:suggestion 28 | dotnet_style_qualification_for_property = true:suggestion 29 | dotnet_style_qualification_for_method = true:suggestion 30 | dotnet_style_qualification_for_event = true:suggestion 31 | # Language keywords vs BCL types preferences 32 | dotnet_style_predefined_type_for_locals_parameters_members = true:silent 33 | dotnet_style_predefined_type_for_member_access = true:silent 34 | # Parentheses preferences 35 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent 36 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent 37 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent 38 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent 39 | # Modifier preferences 40 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion 41 | dotnet_style_readonly_field = true:suggestion 42 | # Expression-level preferences 43 | dotnet_style_object_initializer = true:suggestion 44 | dotnet_style_collection_initializer = true:suggestion 45 | dotnet_style_explicit_tuple_names = true:suggestion 46 | dotnet_style_null_propagation = true:suggestion 47 | dotnet_style_coalesce_expression = true:suggestion 48 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent 49 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 50 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion 51 | dotnet_style_prefer_auto_properties = true:silent 52 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent 53 | dotnet_style_prefer_conditional_expression_over_return = true:silent 54 | ############################### 55 | # Naming Conventions # 56 | ############################### 57 | # Style Definitions 58 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case 59 | # Use PascalCase for constant fields 60 | dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion 61 | dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields 62 | dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style 63 | dotnet_naming_symbols.constant_fields.applicable_kinds = field 64 | dotnet_naming_symbols.constant_fields.applicable_accessibilities = * 65 | dotnet_naming_symbols.constant_fields.required_modifiers = const 66 | ############################### 67 | # C# Coding Conventions # 68 | ############################### 69 | [*.cs] 70 | # var preferences 71 | csharp_style_var_for_built_in_types = false:suggestion 72 | csharp_style_var_when_type_is_apparent = false:suggestion 73 | csharp_style_var_elsewhere = false:suggestion 74 | # Expression-bodied members 75 | csharp_style_expression_bodied_methods = false:silent 76 | csharp_style_expression_bodied_constructors = false:silent 77 | csharp_style_expression_bodied_operators = false:silent 78 | csharp_style_expression_bodied_properties = true:silent 79 | csharp_style_expression_bodied_indexers = true:silent 80 | csharp_style_expression_bodied_accessors = true:silent 81 | # Pattern matching preferences 82 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 83 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 84 | # Null-checking preferences 85 | csharp_style_throw_expression = true:suggestion 86 | csharp_style_conditional_delegate_call = true:suggestion 87 | # Modifier preferences 88 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion 89 | # Expression-level preferences 90 | csharp_prefer_braces = true:silent 91 | csharp_style_deconstructed_variable_declaration = true:suggestion 92 | csharp_prefer_simple_default_expression = true:suggestion 93 | csharp_style_pattern_local_over_anonymous_function = true:suggestion 94 | csharp_style_inlined_variable_declaration = true:suggestion 95 | ############################### 96 | # C# Formatting Rules # 97 | ############################### 98 | # New line preferences 99 | csharp_new_line_before_open_brace = all 100 | csharp_new_line_before_else = true 101 | csharp_new_line_before_catch = true 102 | csharp_new_line_before_finally = true 103 | csharp_new_line_before_members_in_object_initializers = true 104 | csharp_new_line_before_members_in_anonymous_types = true 105 | csharp_new_line_between_query_expression_clauses = true 106 | # Indentation preferences 107 | csharp_indent_case_contents = true 108 | csharp_indent_switch_labels = true 109 | csharp_indent_labels = flush_left 110 | # Space preferences 111 | csharp_space_after_cast = false 112 | csharp_space_after_keywords_in_control_flow_statements = true 113 | csharp_space_between_method_call_parameter_list_parentheses = false 114 | csharp_space_between_method_declaration_parameter_list_parentheses = false 115 | csharp_space_between_parentheses = false 116 | csharp_space_before_colon_in_inheritance_clause = true 117 | csharp_space_after_colon_in_inheritance_clause = true 118 | csharp_space_around_binary_operators = before_and_after 119 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 120 | csharp_space_between_method_call_name_and_opening_parenthesis = false 121 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 122 | # Wrapping preferences 123 | csharp_preserve_single_line_statements = true 124 | csharp_preserve_single_line_blocks = true 125 | 126 | 127 | ##### 128 | 129 | dotnet_style_prefer_compound_assignment = false:none 130 | dotnet_style_object_initializer = false:suggestion 131 | 132 | # IDE1006: Naming Styles 133 | dotnet_diagnostic.IDE1006.severity = none -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | # The packages folder can be ignored because of Package Restore 156 | **/packages/* 157 | # except build/, which is used as an MSBuild target. 158 | !**/packages/build/ 159 | # Uncomment if necessary however generally it will be regenerated when needed 160 | #!**/packages/repositories.config 161 | # NuGet v3's project.json files produces more ignoreable files 162 | *.nuget.props 163 | *.nuget.targets 164 | 165 | # Microsoft Azure Build Output 166 | csx/ 167 | *.build.csdef 168 | 169 | # Microsoft Azure Emulator 170 | ecf/ 171 | rcf/ 172 | 173 | # Windows Store app package directories and files 174 | AppPackages/ 175 | BundleArtifacts/ 176 | Package.StoreAssociation.xml 177 | _pkginfo.txt 178 | 179 | # Visual Studio cache files 180 | # files ending in .cache can be ignored 181 | *.[Cc]ache 182 | # but keep track of directories ending in .cache 183 | !*.[Cc]ache/ 184 | 185 | # Others 186 | ClientBin/ 187 | ~$* 188 | *~ 189 | *.dbmdl 190 | *.dbproj.schemaview 191 | *.pfx 192 | *.publishsettings 193 | node_modules/ 194 | orleans.codegen.cs 195 | 196 | # Since there are multiple workflows, uncomment next line to ignore bower_components 197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 198 | #bower_components/ 199 | 200 | # RIA/Silverlight projects 201 | Generated_Code/ 202 | 203 | # Backup & report files from converting an old project file 204 | # to a newer Visual Studio version. Backup files are not needed, 205 | # because we have git ;-) 206 | _UpgradeReport_Files/ 207 | Backup*/ 208 | UpgradeLog*.XML 209 | UpgradeLog*.htm 210 | 211 | # SQL Server files 212 | *.mdf 213 | *.ldf 214 | 215 | # Business Intelligence projects 216 | *.rdl.data 217 | *.bim.layout 218 | *.bim_*.settings 219 | 220 | # Microsoft Fakes 221 | FakesAssemblies/ 222 | 223 | # GhostDoc plugin setting file 224 | *.GhostDoc.xml 225 | 226 | # Node.js Tools for Visual Studio 227 | .ntvs_analysis.dat 228 | 229 | # Visual Studio 6 build log 230 | *.plg 231 | 232 | # Visual Studio 6 workspace options file 233 | *.opt 234 | 235 | # Visual Studio LightSwitch build output 236 | **/*.HTMLClient/GeneratedArtifacts 237 | **/*.DesktopClient/GeneratedArtifacts 238 | **/*.DesktopClient/ModelManifest.xml 239 | **/*.Server/GeneratedArtifacts 240 | **/*.Server/ModelManifest.xml 241 | _Pvt_Extensions 242 | 243 | # Paket dependency manager 244 | .paket/paket.exe 245 | paket-files/ 246 | 247 | # FAKE - F# Make 248 | .fake/ 249 | 250 | # JetBrains Rider 251 | .idea/ 252 | *.sln.iml 253 | 254 | TMP/ 255 | 256 | ./*.nupkg 257 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Microsoft.Data.SqlClient/AsyncDataAdapter.Microsoft.Data.SqlClient.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | true 6 | true 7 | StrongName.snk.pfx 8 | true 9 | true 10 | 1591 11 | 12 | 13 | true 14 | snupkg 15 | 16 | 17 | Jehoel.AsyncDataAdapter.Microsoft.Data.SqlClient 18 | https://github.com/Jehoel/AsyncDataAdapter/ 19 | MIT 20 | https://github.com/Jehoel/AsyncDataAdapter/ 21 | Microsoft Corporation, Vladimir Kloz <vladimir.kloz@gmail.com>; Jeremy Kruer; Dai Rees; 22 | 4.0.0 23 | Vladimir Kloz; Jeremy Kruer; Dai Rees; 24 | 25 | Jehoel.AsyncDataAdapter.Microsoft.Data.SqlClient provides an AsyncDataAdapter for Microsoft.Data.SqlClient 26 | DataAdapter DbDataAdapter SqlDataAdapter AsyncDataAdapter AdaDataAdapter AsyncSqlDataAdapter SqlAsyncDataAdapter FillAsync FillSchemaAsync UpdateAsync 27 | 4.0.0 - Initial release. 28 | 29 | 30 | 31 | 32 | 33 | bin\Release 34 | 35 | 36 | 37 | bin\Debug 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Microsoft.Data.SqlClient/Data/BatchingMSqlDataAdapter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Data.Common; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | using Microsoft.Data.SqlClient; 8 | 9 | namespace AsyncDataAdapter.Internal 10 | { 11 | public class BatchingMSqlDataAdapter : IBatchingAdapter 12 | { 13 | private readonly SqlDataAdapter adapter; 14 | 15 | public BatchingMSqlDataAdapter( SqlDataAdapter adapter ) 16 | { 17 | this.adapter = adapter ?? throw new ArgumentNullException(nameof(adapter)); 18 | } 19 | 20 | private struct _UpdateMappingAction { } 21 | private struct _UpdateSchemaAction { } 22 | private struct _UpdateBatchSize { } 23 | 24 | public MissingMappingAction UpdateMappingAction => ReflectedProperty.GetValue( this.adapter ); 25 | public MissingSchemaAction UpdateSchemaAction => ReflectedProperty.GetValue( this.adapter ); 26 | public Int32 UpdateBatchSize => ReflectedProperty.GetValue( this.adapter ); 27 | 28 | private struct _AddToBatch { } 29 | 30 | public int AddToBatch(DbCommand command) 31 | { 32 | return ReflectedFunc.Invoke( this.adapter, command ); 33 | } 34 | 35 | private struct _ClearBatch { } 36 | 37 | public void ClearBatch() 38 | { 39 | ReflectedAction.Invoke( this.adapter ); 40 | } 41 | 42 | private struct _ExecuteBatchAsync { } 43 | 44 | public Task ExecuteBatchAsync(CancellationToken cancellationToken) 45 | { 46 | return ReflectedFunc>.Invoke( this.adapter, cancellationToken ); 47 | } 48 | 49 | private struct _TerminateBatching { } 50 | 51 | public void TerminateBatching() 52 | { 53 | ReflectedAction.Invoke( this.adapter ); 54 | } 55 | 56 | private struct _GetBatchedParameter { } 57 | 58 | public IDataParameter GetBatchedParameter(int commandIdentifier, int parameterIndex) 59 | { 60 | return ReflectedFunc.Invoke( this.adapter, commandIdentifier, parameterIndex ); 61 | } 62 | 63 | private struct _GetBatchedRecordsAffected { } 64 | 65 | public bool GetBatchedRecordsAffected(int commandIdentifier, out int recordsAffected, out Exception error) 66 | { 67 | return ReflectedFuncO2O3.Invoke( this.adapter, commandIdentifier, out recordsAffected, out error ); 68 | } 69 | 70 | private struct _InitializeBatching { } 71 | 72 | public void InitializeBatching() 73 | { 74 | ReflectedAction.Invoke( this.adapter ); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Microsoft.Data.SqlClient/Data/MSSqlAsyncDataAdapter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.Common; 3 | 4 | using Microsoft.Data.SqlClient; 5 | 6 | using AsyncDataAdapter.Internal; 7 | 8 | namespace AsyncDataAdapter 9 | { 10 | using ProxyDbDataAdapterForSqlClient = ProxyDbDataAdapter< 11 | SqlDataAdapter, 12 | SqlConnection, 13 | SqlCommand, 14 | SqlDataReader 15 | >; 16 | 17 | /// For use with (Microsoft.Data.SqlClient, not System.Data.SqlClient). 18 | public sealed class MSSqlAsyncDbDataAdapter : ProxyDbDataAdapterForSqlClient 19 | { 20 | public MSSqlAsyncDbDataAdapter() 21 | : this( original: new SqlDataAdapter() ) 22 | { 23 | } 24 | 25 | public MSSqlAsyncDbDataAdapter( SqlCommand selectCommand ) 26 | : this( original: new SqlDataAdapter( selectCommand ) ) 27 | { 28 | } 29 | 30 | public MSSqlAsyncDbDataAdapter( String selectCommandText, SqlConnection connection ) 31 | : this( original: new SqlDataAdapter( selectCommandText, connection ) ) 32 | { 33 | } 34 | 35 | public MSSqlAsyncDbDataAdapter( String selectCommandText, String connectionString ) 36 | : this( original: new SqlDataAdapter( selectCommandText, connectionString ) ) 37 | { 38 | } 39 | 40 | // 41 | 42 | private MSSqlAsyncDbDataAdapter( SqlDataAdapter original ) 43 | : this( original: original, batching: new BatchingMSqlDataAdapter( original ) ) 44 | { 45 | 46 | } 47 | 48 | private MSSqlAsyncDbDataAdapter( SqlDataAdapter original, BatchingMSqlDataAdapter batching ) 49 | : base( batchingAdapter: batching, subject: original ) 50 | { 51 | } 52 | 53 | protected override DbCommandBuilder CreateCommandBuilder() 54 | { 55 | return new SqlCommandBuilder( this.Subject ); 56 | } 57 | } 58 | 59 | public static class MSSqlClientExtensions 60 | { 61 | /// Creates a new using (the extension method subject) as the . Note that the 's property MUST be non-null. The connection does not need to be in an Open state yet, however. 62 | /// Required. Cannot be null. Must have a valid non-null set. 63 | public static MSSqlAsyncDbDataAdapter CreateAsyncAdapter( this SqlCommand selectCommand ) 64 | { 65 | if (selectCommand is null) throw new ArgumentNullException(nameof(selectCommand)); 66 | 67 | if( selectCommand.Connection is null ) throw new ArgumentException( message: "The Connection property must be set.", paramName: nameof(selectCommand) ); 68 | 69 | return new MSSqlAsyncDbDataAdapter( selectCommand ); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /AsyncDataAdapter.System.Data.SqlClient/AsyncDataAdapter.System.Data.SqlClient.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | true 6 | true 7 | StrongName.snk.pfx 8 | true 9 | true 10 | 1591 11 | 12 | 13 | true 14 | snupkg 15 | 16 | 17 | Jehoel.AsyncDataAdapter.System.Data.SqlClient 18 | https://github.com/Jehoel/AsyncDataAdapter/ 19 | MIT 20 | https://github.com/Jehoel/AsyncDataAdapter/ 21 | Microsoft Corporation, Vladimir Kloz <vladimir.kloz@gmail.com>; Jeremy Kruer; Dai Rees; 22 | 4.0.0 23 | Vladimir Kloz; Jeremy Kruer; Dai Rees; 24 | 25 | Jehoel.AsyncDataAdapter builds on Vladimir Kloz' original AsyncDataAdapter package, with support for .NET Standard 2.0, and numerous other improvements. The original implementation is based on Microsoft's MIT-licensed implementation of DataReader, DbDataReader, and SqlDataReader. 26 | DataAdapter DbDataAdapter SqlDataAdapter AsyncDataAdapter AdaDataAdapter AsyncSqlDataAdapter SqlAsyncDataAdapter FillAsync FillSchemaAsync UpdateAsync 27 | 4.0.0 - Initial release. 28 | 29 | 30 | 31 | 32 | 33 | bin\Release 34 | 35 | 36 | 37 | bin\Debug 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /AsyncDataAdapter.System.Data.SqlClient/Data/BatchingSqlDataAdapter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Data.Common; 4 | using System.Data.SqlClient; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace AsyncDataAdapter.Internal 9 | { 10 | public class BatchingSqlDataAdapter : IBatchingAdapter 11 | { 12 | private readonly SqlDataAdapter adapter; 13 | 14 | public BatchingSqlDataAdapter( SqlDataAdapter adapter ) 15 | { 16 | this.adapter = adapter ?? throw new ArgumentNullException(nameof(adapter)); 17 | } 18 | 19 | private struct _UpdateMappingAction { } 20 | private struct _UpdateSchemaAction { } 21 | private struct _UpdateBatchSize { } 22 | 23 | public MissingMappingAction UpdateMappingAction => ReflectedProperty.GetValue( this.adapter ); 24 | public MissingSchemaAction UpdateSchemaAction => ReflectedProperty.GetValue( this.adapter ); 25 | public Int32 UpdateBatchSize => ReflectedProperty.GetValue( this.adapter ); 26 | 27 | private struct _AddToBatch { } 28 | 29 | public int AddToBatch(DbCommand command) 30 | { 31 | return ReflectedFunc.Invoke( this.adapter, command ); 32 | } 33 | 34 | private struct _ClearBatch { } 35 | 36 | public void ClearBatch() 37 | { 38 | ReflectedAction.Invoke( this.adapter ); 39 | } 40 | 41 | private struct _ExecuteBatchAsync { } 42 | 43 | public Task ExecuteBatchAsync(CancellationToken cancellationToken) 44 | { 45 | return ReflectedFunc>.Invoke( this.adapter, cancellationToken ); 46 | } 47 | 48 | private struct _TerminateBatching { } 49 | 50 | public void TerminateBatching() 51 | { 52 | ReflectedAction.Invoke( this.adapter ); 53 | } 54 | 55 | private struct _GetBatchedParameter { } 56 | 57 | public IDataParameter GetBatchedParameter(int commandIdentifier, int parameterIndex) 58 | { 59 | return ReflectedFunc.Invoke( this.adapter, commandIdentifier, parameterIndex ); 60 | } 61 | 62 | private struct _GetBatchedRecordsAffected { } 63 | 64 | public bool GetBatchedRecordsAffected(int commandIdentifier, out int recordsAffected, out Exception error) 65 | { 66 | return ReflectedFuncO2O3.Invoke( this.adapter, commandIdentifier, out recordsAffected, out error ); 67 | } 68 | 69 | private struct _InitializeBatching { } 70 | 71 | public void InitializeBatching() 72 | { 73 | ReflectedAction.Invoke( this.adapter ); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /AsyncDataAdapter.System.Data.SqlClient/Data/SqlAsyncDataAdapter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.Common; 3 | using System.Data.SqlClient; 4 | 5 | using AsyncDataAdapter.Internal; 6 | 7 | namespace AsyncDataAdapter 8 | { 9 | using ProxyDbDataAdapterForSqlClient = ProxyDbDataAdapter< 10 | SqlDataAdapter, 11 | SqlConnection, 12 | SqlCommand, 13 | SqlDataReader 14 | >; 15 | 16 | /// For use with (System.Data.SqlClient, not Microsoft.Data.SqlClient). 17 | public sealed class SqlAsyncDbDataAdapter : ProxyDbDataAdapterForSqlClient 18 | { 19 | public SqlAsyncDbDataAdapter() 20 | : this( original: new SqlDataAdapter() ) 21 | { 22 | } 23 | 24 | public SqlAsyncDbDataAdapter( SqlCommand selectCommand ) 25 | : this( original: new SqlDataAdapter( selectCommand ) ) 26 | { 27 | } 28 | 29 | public SqlAsyncDbDataAdapter( String selectCommandText, SqlConnection connection ) 30 | : this( original: new SqlDataAdapter( selectCommandText, connection ) ) 31 | { 32 | } 33 | 34 | public SqlAsyncDbDataAdapter( String selectCommandText, String connectionString ) 35 | : this( original: new SqlDataAdapter( selectCommandText, connectionString ) ) 36 | { 37 | } 38 | 39 | // 40 | 41 | private SqlAsyncDbDataAdapter( SqlDataAdapter original ) 42 | : this( original: original, batching: new BatchingSqlDataAdapter( original ) ) 43 | { 44 | 45 | } 46 | 47 | private SqlAsyncDbDataAdapter( SqlDataAdapter original, BatchingSqlDataAdapter batching ) 48 | : base( batchingAdapter: batching, subject: original ) 49 | { 50 | } 51 | 52 | protected override DbCommandBuilder CreateCommandBuilder() 53 | { 54 | return new SqlCommandBuilder( this.Subject ); 55 | } 56 | } 57 | 58 | public static class SqlClientExtensions 59 | { 60 | /// Creates a new using (the extension method subject) as the . Note that the 's property MUST be non-null. The connection does not need to be in an Open state yet, however. 61 | /// Required. Cannot be null. Must have a valid non-null set. 62 | public static SqlAsyncDbDataAdapter CreateAsyncAdapter( this SqlCommand selectCommand ) 63 | { 64 | if (selectCommand is null) throw new ArgumentNullException(nameof(selectCommand)); 65 | 66 | if( selectCommand.Connection is null ) throw new ArgumentException( message: "The Connection property must be set.", paramName: nameof(selectCommand) ); 67 | 68 | return new SqlAsyncDbDataAdapter( selectCommand ); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/.gitignore: -------------------------------------------------------------------------------- 1 | test-config.json -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/AsyncDataAdapter.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | PreserveNewest 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/FakeDb/AsyncMode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace AsyncDataAdapter.Tests 6 | { 7 | [Flags] 8 | public enum AsyncMode 9 | { 10 | None = 0, 11 | 12 | AllowSync = 1, 13 | 14 | /// Do real async work, or if that's not possible then emulate it by awaiting and then calling the synchronous implementation, for example. 15 | AwaitAsync = 2 | 4, 16 | 17 | /// Block the thread using and then directly call the synchronous version. This is meant to simulate crappy fake-async implementations. 18 | BlockAsync = 2 | 8, 19 | 20 | /// If the async method is virtual and already implemented by the framework superclass (e.g. ) then call that method. 21 | BaseAsync = 2 | 16, 22 | 23 | /// Run the logic (sync or async) inside a job. 24 | RunAsync = 2 | 32, 25 | 26 | // This enum design is probably wrong - the 4/8/16 options are meant to be mutually-exclusive, ugh. 27 | } 28 | 29 | public static class Extensions 30 | { 31 | /// Not named AllowSync because it's too similar to . 32 | public static Boolean AllowOld( this AsyncMode value ) => ( (Int32)value & 1 ) == 1; 33 | 34 | public static Boolean AllowAsync( this AsyncMode value ) => ( (Int32)value & 2 ) == 2; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/FakeDb/FakeDbCommandBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Data.Common; 4 | using System.Globalization; 5 | 6 | namespace AsyncDataAdapter.Tests.FakeDb 7 | { 8 | public class FakeDbCommandBuilder : DbCommandBuilder 9 | { 10 | /// This ctor is only used by and should not be called by anyone, ever, really. 11 | internal FakeDbCommandBuilder() 12 | { 13 | base.QuotePrefix = "["; 14 | base.QuoteSuffix = "]"; 15 | } 16 | 17 | public FakeDbCommandBuilder( FakeDbDataAdapter adapter ) 18 | : base() 19 | { 20 | base.QuotePrefix = "["; 21 | base.QuoteSuffix = "]"; 22 | this.DataAdapter = adapter; 23 | } 24 | 25 | protected override void ApplyParameterInfo( DbParameter parameter, DataRow row, StatementType statementType, bool whereClause ) 26 | { 27 | // NOOP. 28 | } 29 | 30 | protected override string GetParameterName(int parameterOrdinal) 31 | { 32 | return "@p" + parameterOrdinal.ToString( CultureInfo.InvariantCulture ); 33 | } 34 | 35 | protected override string GetParameterName(string parameterName) 36 | { 37 | return "@" + parameterName; 38 | } 39 | 40 | protected override string GetParameterPlaceholder(int parameterOrdinal) 41 | { 42 | return "@p" + parameterOrdinal.ToString(CultureInfo.InvariantCulture); 43 | } 44 | 45 | protected override void SetRowUpdatingHandler(DbDataAdapter adapter) 46 | { 47 | FakeDbDataAdapter fda = (FakeDbDataAdapter)adapter; 48 | 49 | if (fda == base.DataAdapter) 50 | { 51 | // fda.RowUpdating -= this.RowUpdatingHandler; 52 | } 53 | else 54 | { 55 | // fda.RowUpdating += this.RowUpdatingHandler; 56 | } 57 | } 58 | 59 | // Why aren't these methods abstract? The default impl always throws. 60 | public override string QuoteIdentifier(string unquotedIdentifier) 61 | { 62 | if( String.IsNullOrWhiteSpace( unquotedIdentifier ) ) return unquotedIdentifier; 63 | 64 | return '[' + unquotedIdentifier.Trim( '[', ']' ) + ']'; 65 | } 66 | 67 | public override string UnquoteIdentifier(string quotedIdentifier) 68 | { 69 | if( String.IsNullOrWhiteSpace( quotedIdentifier ) ) return quotedIdentifier; 70 | 71 | return quotedIdentifier.Trim( '[', ']' ); 72 | } 73 | 74 | // 75 | 76 | public new FakeDbCommand GetUpdateCommand() => (FakeDbCommand)base.GetUpdateCommand(); 77 | 78 | public new FakeDbCommand GetUpdateCommand( Boolean useColumnsForParameterNames ) => (FakeDbCommand)base.GetUpdateCommand( useColumnsForParameterNames ); 79 | 80 | // 81 | 82 | public new FakeDbCommand GetDeleteCommand() => (FakeDbCommand)base.GetDeleteCommand(); 83 | 84 | public new FakeDbCommand GetDeleteCommand( Boolean useColumnsForParameterNames ) => (FakeDbCommand)base.GetDeleteCommand( useColumnsForParameterNames ); 85 | 86 | // 87 | 88 | public new FakeDbCommand GetInsertCommand() => (FakeDbCommand)base.GetInsertCommand(); 89 | 90 | public new FakeDbCommand GetInsertCommand( Boolean useColumnsForParameterNames ) => (FakeDbCommand)base.GetInsertCommand( useColumnsForParameterNames ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/FakeDb/FakeDbDataAdapter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Data.Common; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | using AsyncDataAdapter.Internal; 9 | 10 | namespace AsyncDataAdapter.Tests.FakeDb 11 | { 12 | /// NOTE: This does not implement . For that, see . 13 | public class FakeDbDataAdapter : DbDataAdapter, IFullDbDataAdapter 14 | { 15 | // TODO: Override every, single, method - and add call-counts. 16 | 17 | /// This ctor is only used by and should not be called by anyone, ever, really. 18 | internal FakeDbDataAdapter() 19 | { 20 | } 21 | 22 | /// The is required before can be used. 23 | public FakeDbDataAdapter( FakeDbCommand select ) 24 | : base() 25 | { 26 | this.SelectCommand = select ?? throw new ArgumentNullException(nameof(select)); 27 | } 28 | 29 | /// The is required before can be used. 30 | public FakeDbDataAdapter( FakeDbCommand select, FakeDbCommand update, FakeDbCommand insert, FakeDbCommand delete ) 31 | : this( select ) 32 | { 33 | this.UpdateCommand = update ?? throw new ArgumentNullException(nameof(update)); 34 | this.InsertCommand = insert ?? throw new ArgumentNullException(nameof(insert)); 35 | this.DeleteCommand = delete ?? throw new ArgumentNullException(nameof(delete)); 36 | } 37 | 38 | public new FakeDbCommand UpdateCommand 39 | { 40 | get => (FakeDbCommand)base.UpdateCommand; 41 | set => base.UpdateCommand = value; 42 | } 43 | 44 | public FakeDbCommandBuilder CreateCommandBuilder() 45 | { 46 | return new FakeDbCommandBuilder( this ); 47 | } 48 | } 49 | 50 | public class BatchingFakeDbDataAdapter : FakeDbDataAdapter /*DbDataAdapter*/, IBatchingAdapter 51 | { 52 | // TODO: Override every, single, method - and add call-counts. 53 | 54 | /// The is required before can be used. 55 | public BatchingFakeDbDataAdapter( FakeDbCommand select ) 56 | : base( select ) 57 | { 58 | } 59 | 60 | /// The is required before can be used. 61 | public BatchingFakeDbDataAdapter( FakeDbCommand select, FakeDbCommand update, FakeDbCommand insert, FakeDbCommand delete ) 62 | : base( select, update, insert, delete ) 63 | { 64 | } 65 | 66 | #region IBatchingAdapter 67 | // Btw, don't confuse *Update*FooAction with *Missing*FooAction. 68 | 69 | public MissingMappingAction UpdateMappingAction => BatchingAdapterMethods.UpdateMappingAction( base.MissingMappingAction ); 70 | public MissingSchemaAction UpdateSchemaAction => BatchingAdapterMethods.UpdateSchemaAction ( base.MissingSchemaAction ); 71 | 72 | public List BatchList { get; set; } = new List(); 73 | 74 | public int AddToBatch(DbCommand command) 75 | { 76 | this.BatchList.Add( command ); 77 | return this.BatchList.Count; 78 | } 79 | 80 | void IBatchingAdapter.ClearBatch() 81 | { 82 | this.BatchList.Clear(); 83 | } 84 | 85 | public Task ExecuteBatchAsync(CancellationToken cancellationToken) 86 | { 87 | throw new NotImplementedException(); 88 | } 89 | 90 | void IBatchingAdapter.TerminateBatching() 91 | { 92 | throw new NotImplementedException(); 93 | } 94 | 95 | IDataParameter IBatchingAdapter.GetBatchedParameter(int commandIdentifier, int parameterIndex) 96 | { 97 | return this.BatchList[commandIdentifier].Parameters[parameterIndex]; 98 | } 99 | 100 | bool IBatchingAdapter.GetBatchedRecordsAffected(int commandIdentifier, out int recordsAffected, out Exception error) 101 | { 102 | throw new NotImplementedException(); 103 | } 104 | 105 | void IBatchingAdapter.InitializeBatching() 106 | { 107 | throw new NotImplementedException(); 108 | } 109 | 110 | #endregion 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/FakeDb/FakeDbDelays.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace AsyncDataAdapter.Tests.FakeDb 6 | { 7 | public class FakeDbDelays 8 | { 9 | public static FakeDbDelays DefaultDelaysNone { get; } = new FakeDbDelays( 10 | connect : null, 11 | execute : null, 12 | transact: null, 13 | result : null, 14 | row : null 15 | ); 16 | 17 | public static FakeDbDelays DefaultDelays1MS { get; } = new FakeDbDelays( 18 | connect : TimeSpan.FromMilliseconds(1), 19 | execute : TimeSpan.FromMilliseconds(1), 20 | transact: TimeSpan.FromMilliseconds(1), 21 | result : TimeSpan.FromMilliseconds(1), 22 | row : TimeSpan.FromMilliseconds(1) 23 | ); 24 | 25 | public FakeDbDelays(TimeSpan? connect, TimeSpan? execute, TimeSpan? transact, TimeSpan? result, TimeSpan? row) 26 | { 27 | this.Connect = connect; 28 | this.Execute = execute; 29 | this.Transact = transact; 30 | this.Result = result; 31 | this.Row = row; 32 | } 33 | 34 | public TimeSpan? Connect { get; } 35 | public TimeSpan? Execute { get; } 36 | public TimeSpan? Transact { get; } 37 | public TimeSpan? Result { get; } 38 | public TimeSpan? Row { get; } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/FakeDb/FakeDbParameter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Data.Common; 4 | 5 | namespace AsyncDataAdapter.Tests.FakeDb 6 | { 7 | public class FakeDbParameter : DbParameter 8 | { 9 | public FakeDbParameter() 10 | { 11 | } 12 | 13 | public FakeDbParameter( String name, DbType dbType ) 14 | { 15 | this.ParameterName = name; 16 | this.DbType = dbType; 17 | } 18 | 19 | public override void ResetDbType() 20 | { 21 | this.DbType = DbType.String; 22 | } 23 | 24 | public override DbType DbType { get; set; } = DbType.String; 25 | public override ParameterDirection Direction { get; set; } = ParameterDirection.Input; 26 | public override Boolean IsNullable { get; set; } = true; 27 | public override String ParameterName { get; set; } = "?"; 28 | public override Int32 Size { get; set; } 29 | public override String SourceColumn { get; set; } 30 | public override Boolean SourceColumnNullMapping { get; set; } 31 | public override Object Value { get; set; } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/FakeDb/FakeDbParameterCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Data.Common; 6 | 7 | namespace AsyncDataAdapter.Tests.FakeDb 8 | { 9 | public class FakeDbParameterCollection : DbParameterCollection 10 | { 11 | private readonly Object listLock = new Object(); 12 | private readonly List list = new List(); 13 | 14 | public override int Add(object value) 15 | { 16 | if( value is FakeDbParameter p ) 17 | { 18 | this.list.Add( p ); 19 | return this.list.Count; 20 | } 21 | else 22 | { 23 | throw new ArgumentException( "Argument is null or is the wrong type." ); 24 | } 25 | } 26 | 27 | public override void AddRange(Array values) 28 | { 29 | foreach( Object obj in values ) 30 | { 31 | _ = this.Add( obj ); 32 | } 33 | } 34 | 35 | public override void Clear() 36 | { 37 | this.list.Clear(); 38 | } 39 | 40 | public override bool Contains(object value) 41 | { 42 | if( value is FakeDbParameter p ) 43 | { 44 | return this.list.Contains( p ); 45 | } 46 | else 47 | { 48 | return false; 49 | } 50 | } 51 | 52 | public override bool Contains(string value) 53 | { 54 | return this.list.Any( p => p.ParameterName == value ); 55 | } 56 | 57 | public override void CopyTo(Array array, int index) 58 | { 59 | FakeDbParameter[] a2 = (FakeDbParameter[])array; 60 | 61 | this.list.CopyTo( a2, index ); 62 | } 63 | 64 | public override IEnumerator GetEnumerator() 65 | { 66 | return this.list.GetEnumerator(); 67 | } 68 | 69 | protected override DbParameter GetParameter(int index) 70 | { 71 | return this.list[index]; 72 | } 73 | 74 | protected override DbParameter GetParameter(string parameterName) 75 | { 76 | return this.list.SingleOrDefault( p => p.ParameterName == parameterName ); 77 | } 78 | 79 | public override int IndexOf(object value) 80 | { 81 | if( value is FakeDbParameter p ) 82 | { 83 | return this.list.IndexOf( p ); 84 | } 85 | 86 | return -1; 87 | } 88 | 89 | public override int IndexOf(string parameterName) 90 | { 91 | var match = this.list 92 | .Select( ( p, idx ) => ( p, idx ) ) 93 | .SingleOrDefault( t => t.p.ParameterName == parameterName ); 94 | 95 | if( match != default ) 96 | { 97 | return match.idx; 98 | } 99 | 100 | return -1; 101 | } 102 | 103 | public override void Insert(int index, object value) 104 | { 105 | if( value is FakeDbParameter p ) 106 | { 107 | this.list.Insert( index, p ); 108 | } 109 | else 110 | { 111 | throw new ArgumentException( "Null or incorrect parameter type." ); 112 | } 113 | } 114 | 115 | public override void Remove(object value) 116 | { 117 | if( value is FakeDbParameter p ) 118 | { 119 | _ = this.list.Remove( p ); 120 | } 121 | else 122 | { 123 | throw new ArgumentException( "Null or incorrect parameter type." ); 124 | } 125 | } 126 | 127 | public override void RemoveAt(int index) 128 | { 129 | this.list.RemoveAt( index ); 130 | } 131 | 132 | public override void RemoveAt(string parameterName) 133 | { 134 | var match = this.list 135 | .Select( ( p, idx ) => ( p, idx ) ) 136 | .SingleOrDefault( t => t.p.ParameterName == parameterName ); 137 | 138 | if( match != default ) 139 | { 140 | this.list.RemoveAt( match.idx ); 141 | } 142 | } 143 | 144 | protected override void SetParameter(int index, DbParameter value) 145 | { 146 | if( value is FakeDbParameter p ) 147 | { 148 | this.list[ index ] = p; 149 | } 150 | else 151 | { 152 | throw new ArgumentException( "Null or incorrect parameter type." ); 153 | } 154 | } 155 | 156 | protected override void SetParameter(string parameterName, DbParameter value) 157 | { 158 | if( value is FakeDbParameter p ) 159 | { 160 | var match = this.list 161 | .Select( ( p, idx ) => ( p, idx ) ) 162 | .SingleOrDefault( t => t.p.ParameterName == parameterName ); 163 | 164 | if( match != default ) 165 | { 166 | this.list[ match.idx ] = p; 167 | } 168 | } 169 | else 170 | { 171 | throw new ArgumentException( "Null or incorrect parameter type." ); 172 | } 173 | } 174 | 175 | public override int Count => this.list.Count; 176 | 177 | public override object SyncRoot => this.listLock; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/FakeDb/FakeDbProviderFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.Common; 4 | 5 | namespace AsyncDataAdapter.Tests.FakeDb 6 | { 7 | public class FakeDbProviderFactory : DbProviderFactory 8 | { 9 | public static FakeDbProviderFactory Instance { get; } = new FakeDbProviderFactory(); 10 | 11 | private FakeDbProviderFactory() 12 | { 13 | } 14 | 15 | public override bool CanCreateDataSourceEnumerator => false; 16 | 17 | public override bool CanCreateCommandBuilder => true; 18 | 19 | public override bool CanCreateDataAdapter => true; 20 | 21 | public override DbCommand CreateCommand() 22 | { 23 | return new FakeDbCommand(); 24 | } 25 | 26 | public FakeDbCommand CreateCommand( FakeDbConnection connection, List testTables, FakeDbDelays delays ) 27 | { 28 | return new FakeDbCommand( connection: connection, testTables: testTables, delays ) 29 | { 30 | AsyncMode = connection.AsyncMode 31 | }; 32 | } 33 | 34 | public override DbCommandBuilder CreateCommandBuilder() 35 | { 36 | return new FakeDbCommandBuilder(); 37 | } 38 | 39 | public override DbConnection CreateConnection() 40 | { 41 | // Make this one fail every time, so we can be sure `CreateConnection` is never used without us knowing! 42 | // ...or just fail? 43 | throw new NotSupportedException(); 44 | 45 | //return new FakeDbConnection( asyncMode: AsyncMode.None, openDelayMS: -1, executeDelayMS: -1, readDelayMS: -1 ); 46 | } 47 | 48 | public override DbConnectionStringBuilder CreateConnectionStringBuilder() 49 | { 50 | // return new FakeDbConnectionStringBuilder(); 51 | return base.CreateConnectionStringBuilder(); 52 | } 53 | 54 | public override DbDataAdapter CreateDataAdapter() 55 | { 56 | return new FakeDbDataAdapter(); 57 | } 58 | 59 | public override DbDataSourceEnumerator CreateDataSourceEnumerator() 60 | { 61 | // return new FakeDbDataSourceEnumerator(); 62 | return base.CreateDataSourceEnumerator(); 63 | } 64 | 65 | public override DbParameter CreateParameter() 66 | { 67 | return new FakeDbParameter(); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/FakeDb/FakeDbTransaction.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | using System.Data.Common; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace AsyncDataAdapter.Tests.FakeDb 7 | { 8 | public class FakeDbTransaction : DbTransaction 9 | { 10 | public FakeDbTransaction( FakeDbConnection c, IsolationLevel level ) 11 | : base() 12 | { 13 | this.DbConnection = c; 14 | this.IsolationLevel = level; 15 | } 16 | 17 | protected override DbConnection DbConnection { get; } 18 | 19 | public override IsolationLevel IsolationLevel { get; } 20 | 21 | public override void Commit() 22 | { 23 | } 24 | 25 | public override Task CommitAsync(CancellationToken cancellationToken = default) 26 | { 27 | return Task.CompletedTask; 28 | } 29 | 30 | public override void Rollback() 31 | { 32 | } 33 | 34 | public override Task RollbackAsync(CancellationToken cancellationToken = default) 35 | { 36 | return Task.CompletedTask; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/FakeDb/ProxiedFakeDb/ProxiedFakeDbDataAdapter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Data.Common; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | using AsyncDataAdapter.Internal; 8 | 9 | namespace AsyncDataAdapter.Tests.FakeDb 10 | { 11 | public sealed class FakeProxiedDbDataAdapter : ProxyDbDataAdapter, IFullDbDataAdapter, IFullAsyncDbDataAdapter 12 | { 13 | public FakeProxiedDbDataAdapter( FakeDbCommand selectCmd ) 14 | : base( subject: new FakeDbDataAdapter( selectCmd ), batchingAdapter: null ) 15 | { 16 | } 17 | 18 | protected override DbCommandBuilder CreateCommandBuilder() 19 | { 20 | return new FakeDbCommandBuilder( this.Subject ); 21 | } 22 | } 23 | 24 | public sealed class BatchingFakeProxiedDbDataAdapter : ProxyDbDataAdapter, IFullDbDataAdapter, IFullAsyncDbDataAdapter 25 | { 26 | public BatchingFakeProxiedDbDataAdapter( FakeDbCommand selectCmd ) 27 | : this( adp: new BatchingFakeDbDataAdapter( selectCmd ) ) 28 | { 29 | 30 | } 31 | 32 | private BatchingFakeProxiedDbDataAdapter( BatchingFakeDbDataAdapter adp ) 33 | : base( subject: adp, batchingAdapter: adp ) 34 | { 35 | } 36 | 37 | protected override DbCommandBuilder CreateCommandBuilder() 38 | { 39 | return new FakeDbCommandBuilder( this.Subject ); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyCopyright("Copyright © Microsoft 2016")] 10 | [assembly: AssemblyTrademark("")] 11 | [assembly: AssemblyCulture("")] 12 | 13 | // Setting ComVisible to false makes the types in this assembly not visible 14 | // to COM components. If you need to access a type in this assembly from 15 | // COM, set the ComVisible attribute to true on that type. 16 | [assembly: ComVisible(false)] 17 | 18 | // The following GUID is for the ID of the typelib if this project is exposed to COM 19 | [assembly: Guid("34b7e6f3-858a-489b-bdd0-45cc6eb62551")] 20 | 21 | // Version information for an assembly consists of the following four values: 22 | // 23 | // Major Version 24 | // Minor Version 25 | // Build Number 26 | // Revision 27 | // 28 | // You can specify all the values or you can default the Build and Revision Numbers 29 | // by using the '*' as shown below: 30 | // [assembly: AssemblyVersion("1.0.*")] 31 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/ProxyDataAdapter/ReentrancyDetectionTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Data.Common; 5 | 6 | using NUnit.Framework; 7 | 8 | using Shouldly; 9 | 10 | using AsyncDataAdapter.Tests.FakeDb; 11 | 12 | namespace AsyncDataAdapter.Tests 13 | { 14 | public class ReentrancyDetectionTests 15 | { 16 | private static void FiddleWithPropertiesAsFakeProxiedDbDataAdapter( FakeProxiedDbDataAdapter adapter ) 17 | { 18 | { 19 | FakeDbCommand cmd = adapter.SelectCommand; 20 | adapter.SelectCommand = null; 21 | adapter.SelectCommand = cmd; 22 | } 23 | 24 | { 25 | FakeDbCommand cmd = adapter.InsertCommand; 26 | adapter.InsertCommand = null; 27 | adapter.InsertCommand = cmd; 28 | } 29 | 30 | { 31 | FakeDbCommand cmd = adapter.DeleteCommand; 32 | adapter.DeleteCommand = null; 33 | adapter.DeleteCommand = cmd; 34 | } 35 | 36 | { 37 | FakeDbCommand cmd = adapter.UpdateCommand; 38 | adapter.UpdateCommand = null; 39 | adapter.UpdateCommand = cmd; 40 | } 41 | } 42 | 43 | private static void FiddleWithPropertiesAsFakeDbDataAdapter( FakeDbDataAdapter adapter ) 44 | { 45 | { 46 | FakeDbCommand cmd = (FakeDbCommand)adapter.SelectCommand; 47 | adapter.SelectCommand = null; 48 | adapter.SelectCommand = cmd; 49 | } 50 | 51 | { 52 | FakeDbCommand cmd = (FakeDbCommand)adapter.InsertCommand; 53 | adapter.InsertCommand = null; 54 | adapter.InsertCommand = cmd; 55 | } 56 | 57 | { 58 | FakeDbCommand cmd = (FakeDbCommand)adapter.DeleteCommand; 59 | adapter.DeleteCommand = null; 60 | adapter.DeleteCommand = cmd; 61 | } 62 | 63 | { 64 | FakeDbCommand cmd = (FakeDbCommand)adapter.UpdateCommand; 65 | adapter.UpdateCommand = null; 66 | adapter.UpdateCommand = cmd; 67 | } 68 | } 69 | 70 | private static void FiddleWithPropertiesAsDbDataAdapter( DbDataAdapter adapter ) 71 | { 72 | { 73 | FakeDbCommand cmd = (FakeDbCommand)adapter.SelectCommand; 74 | adapter.SelectCommand = null; 75 | adapter.SelectCommand = cmd; 76 | } 77 | 78 | { 79 | FakeDbCommand cmd = (FakeDbCommand)adapter.InsertCommand; 80 | adapter.InsertCommand = null; 81 | adapter.InsertCommand = cmd; 82 | } 83 | 84 | { 85 | FakeDbCommand cmd = (FakeDbCommand)adapter.DeleteCommand; 86 | adapter.DeleteCommand = null; 87 | adapter.DeleteCommand = cmd; 88 | } 89 | 90 | { 91 | FakeDbCommand cmd = (FakeDbCommand)adapter.UpdateCommand; 92 | adapter.UpdateCommand = null; 93 | adapter.UpdateCommand = cmd; 94 | } 95 | } 96 | 97 | private static void FiddleWithPropertiesAsIDbDataAdapter( IDbDataAdapter adapter ) 98 | { 99 | { 100 | FakeDbCommand cmd = (FakeDbCommand)adapter.SelectCommand; 101 | adapter.SelectCommand = null; 102 | adapter.SelectCommand = cmd; 103 | } 104 | 105 | { 106 | FakeDbCommand cmd = (FakeDbCommand)adapter.InsertCommand; 107 | adapter.InsertCommand = null; 108 | adapter.InsertCommand = cmd; 109 | } 110 | 111 | { 112 | FakeDbCommand cmd = (FakeDbCommand)adapter.DeleteCommand; 113 | adapter.DeleteCommand = null; 114 | adapter.DeleteCommand = cmd; 115 | } 116 | 117 | { 118 | FakeDbCommand cmd = (FakeDbCommand)adapter.UpdateCommand; 119 | adapter.UpdateCommand = null; 120 | adapter.UpdateCommand = cmd; 121 | } 122 | } 123 | 124 | [Test] 125 | public void FakeDbDataAdapter_properties_should_not_have_infinite_loops_and_stack_overflows() 126 | { 127 | List randomDataSource = RandomDataGenerator.CreateRandomTables( seed: 1, tableCount: 2, /*allowZeroRowsInTablesByIdx: */ 1, 3 ); 128 | 129 | // Test that .Dispose() works (DbDataAdapter clears mutable properties in its disposal method) 130 | using( FakeDbConnection connection = new FakeDbConnection( asyncMode: AsyncMode.AllowSync ) ) 131 | using( FakeDbCommand selectCommand = connection.CreateCommand( testTables: randomDataSource ) ) 132 | using( FakeDbDataAdapter adapter = new FakeDbDataAdapter( selectCommand ) ) 133 | { 134 | } 135 | 136 | using( FakeDbConnection connection = new FakeDbConnection( asyncMode: AsyncMode.AllowSync ) ) 137 | using( FakeDbCommand selectCommand1 = connection.CreateCommand( testTables: randomDataSource ) ) 138 | using( FakeDbCommand selectCommand2 = connection.CreateCommand( testTables: randomDataSource ) ) 139 | using( FakeDbDataAdapter adapter = new FakeDbDataAdapter( selectCommand1 ) ) 140 | { 141 | using( FakeProxiedDbDataAdapter prox = new FakeProxiedDbDataAdapter( selectCommand1 ) ) 142 | { 143 | FiddleWithPropertiesAsFakeProxiedDbDataAdapter( prox ); 144 | 145 | FiddleWithPropertiesAsFakeDbDataAdapter( prox ); 146 | 147 | FiddleWithPropertiesAsDbDataAdapter( prox ); 148 | 149 | FiddleWithPropertiesAsIDbDataAdapter( prox ); 150 | } 151 | 152 | FiddleWithPropertiesAsFakeDbDataAdapter( adapter ); 153 | 154 | FiddleWithPropertiesAsDbDataAdapter( adapter ); 155 | 156 | FiddleWithPropertiesAsIDbDataAdapter( adapter ); 157 | } 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/ReflectionTest.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | using System.Data.Common; 3 | 4 | using NUnit.Framework; 5 | 6 | using AsyncDataAdapter.Internal; 7 | 8 | namespace AsyncDataAdapter.Tests 9 | { 10 | [TestFixture] 11 | public class ReflectionTest 12 | { 13 | [Test] 14 | public void AdapterInitIntShouldWork() 15 | { 16 | var e = new RowUpdatedEventArgs(null, null, StatementType.Select, null); 17 | 18 | Assert.DoesNotThrow(() => e.AdapterInit_( recordsAffected: 10 ) ); 19 | } 20 | 21 | [Test] 22 | public void AdapterInitDataRowArrayShouldWork() 23 | { 24 | var e = new RowUpdatedEventArgs(null, null, StatementType.Select, null); 25 | 26 | Assert.DoesNotThrow(() => e.AdapterInit_( dataRows: new DataRow[0] ) ); 27 | } 28 | 29 | [Test] 30 | public void GetRowsShouldWork() 31 | { 32 | var t = new DataTable(); 33 | 34 | var dataRow = t.NewRow(); 35 | 36 | var e = new RowUpdatedEventArgs(null, null, StatementType.Select, null); 37 | e.AdapterInit_(new []{dataRow}); 38 | 39 | var rows = e.GetRows_(); 40 | 41 | Assert.AreEqual(1, rows.Length); 42 | Assert.AreEqual(dataRow, rows[0]); 43 | } 44 | 45 | [Test] 46 | public void GetRowsForSingleRowShouldWork() 47 | { 48 | var t = new DataTable(); 49 | 50 | var dataRow = t.NewRow(); 51 | 52 | var e = new RowUpdatedEventArgs(null, null, StatementType.Select, null); 53 | e.AdapterInit_(new []{dataRow}); 54 | 55 | var row = e.GetRow_(0); 56 | 57 | Assert.AreEqual(dataRow, row); 58 | } 59 | 60 | [Test] 61 | public void EnsureAdditionalCapacityShouldWork() 62 | { 63 | var c = new DataTable().Columns; 64 | 65 | Assert.DoesNotThrow(() => c.EnsureAdditionalCapacity_(10)); 66 | } 67 | 68 | 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/SingleMethodTests/Fill1Test.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | 6 | using Shouldly; 7 | 8 | using AsyncDataAdapter.Tests.FakeDb; 9 | using NUnit.Framework; 10 | 11 | namespace AsyncDataAdapter.Tests.Big3 12 | { 13 | public class Fill1Test : SingleMethodTest 14 | { 15 | protected override DataSet RunDbDataAdapterSynchronous(List randomDataSource, FakeDbDataAdapter adapter) 16 | { 17 | DataSet dataSet = new DataSet(); 18 | 19 | Int32 rowsInFirstTable = adapter.Fill1( dataSet ); 20 | rowsInFirstTable.ShouldBe( 40 ); 21 | 22 | return dataSet; 23 | } 24 | 25 | protected override DataSet RunProxiedDbDataAdapter(List randomDataSource, FakeProxiedDbDataAdapter adapter) 26 | { 27 | DataSet dataSet = new DataSet(); 28 | 29 | Int32 rowsInFirstTable = adapter.Fill1( dataSet ); 30 | rowsInFirstTable.ShouldBe( 40 ); 31 | 32 | return dataSet; 33 | } 34 | 35 | protected override async Task RunProxiedDbDataAdapterAsync(List randomDataSource, FakeProxiedDbDataAdapter adapter) 36 | { 37 | DataSet dataSet = new DataSet(); 38 | 39 | Int32 rowsInFirstTable = await adapter.Fill1Async( dataSet ); 40 | rowsInFirstTable.ShouldBe( 40 ); 41 | 42 | return dataSet; 43 | } 44 | 45 | protected override async Task RunBatchingProxiedDbDataAdapterAsync(List randomDataSource, BatchingFakeProxiedDbDataAdapter adapter) 46 | { 47 | DataSet dataSet = new DataSet(); 48 | 49 | Int32 rowsInFirstTable = await adapter.Fill1Async( dataSet ); 50 | rowsInFirstTable.ShouldBe( 40 ); 51 | 52 | return dataSet; 53 | } 54 | 55 | protected override void AssertResult(DataSet dbSynchronous, DataSet dbProxied, DataSet dbProxiedAsync, DataSet dbBatchingProxiedAsync) 56 | { 57 | DataTableMethods.DataSetEquals( dbSynchronous, dbProxied , out String diffs1 ).ShouldBeTrue( customMessage: diffs1 ); 58 | DataTableMethods.DataSetEquals( dbSynchronous, dbProxiedAsync , out String diffs2 ).ShouldBeTrue( customMessage: diffs2 ); 59 | DataTableMethods.DataSetEquals( dbSynchronous, dbBatchingProxiedAsync, out String diffs3 ).ShouldBeTrue( customMessage: diffs3 ); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/SingleMethodTests/Fill2Test.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | 6 | using Shouldly; 7 | 8 | using AsyncDataAdapter.Tests.FakeDb; 9 | 10 | namespace AsyncDataAdapter.Tests.Big3 11 | { 12 | public class Fill2Test : SingleMethodTest 13 | { 14 | protected override DataSet RunDbDataAdapterSynchronous(List randomDataSource, FakeDbDataAdapter adapter) 15 | { 16 | DataSet dataSet = new DataSet(); 17 | 18 | Int32 rowsInFirstTable = adapter.Fill2( dataSet, srcTable: "Foobar" ); 19 | rowsInFirstTable.ShouldBe( 40 ); 20 | 21 | return dataSet; 22 | } 23 | 24 | protected override DataSet RunProxiedDbDataAdapter(List randomDataSource, FakeProxiedDbDataAdapter adapter) 25 | { 26 | DataSet dataSet = new DataSet(); 27 | 28 | Int32 rowsInFirstTable = adapter.Fill2( dataSet, srcTable: "Foobar" ); 29 | rowsInFirstTable.ShouldBe( 40 ); 30 | 31 | return dataSet; 32 | } 33 | 34 | protected override async Task RunProxiedDbDataAdapterAsync(List randomDataSource, FakeProxiedDbDataAdapter adapter) 35 | { 36 | DataSet dataSet = new DataSet(); 37 | 38 | Int32 rowsInFirstTable = await adapter.Fill2Async( dataSet, srcTable: "Foobar" ); 39 | rowsInFirstTable.ShouldBe( 40 ); 40 | 41 | return dataSet; 42 | } 43 | 44 | protected override async Task RunBatchingProxiedDbDataAdapterAsync(List randomDataSource, BatchingFakeProxiedDbDataAdapter adapter) 45 | { 46 | DataSet dataSet = new DataSet(); 47 | 48 | Int32 rowsInFirstTable = await adapter.Fill2Async( dataSet, srcTable: "Foobar" ); 49 | rowsInFirstTable.ShouldBe( 40 ); 50 | 51 | return dataSet; 52 | } 53 | 54 | protected override void AssertResult(DataSet dbSynchronous, DataSet dbProxied, DataSet dbProxiedAsync, DataSet dbBatchingProxiedAsync) 55 | { 56 | DataTableMethods.DataSetEquals( dbSynchronous, dbProxied , out String diffs1 ).ShouldBeTrue( customMessage: diffs1 ); 57 | DataTableMethods.DataSetEquals( dbSynchronous, dbProxiedAsync , out String diffs2 ).ShouldBeTrue( customMessage: diffs2 ); 58 | DataTableMethods.DataSetEquals( dbSynchronous, dbBatchingProxiedAsync, out String diffs3 ).ShouldBeTrue( customMessage: diffs3 ); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/SingleMethodTests/Fill3Test.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | 6 | using Shouldly; 7 | 8 | using AsyncDataAdapter.Tests.FakeDb; 9 | 10 | namespace AsyncDataAdapter.Tests.Big3 11 | { 12 | public class Fill3Test : SingleMethodTest 13 | { 14 | protected override DataSet RunDbDataAdapterSynchronous(List randomDataSource, FakeDbDataAdapter adapter) 15 | { 16 | DataSet dataSet = new DataSet(); 17 | 18 | Int32 rowsInFirstTable = adapter.Fill3( dataSet, startRecord: 5, maxRecords: 10, srcTable: "Barqux" ); 19 | rowsInFirstTable.ShouldBe( 10 ); 20 | 21 | return dataSet; 22 | } 23 | 24 | protected override DataSet RunProxiedDbDataAdapter(List randomDataSource, FakeProxiedDbDataAdapter adapter) 25 | { 26 | DataSet dataSet = new DataSet(); 27 | 28 | Int32 rowsInFirstTable = adapter.Fill3( dataSet, startRecord: 5, maxRecords: 10, srcTable: "Barqux" ); 29 | rowsInFirstTable.ShouldBe( 10 ); 30 | 31 | return dataSet; 32 | } 33 | 34 | protected override async Task RunProxiedDbDataAdapterAsync(List randomDataSource, FakeProxiedDbDataAdapter adapter) 35 | { 36 | DataSet dataSet = new DataSet(); 37 | 38 | Int32 rowsInFirstTable = await adapter.Fill3Async( dataSet, startRecord: 5, maxRecords: 10, srcTable: "Barqux" ); 39 | rowsInFirstTable.ShouldBe( 10 ); 40 | 41 | return dataSet; 42 | } 43 | 44 | protected override async Task RunBatchingProxiedDbDataAdapterAsync(List randomDataSource, BatchingFakeProxiedDbDataAdapter adapter) 45 | { 46 | DataSet dataSet = new DataSet(); 47 | 48 | Int32 rowsInFirstTable = await adapter.Fill3Async( dataSet, startRecord: 5, maxRecords: 10, srcTable: "Barqux" ); 49 | rowsInFirstTable.ShouldBe( 10 ); 50 | 51 | return dataSet; 52 | } 53 | 54 | protected override void AssertResult(DataSet dbSynchronous, DataSet dbProxied, DataSet dbProxiedAsync, DataSet dbBatchingProxiedAsync) 55 | { 56 | DataTableMethods.DataSetEquals( dbSynchronous, dbProxied , out String diffs1 ).ShouldBeTrue( customMessage: diffs1 ); 57 | DataTableMethods.DataSetEquals( dbSynchronous, dbProxiedAsync , out String diffs2 ).ShouldBeTrue( customMessage: diffs2 ); 58 | DataTableMethods.DataSetEquals( dbSynchronous, dbBatchingProxiedAsync, out String diffs3 ).ShouldBeTrue( customMessage: diffs3 ); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/SingleMethodTests/Fill4Test.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | 6 | using Shouldly; 7 | 8 | using AsyncDataAdapter.Tests.FakeDb; 9 | 10 | namespace AsyncDataAdapter.Tests.Big3 11 | { 12 | public class Fill4Test : SingleMethodTest 13 | { 14 | protected override DataTable RunDbDataAdapterSynchronous(List randomDataSource, FakeDbDataAdapter adapter) 15 | { 16 | DataTable dataTable = new DataTable(); 17 | 18 | Int32 rowsInFirstTable = adapter.Fill4( dataTable: dataTable ); 19 | rowsInFirstTable.ShouldBe( 40 ); 20 | 21 | return dataTable; 22 | } 23 | 24 | protected override DataTable RunProxiedDbDataAdapter(List randomDataSource, FakeProxiedDbDataAdapter adapter) 25 | { 26 | DataTable dataTable = new DataTable(); 27 | 28 | Int32 rowsInFirstTable = adapter.Fill4( dataTable: dataTable ); 29 | rowsInFirstTable.ShouldBe( 40 ); 30 | 31 | return dataTable; 32 | } 33 | 34 | protected override async Task RunProxiedDbDataAdapterAsync(List randomDataSource, FakeProxiedDbDataAdapter adapter) 35 | { 36 | DataTable dataTable = new DataTable(); 37 | 38 | Int32 rowsInFirstTable = await adapter.Fill4Async( dataTable: dataTable ); 39 | rowsInFirstTable.ShouldBe( 40 ); 40 | 41 | return dataTable; 42 | } 43 | 44 | protected override async Task RunBatchingProxiedDbDataAdapterAsync(List randomDataSource, BatchingFakeProxiedDbDataAdapter adapter) 45 | { 46 | DataTable dataTable = new DataTable(); 47 | 48 | Int32 rowsInFirstTable = await adapter.Fill4Async( dataTable ); 49 | rowsInFirstTable.ShouldBe( 40 ); 50 | 51 | return dataTable; 52 | } 53 | 54 | protected override void AssertResult(DataTable dbSynchronous, DataTable dbProxied, DataTable dbProxiedAsync, DataTable dbBatchingProxiedAsync) 55 | { 56 | DataTableMethods.DataTableEquals( dbSynchronous, dbProxied , "1", out String differences1 ).ShouldBeTrue( customMessage: "First different row at index: " + differences1 ); 57 | DataTableMethods.DataTableEquals( dbSynchronous, dbProxiedAsync , "2", out String differences2 ).ShouldBeTrue( customMessage: "First different row at index: " + differences2 ); 58 | DataTableMethods.DataTableEquals( dbSynchronous, dbBatchingProxiedAsync, "3", out String differences3 ).ShouldBeTrue( customMessage: "First different row at index: " + differences3 ); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/SingleMethodTests/Fill5Test.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | 6 | using Shouldly; 7 | 8 | using AsyncDataAdapter.Tests.FakeDb; 9 | 10 | namespace AsyncDataAdapter.Tests.Big3 11 | { 12 | public class Fill5Test : SingleMethodTest 13 | { 14 | // When using Fill5 with DataTable[] arrays of length other-than 1, the startRecord and maxRecord values must both be zero. 15 | // See `throw ADP.OnlyOneTableForStartRecordOrMaxRecords();` in the reference-source. 16 | 17 | protected override DataTable[] RunDbDataAdapterSynchronous(List randomDataSource, FakeDbDataAdapter adapter) 18 | { 19 | DataTable[] tables = new DataTable[5] 20 | { 21 | new DataTable( tableName: "Foo", tableNamespace: "NS1" ), 22 | new DataTable( tableName: "Bar", tableNamespace: "NS1" ), 23 | new DataTable( tableName: "Baz", tableNamespace: "NS1" ), 24 | new DataTable( tableName: "Qux", tableNamespace: "NS1" ), 25 | new DataTable( tableName: "Tux", tableNamespace: "NS1" ) 26 | }; 27 | 28 | Int32 rowsInFirstTable = adapter.Fill5( startRecord: 0, maxRecords: 0, dataTables: tables ); 29 | rowsInFirstTable.ShouldBe( 40 ); 30 | 31 | return tables; 32 | } 33 | 34 | protected override DataTable[] RunProxiedDbDataAdapter(List randomDataSource, FakeProxiedDbDataAdapter adapter) 35 | { 36 | DataTable[] tables = new DataTable[5] 37 | { 38 | new DataTable( tableName: "Foo", tableNamespace: "NS1" ), 39 | new DataTable( tableName: "Bar", tableNamespace: "NS1" ), 40 | new DataTable( tableName: "Baz", tableNamespace: "NS1" ), 41 | new DataTable( tableName: "Qux", tableNamespace: "NS1" ), 42 | new DataTable( tableName: "Tux", tableNamespace: "NS1" ) 43 | }; 44 | 45 | Int32 rowsInFirstTable = adapter.Fill5( startRecord: 0, maxRecords: 0, dataTables: tables ); 46 | rowsInFirstTable.ShouldBe( 40 ); 47 | 48 | return tables; 49 | } 50 | 51 | protected override async Task RunProxiedDbDataAdapterAsync(List randomDataSource, FakeProxiedDbDataAdapter adapter) 52 | { 53 | DataTable[] tables = new DataTable[5] 54 | { 55 | new DataTable( tableName: "Foo", tableNamespace: "NS1" ), 56 | new DataTable( tableName: "Bar", tableNamespace: "NS1" ), 57 | new DataTable( tableName: "Baz", tableNamespace: "NS1" ), 58 | new DataTable( tableName: "Qux", tableNamespace: "NS1" ), 59 | new DataTable( tableName: "Tux", tableNamespace: "NS1" ) 60 | }; 61 | 62 | Int32 rowsInFirstTable = await adapter.Fill5Async( startRecord: 0, maxRecords: 0, dataTables: tables ); 63 | rowsInFirstTable.ShouldBe( 40 ); 64 | 65 | return tables; 66 | } 67 | 68 | protected override async Task RunBatchingProxiedDbDataAdapterAsync(List randomDataSource, BatchingFakeProxiedDbDataAdapter adapter) 69 | { 70 | DataTable[] tables = new DataTable[5] 71 | { 72 | new DataTable( tableName: "Foo", tableNamespace: "NS1" ), 73 | new DataTable( tableName: "Bar", tableNamespace: "NS1" ), 74 | new DataTable( tableName: "Baz", tableNamespace: "NS1" ), 75 | new DataTable( tableName: "Qux", tableNamespace: "NS1" ), 76 | new DataTable( tableName: "Tux", tableNamespace: "NS1" ) 77 | }; 78 | 79 | Int32 rowsInFirstTable = await adapter.Fill5Async( startRecord: 0, maxRecords: 0, dataTables: tables ); 80 | rowsInFirstTable.ShouldBe( 40 ); 81 | 82 | return tables; 83 | } 84 | 85 | protected override void AssertResult(DataTable[] dbSynchronous, DataTable[] dbProxied, DataTable[] dbProxiedAsync, DataTable[] dbBatchingProxiedAsync) 86 | { 87 | DataTableMethods.DataTablesEquals( dbSynchronous, dbProxied , out String diffs1 ).ShouldBeTrue( customMessage: diffs1 ); 88 | DataTableMethods.DataTablesEquals( dbSynchronous, dbProxiedAsync , out String diffs2 ).ShouldBeTrue( customMessage: diffs2 ); 89 | DataTableMethods.DataTablesEquals( dbSynchronous, dbBatchingProxiedAsync, out String diffs3 ).ShouldBeTrue( customMessage: diffs3 ); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/SingleMethodTests/FillSchema1Test.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | 6 | using Shouldly; 7 | 8 | using AsyncDataAdapter.Tests.FakeDb; 9 | 10 | namespace AsyncDataAdapter.Tests.Big3 11 | { 12 | using FS1Pair = ValueTuple; 13 | 14 | public abstract class FillSchema1Test : SingleMethodTest 15 | { 16 | protected FillSchema1Test( SchemaType schemaType ) 17 | { 18 | this.SchemaType = schemaType; 19 | } 20 | 21 | protected SchemaType SchemaType { get; } 22 | 23 | protected override FS1Pair RunDbDataAdapterSynchronous(List randomDataSource, FakeDbDataAdapter adapter) 24 | { 25 | DataSet dataSet = new DataSet(); 26 | 27 | DataTable[] schemaTables = adapter.FillSchema1( dataSet, schemaType: this.SchemaType ); 28 | 29 | return ( dataSet, schemaTables ); 30 | } 31 | 32 | protected override FS1Pair RunProxiedDbDataAdapter(List randomDataSource, FakeProxiedDbDataAdapter adapter) 33 | { 34 | DataSet dataSet = new DataSet(); 35 | 36 | DataTable[] schemaTables = adapter.FillSchema1( dataSet, schemaType: this.SchemaType ); 37 | 38 | return ( dataSet, schemaTables ); 39 | } 40 | 41 | protected override async Task RunProxiedDbDataAdapterAsync(List randomDataSource, FakeProxiedDbDataAdapter adapter) 42 | { 43 | DataSet dataSet = new DataSet(); 44 | 45 | DataTable[] schemaTables = await adapter.FillSchema1Async( dataSet, schemaType: this.SchemaType ); 46 | 47 | return ( dataSet, schemaTables ); 48 | } 49 | 50 | protected override async Task RunBatchingProxiedDbDataAdapterAsync(List randomDataSource, BatchingFakeProxiedDbDataAdapter adapter) 51 | { 52 | DataSet dataSet = new DataSet(); 53 | 54 | DataTable[] schemaTables = await adapter.FillSchema1Async( dataSet, schemaType: this.SchemaType ); 55 | 56 | return ( dataSet, schemaTables ); 57 | } 58 | 59 | protected override void AssertResult( FS1Pair dbSynchronous, FS1Pair dbProxied, FS1Pair dbProxiedAsync, FS1Pair dbBatchingProxiedAsync ) 60 | { 61 | DataTableMethods.DataSetEquals( dbSynchronous.Item1, dbProxied .Item1, out String diffs11 ).ShouldBeTrue( customMessage: diffs11 ); 62 | DataTableMethods.DataSetEquals( dbSynchronous.Item1, dbProxiedAsync .Item1, out String diffs12 ).ShouldBeTrue( customMessage: diffs12 ); 63 | DataTableMethods.DataSetEquals( dbSynchronous.Item1, dbBatchingProxiedAsync.Item1, out String diffs13 ).ShouldBeTrue( customMessage: diffs13 ); 64 | 65 | DataTableMethods.DataTablesEquals( dbSynchronous.Item2, dbProxied .Item2, out String diffs21 ).ShouldBeTrue( customMessage: diffs21 ); 66 | DataTableMethods.DataTablesEquals( dbSynchronous.Item2, dbProxiedAsync .Item2, out String diffs22 ).ShouldBeTrue( customMessage: diffs22 ); 67 | DataTableMethods.DataTablesEquals( dbSynchronous.Item2, dbBatchingProxiedAsync.Item2, out String diffs23 ).ShouldBeTrue( customMessage: diffs23 ); 68 | } 69 | } 70 | 71 | public class FillSchema1MappedTest : FillSchema1Test 72 | { 73 | public FillSchema1MappedTest() 74 | : base( SchemaType.Mapped ) 75 | { 76 | } 77 | } 78 | 79 | public class FillSchema1SourceTest : FillSchema1Test 80 | { 81 | public FillSchema1SourceTest() 82 | : base( SchemaType.Source ) 83 | { 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/SingleMethodTests/FillSchema2Test.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | 6 | using Shouldly; 7 | 8 | using AsyncDataAdapter.Tests.FakeDb; 9 | 10 | namespace AsyncDataAdapter.Tests.Big3 11 | { 12 | using FS2Pair = ValueTuple; 13 | 14 | public abstract class FillSchema2Test : SingleMethodTest 15 | { 16 | protected FillSchema2Test( SchemaType schemaType ) 17 | { 18 | this.SchemaType = schemaType; 19 | } 20 | 21 | protected SchemaType SchemaType { get; } 22 | 23 | protected override FS2Pair RunDbDataAdapterSynchronous(List randomDataSource, FakeDbDataAdapter adapter) 24 | { 25 | DataTable dataTable = new DataTable(); 26 | 27 | DataTable schemaTables = adapter.FillSchema2( dataTable, schemaType: this.SchemaType ); 28 | 29 | return ( dataTable, schemaTables ); 30 | } 31 | 32 | protected override FS2Pair RunProxiedDbDataAdapter(List randomDataSource, FakeProxiedDbDataAdapter adapter) 33 | { 34 | DataTable dataTable = new DataTable(); 35 | 36 | DataTable schemaTables = adapter.FillSchema2( dataTable, schemaType: this.SchemaType ); 37 | 38 | return ( dataTable, schemaTables ); 39 | } 40 | 41 | protected override async Task RunProxiedDbDataAdapterAsync(List randomDataSource, FakeProxiedDbDataAdapter adapter) 42 | { 43 | DataTable dataTable = new DataTable(); 44 | 45 | DataTable schemaTables = await adapter.FillSchema2Async( dataTable, schemaType: this.SchemaType ); 46 | 47 | return ( dataTable, schemaTables ); 48 | } 49 | 50 | protected override async Task RunBatchingProxiedDbDataAdapterAsync(List randomDataSource, BatchingFakeProxiedDbDataAdapter adapter) 51 | { 52 | DataTable dataTable = new DataTable(); 53 | 54 | DataTable schemaTables = await adapter.FillSchema2Async( dataTable, schemaType: this.SchemaType ); 55 | 56 | return ( dataTable, schemaTables ); 57 | } 58 | 59 | protected override void AssertResult( FS2Pair dbSynchronous, FS2Pair dbProxied, FS2Pair dbProxiedAsync, FS2Pair dbBatchingProxiedAsync ) 60 | { 61 | DataTableMethods.DataTableEquals( dbSynchronous.Item1, dbProxied .Item1, "1", out String diffs11 ).ShouldBeTrue( customMessage: diffs11 ); 62 | DataTableMethods.DataTableEquals( dbSynchronous.Item1, dbProxiedAsync .Item1, "2", out String diffs12 ).ShouldBeTrue( customMessage: diffs12 ); 63 | DataTableMethods.DataTableEquals( dbSynchronous.Item1, dbBatchingProxiedAsync.Item1, "3", out String diffs13 ).ShouldBeTrue( customMessage: diffs13 ); 64 | 65 | DataTableMethods.DataTableEquals( dbSynchronous.Item2, dbProxied .Item2, "1", out String diffs21 ).ShouldBeTrue( customMessage: diffs21 ); 66 | DataTableMethods.DataTableEquals( dbSynchronous.Item2, dbProxiedAsync .Item2, "2", out String diffs22 ).ShouldBeTrue( customMessage: diffs22 ); 67 | DataTableMethods.DataTableEquals( dbSynchronous.Item2, dbBatchingProxiedAsync.Item2, "3", out String diffs23 ).ShouldBeTrue( customMessage: diffs23 ); 68 | } 69 | } 70 | 71 | public class FillSchema2MappedTest : FillSchema2Test 72 | { 73 | public FillSchema2MappedTest() 74 | : base( SchemaType.Mapped ) 75 | { 76 | } 77 | } 78 | 79 | public class FillSchema2SourceTest : FillSchema2Test 80 | { 81 | public FillSchema2SourceTest() 82 | : base( SchemaType.Source ) 83 | { 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/SingleMethodTests/FillSchema3Test.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | 6 | using Shouldly; 7 | 8 | using AsyncDataAdapter.Tests.FakeDb; 9 | 10 | namespace AsyncDataAdapter.Tests.Big3 11 | { 12 | using FS3Pair = ValueTuple; 13 | 14 | public abstract class FillSchema3Test : SingleMethodTest 15 | { 16 | protected FillSchema3Test( SchemaType schemaType ) 17 | { 18 | this.SchemaType = schemaType; 19 | } 20 | 21 | protected SchemaType SchemaType { get; } 22 | 23 | protected override FS3Pair RunDbDataAdapterSynchronous(List randomDataSource, FakeDbDataAdapter adapter) 24 | { 25 | DataSet dataSet = new DataSet(); 26 | 27 | DataTable[] schemaTables = adapter.FillSchema3( dataSet, schemaType: this.SchemaType, srcTable: "Foobar" ); 28 | 29 | return ( dataSet, schemaTables ); 30 | } 31 | 32 | protected override FS3Pair RunProxiedDbDataAdapter(List randomDataSource, FakeProxiedDbDataAdapter adapter) 33 | { 34 | DataSet dataSet = new DataSet(); 35 | 36 | DataTable[] schemaTables = adapter.FillSchema3( dataSet, schemaType: this.SchemaType, srcTable: "Foobar" ); 37 | 38 | return ( dataSet, schemaTables ); 39 | } 40 | 41 | protected override async Task RunProxiedDbDataAdapterAsync(List randomDataSource, FakeProxiedDbDataAdapter adapter) 42 | { 43 | DataSet dataSet = new DataSet(); 44 | 45 | DataTable[] schemaTables = await adapter.FillSchema3Async( dataSet, schemaType: this.SchemaType, srcTable: "Foobar" ); 46 | 47 | return ( dataSet, schemaTables ); 48 | } 49 | 50 | protected override async Task RunBatchingProxiedDbDataAdapterAsync(List randomDataSource, BatchingFakeProxiedDbDataAdapter adapter) 51 | { 52 | DataSet dataSet = new DataSet(); 53 | 54 | DataTable[] schemaTables = await adapter.FillSchema3Async( dataSet, schemaType: this.SchemaType, srcTable: "Foobar" ); 55 | 56 | return ( dataSet, schemaTables ); 57 | } 58 | 59 | protected override void AssertResult( FS3Pair dbSynchronous, FS3Pair dbProxied, FS3Pair dbProxiedAsync, FS3Pair dbBatchingProxiedAsync ) 60 | { 61 | DataTableMethods.DataSetEquals( dbSynchronous.Item1, dbProxied .Item1, out String diffs11 ).ShouldBeTrue( customMessage: diffs11 ); 62 | DataTableMethods.DataSetEquals( dbSynchronous.Item1, dbProxiedAsync .Item1, out String diffs12 ).ShouldBeTrue( customMessage: diffs12 ); 63 | DataTableMethods.DataSetEquals( dbSynchronous.Item1, dbBatchingProxiedAsync.Item1, out String diffs13 ).ShouldBeTrue( customMessage: diffs13 ); 64 | 65 | DataTableMethods.DataTablesEquals( dbSynchronous.Item2, dbProxied .Item2, out String diffs21 ).ShouldBeTrue( customMessage: diffs21 ); 66 | DataTableMethods.DataTablesEquals( dbSynchronous.Item2, dbProxiedAsync .Item2, out String diffs22 ).ShouldBeTrue( customMessage: diffs22 ); 67 | DataTableMethods.DataTablesEquals( dbSynchronous.Item2, dbBatchingProxiedAsync.Item2, out String diffs23 ).ShouldBeTrue( customMessage: diffs23 ); 68 | } 69 | } 70 | 71 | public class FillSchema3MappedTest : FillSchema3Test 72 | { 73 | public FillSchema3MappedTest() 74 | : base( SchemaType.Mapped ) 75 | { 76 | } 77 | } 78 | 79 | public class FillSchema3SourceTest : FillSchema3Test 80 | { 81 | public FillSchema3SourceTest() 82 | : base( SchemaType.Source ) 83 | { 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/SingleMethodTests/Update2Test.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Data.Common; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | using Shouldly; 9 | 10 | using AsyncDataAdapter.Tests.FakeDb; 11 | 12 | namespace AsyncDataAdapter.Tests.Big3 13 | { 14 | using U2Pair = ValueTuple,Int32>; 15 | 16 | public class Update2Test : SingleMethodTest 17 | { 18 | protected override U2Pair RunDbDataAdapterSynchronous(List randomDataSource, FakeDbDataAdapter adapter) 19 | { 20 | using( FakeDbCommandBuilder cmdBuilder = adapter.CreateCommandBuilder() ) 21 | { 22 | DataTable dataTable = new DataTable(); 23 | 24 | // `.Fill` returns the number of rows in the first table, not any subsequent tables. Yes, that's silly. 25 | Int32 rowsInFirstTable = adapter.Fill( dataTable ); 26 | rowsInFirstTable.ShouldBe( 40 ); 27 | 28 | // 29 | Dictionary rowsModified = DataTableMethods.MutateDataTable( dataTable ); 30 | 31 | // 32 | adapter.UpdateCommand = cmdBuilder.GetUpdateCommand(); 33 | adapter.UpdateCommand.NonQueryResultRowCountValue = ( cmd ) => DataTableMethods.GetUpdateStatementNonQueryResultRowCountValue( expectedTableName: "TODO", adapter, dataTable, cmd, rowsModified ); 34 | 35 | DataRow[] rows = dataTable.Rows.Cast().ToArray(); 36 | 37 | Int32 updatedRows = adapter.Update2( rows ); 38 | // updatedRows.ShouldBe( rowsModified ); 39 | 40 | return ( rows, rowsModified, updatedRows ); 41 | } 42 | } 43 | 44 | protected override U2Pair RunProxiedDbDataAdapter(List randomDataSource, FakeProxiedDbDataAdapter adapter) 45 | { 46 | using( FakeDbCommandBuilder cmdBuilder = new FakeDbCommandBuilder( adapter ) ) 47 | { 48 | DataTable dataTable = new DataTable(); 49 | 50 | // `.Fill` returns the number of rows in the first table, not any subsequent tables. Yes, that's silly. 51 | Int32 rowsInFirstTable = adapter.Fill( dataTable ); 52 | rowsInFirstTable.ShouldBe( 40 ); 53 | 54 | // 55 | Dictionary rowsModified = DataTableMethods.MutateDataTable( dataTable ); 56 | 57 | // 58 | adapter.UpdateCommand = cmdBuilder.GetUpdateCommand(); 59 | adapter.UpdateCommand.NonQueryResultRowCountValue = ( cmd ) => DataTableMethods.GetUpdateStatementNonQueryResultRowCountValue( expectedTableName: "TODO", adapter, dataTable, cmd, rowsModified ); 60 | 61 | DataRow[] rows = dataTable.Rows.Cast().ToArray(); 62 | 63 | Int32 updatedRows = adapter.Update2( rows ); 64 | // updatedRows.ShouldBe( rowsModified ); 65 | 66 | return ( rows, rowsModified, updatedRows ); 67 | } 68 | } 69 | 70 | protected override async Task RunProxiedDbDataAdapterAsync(List randomDataSource, FakeProxiedDbDataAdapter adapter) 71 | { 72 | using( DbCommandBuilder cmdBuilder = await adapter.CreateCommandBuilderAsync().ConfigureAwait(false) ) 73 | { 74 | DataTable dataTable = new DataTable(); 75 | 76 | // `.Fill` returns the number of rows in the first table, not any subsequent tables. Yes, that's silly. 77 | Int32 rowsInFirstTable = await adapter.FillAsync( dataTable ); 78 | rowsInFirstTable.ShouldBe( 40 ); 79 | 80 | // 81 | Dictionary rowsModified = DataTableMethods.MutateDataTable( dataTable ); 82 | 83 | // 84 | adapter.UpdateCommand = cmdBuilder.GetUpdateCommand(); 85 | adapter.UpdateCommand.NonQueryResultRowCountValue = ( cmd ) => DataTableMethods.GetUpdateStatementNonQueryResultRowCountValue( expectedTableName: "TODO", adapter, dataTable, cmd, rowsModified ); 86 | 87 | DataRow[] rows = dataTable.Rows.Cast().ToArray(); 88 | 89 | Int32 updatedRows = await adapter.Update2Async( rows ); 90 | // updatedRows.ShouldBe( rowsModified ); 91 | 92 | return ( rows, rowsModified, updatedRows ); 93 | } 94 | } 95 | 96 | protected override async Task RunBatchingProxiedDbDataAdapterAsync(List randomDataSource, BatchingFakeProxiedDbDataAdapter adapter) 97 | { 98 | using( DbCommandBuilder cmdBuilder = await adapter.CreateCommandBuilderAsync().ConfigureAwait(false) ) 99 | { 100 | DataTable dataTable = new DataTable(); 101 | 102 | // `.Fill` returns the number of rows in the first table, not any subsequent tables. Yes, that's silly. 103 | Int32 rowsInFirstTable = adapter.Fill( dataTable ); 104 | rowsInFirstTable.ShouldBe( 40 ); 105 | 106 | // 107 | Dictionary rowsModified = DataTableMethods.MutateDataTable( dataTable ); 108 | 109 | // 110 | adapter.UpdateCommand = cmdBuilder.GetUpdateCommand(); 111 | adapter.UpdateCommand.NonQueryResultRowCountValue = ( cmd ) => DataTableMethods.GetUpdateStatementNonQueryResultRowCountValue( expectedTableName: "TODO", adapter, dataTable, cmd, rowsModified ); 112 | 113 | DataRow[] rows = dataTable.Rows.Cast().ToArray(); 114 | 115 | Int32 updatedRows = await adapter.Update2Async( rows ); 116 | // updatedRows.ShouldBe( rowsModified ); 117 | 118 | return ( rows, rowsModified, updatedRows ); 119 | } 120 | } 121 | 122 | protected override void AssertResult( U2Pair dbSynchronous, U2Pair dbProxied, U2Pair dbProxiedAsync, U2Pair dbBatchingProxiedAsync ) 123 | { 124 | throw new NotImplementedException(); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/SingleMethodTests/Update3Test.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Data.Common; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | using Shouldly; 9 | 10 | using AsyncDataAdapter.Tests.FakeDb; 11 | 12 | namespace AsyncDataAdapter.Tests.Big3 13 | { 14 | using U3Pair = ValueTuple,Int32>; 15 | 16 | public class Update3Test : SingleMethodTest 17 | { 18 | protected override U3Pair RunDbDataAdapterSynchronous(List randomDataSource, FakeDbDataAdapter adapter) 19 | { 20 | using( FakeDbCommandBuilder cmdBuilder = adapter.CreateCommandBuilder() ) 21 | { 22 | DataTable dataTable = new DataTable(); 23 | 24 | // `.Fill` returns the number of rows in the first table, not any subsequent tables. Yes, that's silly. 25 | Int32 rowsInFirstTable = adapter.Fill( dataTable ); 26 | rowsInFirstTable.ShouldBe( 40 ); 27 | 28 | // 29 | Dictionary rowsModified = DataTableMethods.MutateDataTable( dataTable ); 30 | 31 | // 32 | adapter.UpdateCommand = cmdBuilder.GetUpdateCommand(); 33 | adapter.UpdateCommand.NonQueryResultRowCountValue = ( cmd ) => DataTableMethods.GetUpdateStatementNonQueryResultRowCountValue( expectedTableName: "TODO", adapter, dataTable, cmd, rowsModified ); 34 | 35 | Int32 updatedRows = adapter.Update3( dataTable ); 36 | // updatedRows.ShouldBe( rowsModified ); 37 | 38 | return ( dataTable, rowsModified, updatedRows ); 39 | } 40 | } 41 | 42 | protected override U3Pair RunProxiedDbDataAdapter(List randomDataSource, FakeProxiedDbDataAdapter adapter) 43 | { 44 | using( FakeDbCommandBuilder cmdBuilder = new FakeDbCommandBuilder( adapter ) ) 45 | { 46 | DataTable dataTable = new DataTable(); 47 | 48 | // `.Fill` returns the number of rows in the first table, not any subsequent tables. Yes, that's silly. 49 | Int32 rowsInFirstTable = adapter.Fill( dataTable ); 50 | rowsInFirstTable.ShouldBe( 40 ); 51 | 52 | // 53 | Dictionary rowsModified = DataTableMethods.MutateDataTable( dataTable ); 54 | 55 | // 56 | adapter.UpdateCommand = cmdBuilder.GetUpdateCommand(); 57 | adapter.UpdateCommand.NonQueryResultRowCountValue = ( cmd ) => DataTableMethods.GetUpdateStatementNonQueryResultRowCountValue( expectedTableName: "TODO", adapter, dataTable, cmd, rowsModified ); 58 | 59 | Int32 updatedRows = adapter.Update3( dataTable ); 60 | // updatedRows.ShouldBe( rowsModified ); 61 | 62 | return ( dataTable, rowsModified, updatedRows ); 63 | } 64 | } 65 | 66 | protected override async Task RunProxiedDbDataAdapterAsync(List randomDataSource, FakeProxiedDbDataAdapter adapter) 67 | { 68 | using( DbCommandBuilder cmdBuilder = await adapter.CreateCommandBuilderAsync().ConfigureAwait(false) ) 69 | { 70 | DataTable dataTable = new DataTable(); 71 | 72 | // `.Fill` returns the number of rows in the first table, not any subsequent tables. Yes, that's silly. 73 | Int32 rowsInFirstTable = await adapter.FillAsync( dataTable ); 74 | rowsInFirstTable.ShouldBe( 40 ); 75 | 76 | // 77 | Dictionary rowsModified = DataTableMethods.MutateDataTable( dataTable ); 78 | 79 | // 80 | adapter.UpdateCommand = cmdBuilder.GetUpdateCommand(); 81 | adapter.UpdateCommand.NonQueryResultRowCountValue = ( cmd ) => DataTableMethods.GetUpdateStatementNonQueryResultRowCountValue( expectedTableName: "TODO", adapter, dataTable, cmd, rowsModified ); 82 | 83 | Int32 updatedRows = await adapter.Update3Async( dataTable ); 84 | // updatedRows.ShouldBe( rowsModified ); 85 | 86 | return ( dataTable, rowsModified, updatedRows ); 87 | } 88 | } 89 | 90 | protected override async Task RunBatchingProxiedDbDataAdapterAsync(List randomDataSource, BatchingFakeProxiedDbDataAdapter adapter) 91 | { 92 | using( DbCommandBuilder cmdBuilder = await adapter.CreateCommandBuilderAsync().ConfigureAwait(false) ) 93 | { 94 | DataTable dataTable = new DataTable(); 95 | 96 | // `.Fill` returns the number of rows in the first table, not any subsequent tables. Yes, that's silly. 97 | Int32 rowsInFirstTable = adapter.Fill( dataTable ); 98 | rowsInFirstTable.ShouldBe( 40 ); 99 | 100 | // 101 | Dictionary rowsModified = DataTableMethods.MutateDataTable( dataTable ); 102 | 103 | // 104 | adapter.UpdateCommand = cmdBuilder.GetUpdateCommand(); 105 | adapter.UpdateCommand.NonQueryResultRowCountValue = ( cmd ) => DataTableMethods.GetUpdateStatementNonQueryResultRowCountValue( expectedTableName: "TODO", adapter, dataTable, cmd, rowsModified ); 106 | 107 | DataRow[] rows = dataTable.Rows.Cast().ToArray(); 108 | 109 | Int32 updatedRows = await adapter.Update3Async( dataTable ); 110 | // updatedRows.ShouldBe( rowsModified ); 111 | 112 | return ( dataTable, rowsModified, updatedRows ); 113 | } 114 | } 115 | 116 | protected override void AssertResult( U3Pair dbSynchronous, U3Pair dbProxied, U3Pair dbProxiedAsync, U3Pair dbBatchingProxiedAsync ) 117 | { 118 | throw new NotImplementedException(); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/SingleMethodTests/Update4Test.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Data.Common; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | using Shouldly; 9 | 10 | using AsyncDataAdapter.Tests.FakeDb; 11 | 12 | namespace AsyncDataAdapter.Tests.Big3 13 | { 14 | using U4Pair = ValueTuple,Int32>; 15 | 16 | public class Update4Test : SingleMethodTest 17 | { 18 | protected override U4Pair RunDbDataAdapterSynchronous(List randomDataSource, FakeDbDataAdapter adapter) 19 | { 20 | using( FakeDbCommandBuilder cmdBuilder = adapter.CreateCommandBuilder() ) 21 | { 22 | DataSet dataSet = new DataSet(); 23 | 24 | // `.Fill` returns the number of rows in the first table, not any subsequent tables. Yes, that's silly. 25 | Int32 rowsInFirstTable = adapter.Fill( dataSet ); 26 | rowsInFirstTable.ShouldBe( 40 ); 27 | 28 | // 29 | Dictionary rowsModified = DataTableMethods.MutateDataSet( dataSet ); 30 | 31 | // 32 | adapter.UpdateCommand = cmdBuilder.GetUpdateCommand(); 33 | adapter.UpdateCommand.NonQueryResultRowCountValue = ( cmd ) => DataTableMethods.GetUpdateStatementNonQueryResultRowCountValue( expectedTableName: "TODO", adapter, dataSet, cmd, rowsModified ); 34 | 35 | Int32 updatedRows = adapter.Update4( dataSet, srcTable: "RandomDataTable_2" ); 36 | // updatedRows.ShouldBe( rowsModified ); 37 | 38 | return ( dataSet, rowsModified, updatedRows ); 39 | } 40 | } 41 | 42 | protected override U4Pair RunProxiedDbDataAdapter(List randomDataSource, FakeProxiedDbDataAdapter adapter) 43 | { 44 | using( FakeDbCommandBuilder cmdBuilder = new FakeDbCommandBuilder( adapter ) ) 45 | { 46 | DataSet dataSet = new DataSet(); 47 | 48 | // `.Fill` returns the number of rows in the first table, not any subsequent tables. Yes, that's silly. 49 | Int32 rowsInFirstTable = adapter.Fill( dataSet ); 50 | rowsInFirstTable.ShouldBe( 40 ); 51 | 52 | // 53 | Dictionary rowsModified = DataTableMethods.MutateDataSet( dataSet ); 54 | 55 | // 56 | adapter.UpdateCommand = cmdBuilder.GetUpdateCommand(); 57 | adapter.UpdateCommand.NonQueryResultRowCountValue = ( cmd ) => DataTableMethods.GetUpdateStatementNonQueryResultRowCountValue( expectedTableName: "TODO", adapter, dataSet, cmd, rowsModified ); 58 | 59 | Int32 updatedRows = adapter.Update4( dataSet, srcTable: "RandomDataTable_2" ); 60 | // updatedRows.ShouldBe( rowsModified ); 61 | 62 | return ( dataSet, rowsModified, updatedRows ); 63 | } 64 | } 65 | 66 | protected override async Task RunProxiedDbDataAdapterAsync(List randomDataSource, FakeProxiedDbDataAdapter adapter) 67 | { 68 | using( DbCommandBuilder cmdBuilder = await adapter.CreateCommandBuilderAsync().ConfigureAwait(false) ) 69 | { 70 | DataSet dataSet = new DataSet(); 71 | 72 | // `.Fill` returns the number of rows in the first table, not any subsequent tables. Yes, that's silly. 73 | Int32 rowsInFirstTable = await adapter.FillAsync( dataSet ); 74 | rowsInFirstTable.ShouldBe( 40 ); 75 | 76 | // 77 | Dictionary rowsModified = DataTableMethods.MutateDataSet( dataSet ); 78 | 79 | // 80 | adapter.UpdateCommand = cmdBuilder.GetUpdateCommand(); 81 | adapter.UpdateCommand.NonQueryResultRowCountValue = ( cmd ) => DataTableMethods.GetUpdateStatementNonQueryResultRowCountValue( expectedTableName: "TODO", adapter, dataSet, cmd, rowsModified ); 82 | 83 | Int32 updatedRows = await adapter.Update4Async( dataSet, srcTable: "RandomDataTable_2" ); 84 | // updatedRows.ShouldBe( rowsModified ); 85 | 86 | return ( dataSet, rowsModified, updatedRows ); 87 | } 88 | } 89 | 90 | protected override async Task RunBatchingProxiedDbDataAdapterAsync(List randomDataSource, BatchingFakeProxiedDbDataAdapter adapter) 91 | { 92 | using( DbCommandBuilder cmdBuilder = await adapter.CreateCommandBuilderAsync().ConfigureAwait(false) ) 93 | { 94 | DataSet dataSet = new DataSet(); 95 | 96 | // `.Fill` returns the number of rows in the first table, not any subsequent tables. Yes, that's silly. 97 | Int32 rowsInFirstTable = adapter.Fill( dataSet ); 98 | rowsInFirstTable.ShouldBe( 40 ); 99 | 100 | // 101 | Dictionary rowsModified = DataTableMethods.MutateDataSet( dataSet ); 102 | 103 | // 104 | adapter.UpdateCommand = cmdBuilder.GetUpdateCommand(); 105 | adapter.UpdateCommand.NonQueryResultRowCountValue = ( cmd ) => DataTableMethods.GetUpdateStatementNonQueryResultRowCountValue( expectedTableName: "TODO", adapter, dataSet, cmd, rowsModified ); 106 | 107 | Int32 updatedRows = await adapter.Update4Async( dataSet, srcTable: "RandomDataTable_2" ); 108 | // updatedRows.ShouldBe( rowsModified ); 109 | 110 | return ( dataSet, rowsModified, updatedRows ); 111 | } 112 | } 113 | 114 | protected override void AssertResult( U4Pair dbSynchronous, U4Pair dbProxied, U4Pair dbProxiedAsync, U4Pair dbBatchingProxiedAsync ) 115 | { 116 | throw new NotImplementedException(); 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/SingleMethodTests/Utility/SingleMethodTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | 5 | using AsyncDataAdapter.Tests.FakeDb; 6 | 7 | using NUnit.Framework; 8 | 9 | namespace AsyncDataAdapter.Tests.Big3 10 | { 11 | public abstract class SingleMethodTest 12 | { 13 | protected abstract TResult RunDbDataAdapterSynchronous( List randomDataSource, FakeDbDataAdapter adapter ); 14 | 15 | protected abstract TResult RunProxiedDbDataAdapter( List randomDataSource, FakeProxiedDbDataAdapter adapter ); 16 | 17 | protected abstract Task RunProxiedDbDataAdapterAsync( List randomDataSource, FakeProxiedDbDataAdapter adapter ); 18 | 19 | protected abstract Task RunBatchingProxiedDbDataAdapterAsync( List randomDataSource, BatchingFakeProxiedDbDataAdapter adapter ); 20 | 21 | protected abstract void AssertResult( TResult dbSynchronous, TResult dbProxied, TResult dbProxiedAsync, TResult dbBatchingProxiedAsync ); 22 | 23 | // 24 | 25 | [Test] 26 | public virtual async Task Test_with_single_table() 27 | { 28 | await this.RunAsync( seed: 1234, tableCount: 1 ); 29 | } 30 | 31 | [Test] 32 | public virtual async Task Test_with_five_tables() 33 | { 34 | await this.RunAsync( seed: 1234, tableCount: 5 ); 35 | } 36 | 37 | protected async Task RunAsync( Int32 seed, Int32 tableCount ) 38 | { 39 | TResult dbSynchronous = this.DoRunDbDataAdapterSynchronous( seed, tableCount ); 40 | 41 | TResult dbProxied = this.DoRunProxiedDbDataAdapter( seed, tableCount ); 42 | 43 | TResult dbProxiedAsync = await this.DoRunProxiedDbDataAdapterAsync( seed, tableCount ); 44 | 45 | TResult dbBatchingProxiedAsync = await this.DoRunBatchingProxiedDbDataAdapterAsync( seed, tableCount ); 46 | 47 | // 48 | 49 | this.AssertResult( dbSynchronous, dbProxied, dbProxiedAsync, dbBatchingProxiedAsync ); 50 | } 51 | 52 | protected TResult DoRunDbDataAdapterSynchronous( Int32 seed, Int32 tableCount ) 53 | { 54 | List randomDataSource = RandomDataGenerator.CreateRandomTables( seed: seed, tableCount: tableCount ); 55 | 56 | using( FakeDbConnection connection = new FakeDbConnection( asyncMode: AsyncMode.AllowSync ) ) 57 | using( FakeDbCommand selectCommand = connection.CreateCommand( testTables: randomDataSource ) ) 58 | { 59 | connection.Open(); 60 | 61 | using( FakeDbDataAdapter adapter = new FakeDbDataAdapter( selectCommand ) ) 62 | { 63 | return this.RunDbDataAdapterSynchronous( randomDataSource, adapter ); 64 | } 65 | } 66 | } 67 | 68 | protected TResult DoRunProxiedDbDataAdapter( Int32 seed, Int32 tableCount ) 69 | { 70 | List randomDataSource = RandomDataGenerator.CreateRandomTables( seed: seed, tableCount: tableCount ); 71 | 72 | using( FakeDbConnection connection = new FakeDbConnection( asyncMode: AsyncMode.AllowSync ) ) 73 | using( FakeDbCommand selectCommand = connection.CreateCommand( testTables: randomDataSource ) ) 74 | { 75 | connection.Open(); 76 | 77 | using( FakeProxiedDbDataAdapter adapter = new FakeProxiedDbDataAdapter( selectCommand ) ) 78 | { 79 | return this.RunProxiedDbDataAdapter( randomDataSource, adapter ); 80 | } 81 | } 82 | } 83 | 84 | protected async Task DoRunProxiedDbDataAdapterAsync( Int32 seed, Int32 tableCount ) 85 | { 86 | List randomDataSource = RandomDataGenerator.CreateRandomTables( seed: seed, tableCount: tableCount ); 87 | 88 | using( FakeDbConnection connection = new FakeDbConnection( asyncMode: AsyncMode.AwaitAsync ) ) 89 | using( FakeDbCommand selectCommand = connection.CreateCommand( testTables: randomDataSource ) ) 90 | { 91 | await connection.OpenAsync(); 92 | 93 | using( FakeProxiedDbDataAdapter adapter = new FakeProxiedDbDataAdapter( selectCommand ) ) 94 | { 95 | return await this.RunProxiedDbDataAdapterAsync( randomDataSource, adapter ); 96 | } 97 | } 98 | } 99 | 100 | protected async Task DoRunBatchingProxiedDbDataAdapterAsync( Int32 seed, Int32 tableCount ) 101 | { 102 | List randomDataSource = RandomDataGenerator.CreateRandomTables( seed: seed, tableCount: tableCount ); 103 | 104 | using( FakeDbConnection connection = new FakeDbConnection( asyncMode: AsyncMode.AwaitAsync ) ) 105 | using( FakeDbCommand selectCommand = connection.CreateCommand( testTables: randomDataSource ) ) 106 | { 107 | await connection.OpenAsync(); 108 | 109 | using( BatchingFakeProxiedDbDataAdapter adapter = new BatchingFakeProxiedDbDataAdapter( selectCommand ) ) 110 | { 111 | return await this.RunBatchingProxiedDbDataAdapterAsync( randomDataSource, adapter ); 112 | } 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/SqlServer/MicrosoftDataSqlTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.Common; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | using Microsoft.Data.SqlClient; 8 | 9 | namespace AsyncDataAdapter.Tests.SqlServer 10 | { 11 | public class MicrosoftDataSqlTests : BaseSqlDataAdapterTest 12 | { 13 | protected override SqlConnection CreateConnection( String connectionString ) 14 | { 15 | return new SqlConnection( connectionString ); 16 | } 17 | 18 | protected override SqlCommand CreateCommand(SqlConnection connection) 19 | { 20 | return connection.CreateCommand(); 21 | } 22 | 23 | protected override SqlDataAdapter CreateDbAdapter( SqlCommand cmd ) 24 | { 25 | return new SqlDataAdapter( cmd ); 26 | } 27 | 28 | protected override MSSqlAsyncDbDataAdapter CreateAsyncDbAdapter( SqlCommand cmd ) 29 | { 30 | return new MSSqlAsyncDbDataAdapter( cmd ); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/SqlServer/SystemDataSqlTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.Common; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | using System.Data.SqlClient; 8 | 9 | namespace AsyncDataAdapter.Tests.SqlServer 10 | { 11 | public class SystemDataSqlTests : BaseSqlDataAdapterTest 12 | { 13 | protected override SqlConnection CreateConnection( String connectionString ) 14 | { 15 | return new SqlConnection( connectionString ); 16 | } 17 | 18 | protected override SqlCommand CreateCommand( SqlConnection connection ) 19 | { 20 | return connection.CreateCommand(); 21 | } 22 | 23 | protected override SqlDataAdapter CreateDbAdapter( SqlCommand cmd ) 24 | { 25 | return new SqlDataAdapter( cmd ); 26 | } 27 | 28 | protected override SqlAsyncDbDataAdapter CreateAsyncDbAdapter( SqlCommand cmd ) 29 | { 30 | return new SqlAsyncDbDataAdapter( cmd ); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/TestConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | using Microsoft.Extensions.Configuration; 5 | 6 | namespace AsyncDataAdapter.Tests 7 | { 8 | public class TestConfiguration 9 | { 10 | // private const string _defaultConnectionString = @"server=.\sqlexpress;database=AsyncDataReaderTest;Trusted_Connection=Yes"; 11 | private const string _defaultConnectionString = @"server=.\SQL2017;database=AsyncDataReaderTest;Trusted_Connection=Yes"; 12 | 13 | public static TestConfiguration Instance { get; } = new TestConfiguration(); 14 | 15 | /// See https://stackoverflow.com/questions/39791634/read-appsettings-json-values-in-net-core-test-project/47591692 16 | private TestConfiguration() 17 | { 18 | const string fileName = "test-config.json"; 19 | if( File.Exists( fileName )) 20 | { 21 | IConfigurationRoot config = new ConfigurationBuilder() 22 | .AddJsonFile( fileName ) 23 | .Build(); 24 | 25 | this.ConnectionString = config["ConnectionString"]; 26 | #pragma warning disable IDE0075 // Simplify conditional expression 27 | this.DatabaseTestsEnabled = Boolean.TryParse( config["DatabaseTestsEnabed"], out Boolean b ) ? b : true; 28 | #pragma warning restore 29 | } 30 | 31 | if( string.IsNullOrWhiteSpace(this.ConnectionString) ) 32 | { 33 | this.ConnectionString = _defaultConnectionString; 34 | } 35 | } 36 | 37 | public Boolean DatabaseTestsEnabled { get; } 38 | public String ConnectionString { get; } 39 | 40 | /* Sample appconfig json (note the '\' is escaped!): 41 | 42 | { 43 | "ConnectionString": "server=.\\SQL2017;database=AsyncDataReaderTest;Trusted_Connection=Yes" 44 | } 45 | 46 | */ 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/TestUtility/FakeDbCommandBuilderTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | using AsyncDataAdapter.Tests.FakeDb; 4 | 5 | using NUnit.Framework; 6 | 7 | using Shouldly; 8 | 9 | namespace AsyncDataAdapter.Tests.MetaTests 10 | { 11 | /// Just some meta-tests so I know my class works. 12 | public class FakeDbCommandBuilderTests 13 | { 14 | [Test] 15 | public void FakeDbCommandBuilder_should_work() 16 | { 17 | List randomDataSource = RandomDataGenerator.CreateRandomTables( seed: 1234, tableCount: 5, /*allowZeroRowsInTablesByIdx: */ 1, 3 ); 18 | 19 | using( FakeDbConnection connection = new FakeDbConnection( asyncMode: AsyncMode.AllowSync ) ) 20 | using( FakeDbCommand selectCommand = connection.CreateCommand( testTables: randomDataSource ) ) 21 | { 22 | connection.Open(); 23 | 24 | using( FakeDbDataAdapter adpt = new FakeDbDataAdapter( selectCommand ) ) 25 | using( FakeDbCommandBuilder cmdBuilder = adpt.CreateCommandBuilder() ) 26 | { 27 | cmdBuilder.DataAdapter.ShouldBe( adpt ); 28 | 29 | FakeDbCommand deleteCommand1 = cmdBuilder.GetDeleteCommand(); // Same as ` useColumnsForParameterNames: false );` 30 | FakeDbCommand updateCommand1 = cmdBuilder.GetUpdateCommand(); 31 | FakeDbCommand insertCommand1 = cmdBuilder.GetInsertCommand(); 32 | 33 | FakeDbCommand deleteCommand2 = cmdBuilder.GetDeleteCommand( useColumnsForParameterNames: true ); 34 | FakeDbCommand updateCommand2 = cmdBuilder.GetUpdateCommand( useColumnsForParameterNames: true ); 35 | FakeDbCommand insertCommand2 = cmdBuilder.GetInsertCommand( useColumnsForParameterNames: true ); 36 | 37 | _ = deleteCommand1.ShouldNotBeNull(); 38 | _ = updateCommand1.ShouldNotBeNull(); 39 | _ = insertCommand1.ShouldNotBeNull(); 40 | 41 | _ = deleteCommand2.ShouldNotBeNull(); 42 | _ = updateCommand2.ShouldNotBeNull(); 43 | _ = insertCommand2.ShouldNotBeNull(); 44 | } 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/TestUtility/FakeDbDataReaderTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | 5 | using NUnit.Framework; 6 | 7 | using Shouldly; 8 | 9 | using AsyncDataAdapter.Tests.FakeDb; 10 | 11 | namespace AsyncDataAdapter.Tests.MetaTests 12 | { 13 | /// Just some meta-tests so I know my class works. 14 | public class FakeDbDataReaderTests 15 | { 16 | [Test] 17 | public void FakeDbDataReader_Sync_should_behave() 18 | { 19 | FakeDbCommand cmd = new FakeDbCommand(); 20 | 21 | FakeDbDataReader rdr = new FakeDbDataReader( cmd ); 22 | 23 | List tables = RandomDataGenerator.CreateRandomTables( seed: 1234, tableCount: 5 ); 24 | 25 | rdr.ResetAndLoadTestData( tables ); 26 | 27 | rdr.AllTables.Count.ShouldBe( 5 ); 28 | // The RNG is rather fickle, so don't test these. See the `RandomDataGenerator_seed_values_should_produce_expected_results` test above instead. 29 | // rdr.AllTables[0].Rows.Count.ShouldBe( 40 ); 30 | // rdr.AllTables[1].Rows.Count.ShouldBe( 52 ); 31 | // rdr.AllTables[2].Rows.Count.ShouldBe( 79 ); 32 | // rdr.AllTables[3].Rows.Count.ShouldBe( 37 ); 33 | // rdr.AllTables[4].Rows.Count.ShouldBe( 31 ); 34 | 35 | // 36 | 37 | rdr.AsyncMode = AsyncMode.AllowSync; 38 | 39 | // Table 0: 40 | { 41 | Int32 i = 0; 42 | while( rdr.Read() ) 43 | { 44 | i++; 45 | } 46 | 47 | i.ShouldBe( tables[0].Rows.Count ); 48 | } 49 | 50 | // Table 1: 51 | rdr.NextResult().ShouldBeTrue(); 52 | { 53 | Int32 i = 0; 54 | while( rdr.Read() ) 55 | { 56 | i++; 57 | } 58 | 59 | i.ShouldBe( tables[1].Rows.Count ); 60 | } 61 | 62 | // Table 2: 63 | rdr.NextResult().ShouldBeTrue(); 64 | { 65 | Int32 i = 0; 66 | while( rdr.Read() ) 67 | { 68 | i++; 69 | } 70 | 71 | i.ShouldBe( tables[2].Rows.Count ); 72 | } 73 | 74 | // Table 3: 75 | rdr.NextResult().ShouldBeTrue(); 76 | { 77 | Int32 i = 0; 78 | while( rdr.Read() ) 79 | { 80 | i++; 81 | } 82 | 83 | i.ShouldBe( tables[3].Rows.Count ); 84 | } 85 | 86 | // Table 4: 87 | rdr.NextResult().ShouldBeTrue(); 88 | { 89 | Int32 i = 0; 90 | while( rdr.Read() ) 91 | { 92 | i++; 93 | } 94 | 95 | i.ShouldBe( tables[4].Rows.Count ); 96 | } 97 | 98 | rdr.NextResult().ShouldBeFalse(); 99 | } 100 | 101 | [Test] 102 | public async Task FakeDbDataReader_Async_should_behave() 103 | { 104 | FakeDbCommand cmd = new FakeDbCommand(); 105 | 106 | FakeDbDataReader rdr = new FakeDbDataReader( cmd ); 107 | 108 | // 109 | 110 | List tables = RandomDataGenerator.CreateRandomTables( seed: 1234, tableCount: 5 ); 111 | 112 | rdr.ResetAndLoadTestData( tables ); 113 | 114 | rdr.AllTables.Count.ShouldBe( 5 ); 115 | 116 | // 117 | 118 | rdr.AsyncMode = AsyncMode.AwaitAsync; 119 | 120 | // Table 0: 121 | { 122 | Int32 i = 0; 123 | while( await rdr.ReadAsync() ) 124 | { 125 | i++; 126 | } 127 | 128 | i.ShouldBe( tables[0].Rows.Count ); 129 | } 130 | 131 | // Table 1: 132 | ( await rdr.NextResultAsync() ).ShouldBeTrue(); 133 | { 134 | Int32 i = 0; 135 | while( await rdr.ReadAsync() ) 136 | { 137 | i++; 138 | } 139 | 140 | i.ShouldBe( tables[1].Rows.Count ); 141 | } 142 | 143 | // Table 2: 144 | ( await rdr.NextResultAsync() ).ShouldBeTrue(); 145 | { 146 | Int32 i = 0; 147 | while( await rdr.ReadAsync() ) 148 | { 149 | i++; 150 | } 151 | 152 | i.ShouldBe( tables[2].Rows.Count ); 153 | } 154 | 155 | // Table 3: 156 | ( await rdr.NextResultAsync() ).ShouldBeTrue(); 157 | { 158 | Int32 i = 0; 159 | while( await rdr.ReadAsync() ) 160 | { 161 | i++; 162 | } 163 | 164 | i.ShouldBe( tables[3].Rows.Count ); 165 | } 166 | 167 | // Table 4: 168 | ( await rdr.NextResultAsync() ).ShouldBeTrue(); 169 | { 170 | Int32 i = 0; 171 | while( await rdr.ReadAsync() ) 172 | { 173 | i++; 174 | } 175 | 176 | i.ShouldBe( tables[4].Rows.Count ); 177 | } 178 | 179 | ( await rdr.NextResultAsync() ).ShouldBeFalse(); 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/TestUtility/RandomDataGeneratorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | using NUnit.Framework; 5 | 6 | using Shouldly; 7 | 8 | namespace AsyncDataAdapter.Tests.MetaTests 9 | { 10 | /// Just some meta-tests so I know my class works. 11 | public class RandomDataGeneratorTests 12 | { 13 | [Test] 14 | [TestCase( new Object[] { /*seed:*/ 1234, /*table row counts: */ new Int32[] { 40, 68, 35, 65, 76 } } )] 15 | public void RandomDataGenerator_seed_values_should_produce_expected_results( Int32 seed, Int32[] rowCounts ) 16 | { 17 | List tables = RandomDataGenerator.CreateRandomTables( seed: seed, tableCount: rowCounts.Length ); 18 | 19 | tables.Count.ShouldBe( rowCounts.Length ); 20 | 21 | for( Int32 i = 0; i < rowCounts.Length; i++ ) 22 | { 23 | tables[i].Rows.Count.ShouldBe( rowCounts[i] ); 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /AsyncDataAdapter.Tests/TestUtility/TestTable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | 5 | namespace AsyncDataAdapter.Tests 6 | { 7 | public class TestTable 8 | { 9 | public TestTable( int index, string name, string[] columnNames, Type[] columnTypes, List rows ) 10 | { 11 | this.Index = index; 12 | this.Name = name ?? throw new ArgumentNullException(nameof(name)); 13 | this.ColumnNames = columnNames ?? throw new ArgumentNullException(nameof(columnNames)); 14 | this.ColumnTypes = columnTypes ?? throw new ArgumentNullException(nameof(columnTypes)); 15 | this.Rows = rows ?? throw new ArgumentNullException(nameof(rows)); 16 | } 17 | 18 | public Int32 Index { get; } 19 | 20 | public String Name { get; } // hold on, do names exist outside of DataTable/DataSet? 21 | 22 | public String[] ColumnNames { get; } 23 | 24 | public Type[] ColumnTypes { get; } 25 | 26 | public List Rows { get; } 27 | 28 | public Int32 PKColumnIndex => 0; // The 0th column is always the PK column. 29 | 30 | // 31 | 32 | /// Creates a DataTable with the same structure as this table, but without any rows. This table can be used by for use with . 33 | public DataTable CreateMinimalSchemaTable() 34 | { 35 | DataTable dt = new DataTable( tableName: this.Name ); 36 | 37 | for( Int32 x = 0; x < this.ColumnNames.Length; x++ ) 38 | { 39 | DataColumn col = new DataColumn( columnName: this.ColumnNames[x], dataType: this.ColumnTypes[x] ); 40 | if( x == 0 ) 41 | { 42 | col.Unique = true; 43 | col.AllowDBNull = false; 44 | } 45 | 46 | dt.Columns.Add( col ); 47 | } 48 | 49 | dt.PrimaryKey = new DataColumn[] { dt.Columns[0] }; 50 | 51 | return dt; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /AsyncDataAdapter.msbuild: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Any CPU 7 | v4.5 8 | $(Name)-$(BuildNumber) 9 | 10 | $(TmpDir)\$(TargetDirectoryBase) 11 | $(TmpDir)\$(TargetDirectoryBase)-src 12 | $(TmpDir)\Doc 13 | 14 | 15 | $(TmpDir)\nuget 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /AsyncDataAdapter.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29728.190 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsyncDataAdapter", "AsyncDataAdapter\AsyncDataAdapter.csproj", "{0384745D-0E1D-4F87-A24E-2CF5762FAAB1}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{366B2040-653C-4536-86F6-7A8B8765D6AF}" 9 | ProjectSection(SolutionItems) = preProject 10 | .editorconfig = .editorconfig 11 | .gitignore = .gitignore 12 | appveyor.yml = appveyor.yml 13 | AsyncDataAdapter.msbuild = AsyncDataAdapter.msbuild 14 | DbSetup.sql = DbSetup.sql 15 | LICENSE = LICENSE 16 | Properties.xml = Properties.xml 17 | README.md = README.md 18 | EndProjectSection 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsyncDataAdapter.Tests", "AsyncDataAdapter.Tests\AsyncDataAdapter.Tests.csproj", "{F53AB0CC-DFBC-4D41-A0E6-B670FDABF67A}" 21 | EndProject 22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsyncDataAdapter.Microsoft.Data.SqlClient", "AsyncDataAdapter.Microsoft.Data.SqlClient\AsyncDataAdapter.Microsoft.Data.SqlClient.csproj", "{C3734C27-227B-4CB1-9D5C-5930885A37DE}" 23 | EndProject 24 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsyncDataAdapter.System.Data.SqlClient", "AsyncDataAdapter.System.Data.SqlClient\AsyncDataAdapter.System.Data.SqlClient.csproj", "{FD614642-CD23-4B1A-B5FC-3F5E28EB29E9}" 25 | EndProject 26 | Global 27 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 28 | Debug|Any CPU = Debug|Any CPU 29 | Release|Any CPU = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 32 | {0384745D-0E1D-4F87-A24E-2CF5762FAAB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {0384745D-0E1D-4F87-A24E-2CF5762FAAB1}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {0384745D-0E1D-4F87-A24E-2CF5762FAAB1}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {0384745D-0E1D-4F87-A24E-2CF5762FAAB1}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {F53AB0CC-DFBC-4D41-A0E6-B670FDABF67A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {F53AB0CC-DFBC-4D41-A0E6-B670FDABF67A}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {F53AB0CC-DFBC-4D41-A0E6-B670FDABF67A}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {F53AB0CC-DFBC-4D41-A0E6-B670FDABF67A}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {C3734C27-227B-4CB1-9D5C-5930885A37DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {C3734C27-227B-4CB1-9D5C-5930885A37DE}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {C3734C27-227B-4CB1-9D5C-5930885A37DE}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {C3734C27-227B-4CB1-9D5C-5930885A37DE}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {FD614642-CD23-4B1A-B5FC-3F5E28EB29E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {FD614642-CD23-4B1A-B5FC-3F5E28EB29E9}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {FD614642-CD23-4B1A-B5FC-3F5E28EB29E9}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {FD614642-CD23-4B1A-B5FC-3F5E28EB29E9}.Release|Any CPU.Build.0 = Release|Any CPU 48 | EndGlobalSection 49 | GlobalSection(SolutionProperties) = preSolution 50 | HideSolutionNode = FALSE 51 | EndGlobalSection 52 | GlobalSection(ExtensibilityGlobals) = postSolution 53 | SolutionGuid = {ABB37006-C016-409A-88C0-B3E06924773D} 54 | EndGlobalSection 55 | EndGlobal 56 | -------------------------------------------------------------------------------- /AsyncDataAdapter/AsyncDataAdapter.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | true 6 | true 7 | StrongName.snk.pfx 8 | true 9 | true 10 | 1591 11 | 12 | 13 | true 14 | snupkg 15 | 16 | 17 | Jehoel.AsyncDataAdapter 18 | https://github.com/Jehoel/AsyncDataAdapter/ 19 | MIT 20 | https://github.com/Jehoel/AsyncDataAdapter/ 21 | Microsoft Corporation, Vladimir Kloz <vladimir.kloz@gmail.com>; Jeremy Kruer; Dai Rees; 22 | 5.0.0 23 | Vladimir Kloz; Jeremy Kruer; Dai Rees; 24 | 25 | Jehoel.AsyncDataAdapter builds on Vladimir Kloz' original AsyncDataAdapter package, with support for .NET Standard 2.0, and numerous other improvements. The original implementation is based on Microsoft's MIT-licensed implementation of DataReader, DbDataReader, and SqlDataReader. 26 | DataAdapter DbDataAdapter SqlDataAdapter AsyncDataAdapter AdaDataAdapter AsyncSqlDataAdapter SqlAsyncDataAdapter FillAsync FillSchemaAsync UpdateAsync 27 | Historical releases: 28 | 1.0 - 1.0.25.1 - See https://www.nuget.org/packages/AsyncDataAdapter/ and https://github.com/voloda/AsyncDataAdapter 29 | 2.0 - <3.0 -See https://github.com/jkruer01/AsyncDataAdapter 30 | 3.0 -https://github.com/Jehoel/AsyncDataAdapter/ 31 | 32 | 3.0 - Completed async support: uses `ConfigureAwait(false)` internally, ensuring no synchronous operations are invoked. Code clean-up. Renaming types with an `Ada` prefix to avoid conflicts with original System.Data types. Project and output NuGet package now targets .NET Standard 2.0 and Microsoft.Data.SqlClient instead of .NET Framework. 33 | 4.0.0 - Major reworking: the main async DbDataAdapter (ProxyDbDataAdapter) now derives from DbDataAdapter and fully support both synchronous and asynchronous Fill, FillAsync, FillSchema, FillSchemaAsync, Update, and UpdateSync with overloads for all async equivalents - and implements support for different ADO.NET providers in their own projects. Phew! 34 | 5.0.0 - Removing root package dependency on Microsoft.Data.SqlCLient which was meant to have been removed in 4.0.0. 35 | 36 | 37 | 38 | 39 | 40 | bin\Release 41 | 42 | 43 | 44 | bin\Debug 45 | 46 | 47 | 48 | 49 | 50 | True 51 | True 52 | ReflectedMethods.tt 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | TextTemplatingFileGenerator 63 | ReflectedMethods.cs 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | True 74 | True 75 | ReflectedMethods.tt 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /AsyncDataAdapter/AsyncDataAdapter.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daiplusplus/AsyncDataAdapter/401af0585801986456d48fd60aa2e00aeda20e98/AsyncDataAdapter/AsyncDataAdapter.snk -------------------------------------------------------------------------------- /AsyncDataAdapter/Data/Common/AdaDbSchemaTable.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | using System.Data.Common; 3 | 4 | namespace AsyncDataAdapter.Internal 5 | { 6 | public sealed class AdaDbSchemaTable 7 | { 8 | private enum ColumnEnum 9 | { 10 | ColumnName, 11 | ColumnOrdinal, 12 | ColumnSize, 13 | BaseServerName, 14 | BaseCatalogName, 15 | BaseColumnName, 16 | BaseSchemaName, 17 | BaseTableName, 18 | IsAutoIncrement, 19 | IsUnique, 20 | IsKey, 21 | IsRowVersion, 22 | DataType, 23 | ProviderSpecificDataType, 24 | AllowDBNull, 25 | ProviderType, 26 | IsExpression, 27 | IsHidden, 28 | IsLong, 29 | IsReadOnly, 30 | SchemaMappingUnsortedIndex, 31 | } 32 | 33 | private static readonly string[] DBCOLUMN_NAME = new string[] { 34 | SchemaTableColumn .ColumnName, 35 | SchemaTableColumn .ColumnOrdinal, 36 | SchemaTableColumn .ColumnSize, 37 | SchemaTableOptionalColumn.BaseServerName, 38 | SchemaTableOptionalColumn.BaseCatalogName, 39 | SchemaTableColumn .BaseColumnName, 40 | SchemaTableColumn .BaseSchemaName, 41 | SchemaTableColumn .BaseTableName, 42 | SchemaTableOptionalColumn.IsAutoIncrement, 43 | SchemaTableColumn .IsUnique, 44 | SchemaTableColumn .IsKey, 45 | SchemaTableOptionalColumn.IsRowVersion, 46 | SchemaTableColumn .DataType, 47 | SchemaTableOptionalColumn.ProviderSpecificDataType, 48 | SchemaTableColumn .AllowDBNull, 49 | SchemaTableColumn .ProviderType, 50 | SchemaTableColumn .IsExpression, 51 | SchemaTableOptionalColumn.IsHidden, 52 | SchemaTableColumn .IsLong, 53 | SchemaTableOptionalColumn.IsReadOnly, 54 | AdaDbSchemaRow .SchemaMappingUnsortedIndex, 55 | }; 56 | 57 | #pragma warning disable IDE0052 // Remove unread private members 58 | private readonly DataTable dataTable; 59 | #pragma warning restore 60 | private readonly DataColumnCollection columns; 61 | private readonly DataColumn[] columnCache = new DataColumn[DBCOLUMN_NAME.Length]; 62 | private readonly bool returnProviderSpecificTypes; 63 | 64 | internal AdaDbSchemaTable(DataTable dataTable, bool returnProviderSpecificTypes) 65 | { 66 | this.dataTable = dataTable; 67 | this.columns = dataTable.Columns; 68 | this.returnProviderSpecificTypes = returnProviderSpecificTypes; 69 | } 70 | 71 | internal DataColumn ColumnName { get { return this.CachedDataColumn(ColumnEnum.ColumnName); } } 72 | internal DataColumn Size { get { return this.CachedDataColumn(ColumnEnum.ColumnSize); } } 73 | internal DataColumn BaseServerName { get { return this.CachedDataColumn(ColumnEnum.BaseServerName); } } 74 | internal DataColumn BaseColumnName { get { return this.CachedDataColumn(ColumnEnum.BaseColumnName); } } 75 | internal DataColumn BaseTableName { get { return this.CachedDataColumn(ColumnEnum.BaseTableName); } } 76 | internal DataColumn BaseCatalogName { get { return this.CachedDataColumn(ColumnEnum.BaseCatalogName); } } 77 | internal DataColumn BaseSchemaName { get { return this.CachedDataColumn(ColumnEnum.BaseSchemaName); } } 78 | internal DataColumn IsAutoIncrement { get { return this.CachedDataColumn(ColumnEnum.IsAutoIncrement); } } 79 | internal DataColumn IsUnique { get { return this.CachedDataColumn(ColumnEnum.IsUnique); } } 80 | internal DataColumn IsKey { get { return this.CachedDataColumn(ColumnEnum.IsKey); } } 81 | internal DataColumn IsRowVersion { get { return this.CachedDataColumn(ColumnEnum.IsRowVersion); } } 82 | internal DataColumn AllowDBNull { get { return this.CachedDataColumn(ColumnEnum.AllowDBNull); } } 83 | internal DataColumn IsExpression { get { return this.CachedDataColumn(ColumnEnum.IsExpression); } } 84 | internal DataColumn IsHidden { get { return this.CachedDataColumn(ColumnEnum.IsHidden); } } 85 | internal DataColumn IsLong { get { return this.CachedDataColumn(ColumnEnum.IsLong); } } 86 | internal DataColumn IsReadOnly { get { return this.CachedDataColumn(ColumnEnum.IsReadOnly); } } 87 | internal DataColumn UnsortedIndex { get { return this.CachedDataColumn(ColumnEnum.SchemaMappingUnsortedIndex); } } 88 | 89 | internal DataColumn DataType 90 | { 91 | get 92 | { 93 | if (this.returnProviderSpecificTypes) 94 | { 95 | return this.CachedDataColumn(ColumnEnum.ProviderSpecificDataType, ColumnEnum.DataType); 96 | } 97 | return this.CachedDataColumn(ColumnEnum.DataType); 98 | } 99 | } 100 | 101 | private DataColumn CachedDataColumn(ColumnEnum column) 102 | { 103 | return this.CachedDataColumn(column, column); 104 | } 105 | 106 | private DataColumn CachedDataColumn(ColumnEnum column, ColumnEnum column2) 107 | { 108 | DataColumn dataColumn = this.columnCache[(int)column]; 109 | if (null == dataColumn) 110 | { 111 | int index = this.columns.IndexOf(DBCOLUMN_NAME[(int)column]); 112 | if ((-1 == index) && (column != column2)) 113 | { 114 | index = this.columns.IndexOf(DBCOLUMN_NAME[(int)column2]); 115 | } 116 | if (-1 != index) 117 | { 118 | dataColumn = this.columns[index]; 119 | this.columnCache[(int)column] = dataColumn; 120 | } 121 | } 122 | return dataColumn; 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /AsyncDataAdapter/Data/Common/BatchCommandInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Data.Common; 6 | using System.Diagnostics; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | using AsyncDataAdapter.Internal; 11 | 12 | namespace AsyncDataAdapter.Internal 13 | { 14 | public struct BatchCommandInfo 15 | { 16 | public int CommandIdentifier; // whatever AddToBatch returns, so we can reference the command later in GetBatchedParameter 17 | public int ParameterCount; // number of parameters on the command, so we know how many to loop over when processing output parameters 18 | public DataRow Row; // the row that the command is intended to update 19 | public StatementType StatementType; // the statement type of the command, needed for accept changes 20 | public UpdateRowSource UpdatedRowSource; // the UpdatedRowSource value from the command, to know whether we need to look for output parameters or not 21 | public int? RecordsAffected; 22 | public Exception Errors; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /AsyncDataAdapter/Data/Common/DataTables.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Data; 6 | using System.Text; 7 | 8 | namespace AsyncDataAdapter.Internal 9 | { 10 | /// Discriminated union (tagged union) type over , , and an array of . 11 | public struct DataTables : IReadOnlyCollection 12 | { 13 | private enum DataTablesType 14 | { 15 | Uninitialized, 16 | DataSet, 17 | DataTable, 18 | DataTables 19 | } 20 | 21 | public static implicit operator DataTables( DataSet dataSet ) 22 | { 23 | return new DataTables( dataSet ); 24 | } 25 | 26 | public static implicit operator DataTables( DataTable dataTable ) 27 | { 28 | return new DataTables( dataTable ); 29 | } 30 | 31 | public static implicit operator DataTables( DataTable[] dataTables ) 32 | { 33 | return new DataTables( dataTables ); 34 | } 35 | 36 | private readonly DataTablesType type; 37 | private readonly DataSet dataSet; 38 | private readonly DataTable dataTable; 39 | private readonly DataTable[] dataTables; 40 | 41 | public DataTables( DataSet dataSet ) 42 | { 43 | this.type = DataTablesType.DataSet; 44 | this.dataSet = dataSet ?? throw new ArgumentNullException(nameof(dataSet)); 45 | this.dataTable = null; 46 | this.dataTables = null; 47 | } 48 | 49 | public DataTables( DataTable dataTable ) 50 | { 51 | this.type = DataTablesType.DataTable; 52 | this.dataSet = null; 53 | this.dataTable = dataTable ?? throw new ArgumentNullException(nameof(dataTable)); 54 | this.dataTables = null; 55 | } 56 | 57 | public DataTables( DataTable[] dataTables ) 58 | { 59 | this.type = DataTablesType.DataTables; 60 | this.dataSet = null; 61 | this.dataTable = null; 62 | this.dataTables = dataTables ?? throw new ArgumentNullException(nameof(dataTables)); 63 | } 64 | 65 | public IEnumerable AsEnumerable() 66 | { 67 | switch (this.type) 68 | { 69 | case DataTablesType.DataSet: 70 | return this.dataSet.Tables.Cast(); 71 | 72 | case DataTablesType.DataTable: 73 | return new DataTable[] { this.dataTable }; 74 | 75 | case DataTablesType.DataTables: 76 | return this.dataTables; 77 | 78 | case DataTablesType.Uninitialized: 79 | default: 80 | throw new InvalidOperationException( "This " + nameof(DataTables) + " tagged union is not initialized correctly." ); 81 | } 82 | } 83 | 84 | #region IReadOnlyCollection 85 | 86 | public Int32 Count 87 | { 88 | get 89 | { 90 | switch (this.type) 91 | { 92 | case DataTablesType.DataSet: 93 | return this.dataSet.Tables.Count; 94 | 95 | case DataTablesType.DataTable: 96 | return 1; 97 | 98 | case DataTablesType.DataTables: 99 | return this.dataTables.Length; 100 | 101 | case DataTablesType.Uninitialized: 102 | default: 103 | throw new InvalidOperationException( "This " + nameof(DataTables) + " tagged union is not initialized correctly." ); 104 | } 105 | } 106 | } 107 | 108 | public IEnumerator GetEnumerator() 109 | { 110 | return this.AsEnumerable().GetEnumerator(); 111 | } 112 | 113 | IEnumerator IEnumerable.GetEnumerator() 114 | { 115 | return this.AsEnumerable().GetEnumerator(); 116 | } 117 | 118 | #endregion 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /AsyncDataAdapter/Data/Common/Interfaces/IAdaSchemaMappingAdapter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Data.Common; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace AsyncDataAdapter.Internal 8 | { 9 | public interface IAdaSchemaMappingAdapter 10 | { 11 | LoadOption FillLoadOption { get; } 12 | 13 | Boolean AcceptChangesDuringFill { get; } 14 | 15 | MissingMappingAction MissingMappingAction { get; } 16 | MissingSchemaAction MissingSchemaAction { get; } 17 | 18 | DataTableMappingCollection TableMappings { get; } 19 | 20 | DataTableMapping GetTableMappingBySchemaAction( string sourceTableName, string dataSetTableName, MissingMappingAction mappingAction ); 21 | 22 | int IndexOfDataSetTable(string dataSetTable); 23 | 24 | Task FillFromReaderAsync( DataSet dataset, DataTable datatable, string srcTable, AdaDataReaderContainer dataReader, int startRecord, int maxRecords, DataColumn parentChapterColumn, object parentChapterValue, CancellationToken cancellationToken ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /AsyncDataAdapter/Data/Common/Interfaces/IAsyncDataAdapter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace AsyncDataAdapter 7 | { 8 | /// Extends with support for async methods for read-only data adapter operations. 9 | public interface IAsyncDataAdapter : IDataAdapter 10 | { 11 | Task FillAsync( DataSet dataSet, CancellationToken cancellationToken = default ); 12 | 13 | Task FillSchemaAsync( DataSet dataSet, SchemaType schemaType, CancellationToken cancellationToken = default ); 14 | } 15 | 16 | /// Extends with support for async methods for read-only data adapter operations. 17 | public interface IAsyncDbDataAdapter : IAsyncDataAdapter, IDbDataAdapter 18 | { 19 | } 20 | 21 | /// Extends with support for . 22 | public interface IUpdatingAsyncDataAdapter : IAsyncDataAdapter 23 | { 24 | Task UpdateAsync( DataSet dataSet, CancellationToken cancellationToken = default ); 25 | } 26 | 27 | /// Extends with support for . 28 | public interface IUpdatingAsyncDbDataAdapter : IUpdatingAsyncDataAdapter, IDbDataAdapter 29 | { 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /AsyncDataAdapter/Data/Common/Interfaces/IBatchingAdapter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Data.Common; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace AsyncDataAdapter 10 | { 11 | public interface IBatchingAdapter 12 | { 13 | MissingMappingAction UpdateMappingAction { get; } 14 | MissingSchemaAction UpdateSchemaAction { get; } 15 | 16 | int UpdateBatchSize { get; } 17 | 18 | /// Called to add a single command to the batch of commands that need to be executed as a batch, when batch updates are requested. It must return an identifier that can be used to identify the command to GetBatchedParameter later. 19 | int AddToBatch( DbCommand command ); 20 | 21 | /// Called when batch updates are requested to clear out the contents of the batch, whether or not it's been executed. 22 | void ClearBatch(); 23 | 24 | /// Called to execute the batched update command, returns the number of rows affected, just as ExecuteNonQuery would. 25 | Task ExecuteBatchAsync( CancellationToken cancellationToken ); 26 | 27 | /// Called when batch updates are requested to cleanup after a batch update has been completed. 28 | void TerminateBatching(); 29 | 30 | /// Called to retrieve a parameter from a specific bached command, the first argument is the value that was returned by AddToBatch when it was called for the command. 31 | IDataParameter GetBatchedParameter(int commandIdentifier, int parameterIndex); 32 | 33 | bool GetBatchedRecordsAffected(int commandIdentifier, out int recordsAffected, out Exception error); 34 | 35 | /// Called when batch updates are requested to prepare for processing of a batch of commands. 36 | void InitializeBatching(); 37 | } 38 | 39 | public interface IUpdatedRowOptions 40 | { 41 | Boolean AcceptChangesDuringUpdate { get; } 42 | Boolean ContinueUpdateOnError { get; } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /AsyncDataAdapter/Data/Core/BatchingAdapter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Collections.Generic; 4 | using System.Data; 5 | using System.Data.Common; 6 | using System.Diagnostics; 7 | using System.Globalization; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | 11 | namespace AsyncDataAdapter.Internal 12 | { 13 | // TODO: Many of these methods aren't async and so could use reflection instead of being reimplemented. 14 | 15 | public static class BatchingAdapterMethods 16 | { 17 | /// This is the same logic as SqlDataAdapter. 18 | /// Get this value from . 19 | public static MissingMappingAction UpdateMappingAction( MissingMappingAction missingMappingAction ) 20 | { 21 | if( MissingMappingAction.Passthrough == missingMappingAction ) 22 | { 23 | return MissingMappingAction.Passthrough; 24 | } 25 | 26 | return MissingMappingAction.Error; 27 | } 28 | 29 | /// This is the same logic as SqlDataAdapter. 30 | /// Get this value from . 31 | public static MissingSchemaAction UpdateSchemaAction( MissingSchemaAction missingSchemaAction ) 32 | { 33 | if (MissingSchemaAction.Add == missingSchemaAction || MissingSchemaAction.AddWithKey == missingSchemaAction) 34 | { 35 | return MissingSchemaAction.Ignore; 36 | } 37 | 38 | return MissingSchemaAction.Error; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /AsyncDataAdapter/Data/Core/Connections.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Collections.Generic; 4 | using System.Data; 5 | using System.Data.Common; 6 | using System.Diagnostics; 7 | using System.Globalization; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | 11 | namespace AsyncDataAdapter.Internal 12 | { 13 | public static class AsyncDataReaderConnectionMethods 14 | { 15 | /// 16 | /// needs to appear in the try {} finally { QuietClose } block otherwise a possibility exists that an exception may be thrown, i.e. where we would Open the connection and not close it 17 | /// 18 | public static async Task QuietOpenAsync( DbConnection connection, CancellationToken cancellationToken ) 19 | { 20 | Debug.Assert(null != connection, "QuietOpen: null connection"); 21 | var originalState = connection.State; 22 | if (ConnectionState.Closed == originalState) 23 | { 24 | await connection.OpenAsync( cancellationToken ).ConfigureAwait(false); 25 | } 26 | 27 | return originalState; 28 | } 29 | 30 | public static void QuietClose( DbConnection connection, ConnectionState originalState ) 31 | { 32 | // close the connection if: 33 | // * it was closed on first use and adapter has opened it, AND 34 | // * provider's implementation did not ask to keep this connection open 35 | if ((null != connection) && (ConnectionState.Closed == originalState)) 36 | { 37 | // we don't have to check the current connection state because 38 | // it is supposed to be safe to call Close multiple times 39 | connection.Close(); 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /AsyncDataAdapter/Data/Core/FillLoadDataRowChunk.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Data; 4 | using System.Data.Common; 5 | using System.Diagnostics; 6 | using System.Globalization; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace AsyncDataAdapter.Internal 11 | { 12 | public static partial class AsyncDataReaderMethods 13 | { 14 | public static async Task FillLoadDataRowChunkAsync( Action onFillError, AdaSchemaMapping mapping, int startRecord, int maxRecords, CancellationToken cancellationToken ) 15 | { 16 | AdaDataReaderContainer dataReader = mapping.DataReader; 17 | 18 | while (0 < startRecord) 19 | { 20 | if (!await dataReader.ReadAsync( cancellationToken ).ConfigureAwait(false)) 21 | { 22 | // there are no more rows on first resultset 23 | return 0; 24 | } 25 | --startRecord; 26 | } 27 | 28 | int rowsAddedToDataSet = 0; 29 | if (0 < maxRecords) 30 | { 31 | while ((rowsAddedToDataSet < maxRecords) && await dataReader.ReadAsync( cancellationToken ).ConfigureAwait(false)) 32 | { 33 | try 34 | { 35 | await mapping.LoadDataRowWithClearAsync( cancellationToken ).ConfigureAwait(false); 36 | rowsAddedToDataSet++; 37 | } 38 | catch (Exception e) when(onFillError != null && ADP.IsCatchableExceptionType(e)) 39 | { 40 | onFillError(e, mapping.DataTable, mapping.DataValues); 41 | } 42 | } 43 | // skip remaining rows of the first resultset 44 | } 45 | else 46 | { 47 | rowsAddedToDataSet = await FillLoadDataRowAsync( onFillError, mapping, cancellationToken ).ConfigureAwait(false); 48 | } 49 | return rowsAddedToDataSet; 50 | } 51 | 52 | public static async Task FillLoadDataRowAsync( Action onFillError, AdaSchemaMapping mapping, CancellationToken cancellationToken ) 53 | { 54 | int rowsAddedToDataSet = 0; 55 | AdaDataReaderContainer dataReader = mapping.DataReader; 56 | while (await dataReader.ReadAsync( cancellationToken ).ConfigureAwait(false)) 57 | { // read remaining rows of first and subsequent resultsets 58 | try 59 | { 60 | // only try-catch if a FillErrorEventHandler is registered so that 61 | // in the default case we get the full callstack from users 62 | await mapping.LoadDataRowWithClearAsync( cancellationToken ).ConfigureAwait(false); 63 | rowsAddedToDataSet++; 64 | } 65 | catch (Exception e) when (onFillError != null && ADP.IsCatchableExceptionType(e)) 66 | { 67 | onFillError(e, mapping.DataTable, mapping.DataValues); 68 | } 69 | } 70 | 71 | return rowsAddedToDataSet; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /AsyncDataAdapter/Data/Core/FillMapping.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Data; 4 | using System.Data.Common; 5 | using System.Diagnostics; 6 | using System.Globalization; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace AsyncDataAdapter.Internal 11 | { 12 | public static partial class AsyncDataReaderMethods 13 | { 14 | public static AdaSchemaMapping FillMapping( Action onFillError, IAdaSchemaMappingAdapter adapter, DataSet dataset, DataTable datatable, string srcTable, AdaDataReaderContainer dataReader, int schemaCount, DataColumn parentChapterColumn, object parentChapterValue ) 15 | { 16 | try 17 | { 18 | // only catch if a FillErrorEventHandler is registered so that in the default case we get the full callstack from users 19 | AdaSchemaMapping mapping = FillMappingInternal( adapter, dataset, datatable, srcTable, dataReader, schemaCount, parentChapterColumn, parentChapterValue ); 20 | return mapping; 21 | } 22 | catch (Exception ex) when(onFillError != null && ADP.IsCatchableExceptionType(ex)) 23 | { 24 | onFillError( ex, null, null ); 25 | return null; 26 | } 27 | } 28 | 29 | public static AdaSchemaMapping FillMappingInternal( IAdaSchemaMappingAdapter adapter, DataSet dataset, DataTable datatable, string srcTable, AdaDataReaderContainer dataReader, int schemaCount, DataColumn parentChapterColumn, object parentChapterValue ) 30 | { 31 | bool withKeyInfo = (MissingSchemaAction.AddWithKey == adapter.MissingSchemaAction); 32 | 33 | string sourceTableName = null; 34 | if (dataset != null) 35 | { 36 | sourceTableName = GetSourceTableName( srcTable, schemaCount ); 37 | } 38 | 39 | return new AdaSchemaMapping( adapter, dataset, datatable, dataReader, withKeyInfo, SchemaType.Mapped, sourceTableName, true, parentChapterColumn, parentChapterValue ); 40 | } 41 | 42 | private static string GetSourceTableName(string srcTable, int index) 43 | { 44 | //if ((null != srcTable) && (0 <= index) && (index < srcTable.Length)) { 45 | if (0 == index) 46 | { 47 | return srcTable; //[index]; 48 | } 49 | 50 | return srcTable + index.ToString(CultureInfo.InvariantCulture); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /AsyncDataAdapter/Data/Core/FillSchemaAsync.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Data; 4 | using System.Data.Common; 5 | using System.Diagnostics; 6 | using System.Globalization; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace AsyncDataAdapter.Internal 11 | { 12 | public static partial class AsyncDataReaderMethods 13 | { 14 | public static async Task FillSchemaAsync( IAdaSchemaMappingAdapter adapter, Boolean returnProviderSpecificTypes, DataSet dataSet, SchemaType schemaType, string srcTable, DbDataReader dataReader, CancellationToken cancellationToken ) 15 | { 16 | if (null == dataSet) throw new ArgumentNullException(nameof(dataSet)); 17 | if ((SchemaType.Source != schemaType) && (SchemaType.Mapped != schemaType)) throw ADP.InvalidSchemaType(schemaType); 18 | if (string.IsNullOrEmpty(srcTable)) throw ADP.FillSchemaRequiresSourceTableName("srcTable"); 19 | if ((null == dataReader) || dataReader.IsClosed) throw ADP.FillRequires("dataReader"); 20 | 21 | // user must Close/Dispose of the dataReader 22 | return await FillSchemaFromReaderAsync( adapter, returnProviderSpecificTypes, dataSet, singleDataTable: null, schemaType, srcTable, dataReader, cancellationToken ).ConfigureAwait(false); 23 | } 24 | 25 | public static async Task FillSchemaAsync( IAdaSchemaMappingAdapter adapter, Boolean returnProviderSpecificTypes, DataTable dataTable, SchemaType schemaType, DbDataReader dataReader, CancellationToken cancellationToken ) 26 | { 27 | if (null == dataTable) throw new ArgumentNullException(nameof(dataTable)); 28 | if ((SchemaType.Source != schemaType) && (SchemaType.Mapped != schemaType)) throw ADP.InvalidSchemaType(schemaType); 29 | if ((null == dataReader) || dataReader.IsClosed)throw ADP.FillRequires("dataReader"); 30 | 31 | // user must Close/Dispose of the dataReader 32 | // user will have to call NextResult to access remaining results 33 | DataTable[] singleTable = await FillSchemaFromReaderAsync( adapter, returnProviderSpecificTypes, dataset: null, singleDataTable: dataTable, schemaType, srcTable: null, dataReader, cancellationToken ).ConfigureAwait(false); 34 | if( singleTable is DataTable[] arr && arr.Length == 1 ) return singleTable[0]; 35 | return null; 36 | } 37 | 38 | public static async Task FillSchemaFromReaderAsync( IAdaSchemaMappingAdapter adapter, Boolean returnProviderSpecificTypes, DataSet dataset, DataTable singleDataTable, SchemaType schemaType, string srcTable, DbDataReader dataReader, CancellationToken cancellationToken ) 39 | { 40 | DataTable[] dataTables = null; 41 | int schemaCount = 0; 42 | do 43 | { 44 | AdaDataReaderContainer readerHandler = AdaDataReaderContainer.Create( dataReader, useProviderSpecificDataReader: returnProviderSpecificTypes ); 45 | if (0 >= readerHandler.FieldCount) 46 | { 47 | continue; 48 | } 49 | 50 | string sourceTableName = null; 51 | if (null != dataset) 52 | { 53 | sourceTableName = GetSourceTableName(srcTable, schemaCount); 54 | schemaCount++; // don't increment if no SchemaTable ( a non-row returning result ) 55 | } 56 | 57 | AdaSchemaMapping mapping = new AdaSchemaMapping( adapter: adapter, dataset, singleDataTable, dataReader: readerHandler, keyInfo: true, schemaType, sourceTableName, gettingData: false, parentChapterColumn: null, parentChapterValue: null ); 58 | 59 | if (singleDataTable != null) 60 | { 61 | // do not read remaining results in single DataTable case 62 | return new DataTable[] { mapping.DataTable }; 63 | } 64 | else if (null != mapping.DataTable) 65 | { 66 | if (null == dataTables) 67 | { 68 | dataTables = new DataTable[1] { mapping.DataTable }; 69 | } 70 | else 71 | { 72 | dataTables = AddDataTableToArray( dataTables, mapping.DataTable ); 73 | } 74 | } 75 | } 76 | while ( await dataReader.NextResultAsync( cancellationToken ).ConfigureAwait(false) ); // FillSchema does not capture errors for FillError event 77 | 78 | if( dataTables is null && singleDataTable is null ) 79 | { 80 | return Array.Empty(); 81 | } 82 | else 83 | { 84 | return dataTables; 85 | } 86 | } 87 | 88 | // used by FillSchema which returns an array of datatables added to the dataset 89 | public static DataTable[] AddDataTableToArray(DataTable[] tables, DataTable newTable) 90 | { 91 | for (int i = 0; i < tables.Length; ++i) 92 | { 93 | // search for duplicates: 94 | if (Object.ReferenceEquals( tables[i], newTable )) 95 | { 96 | return tables; // duplicate found 97 | } 98 | } 99 | DataTable[] newTables = new DataTable[tables.Length + 1]; // add unique data table 100 | for (int i = 0; i < tables.Length; ++i) 101 | { 102 | newTables[i] = tables[i]; 103 | } 104 | newTables[tables.Length] = newTable; 105 | return newTables; 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /AsyncDataAdapter/Data/Core/ICanUpdateAsync.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Data.Common; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace AsyncDataAdapter.Internal 8 | { 9 | public interface ICanUpdateAsync : IBatchingAdapter 10 | { 11 | DbCommand SelectCommand { get; } 12 | DbCommand InsertCommand { get; } 13 | DbCommand DeleteCommand { get; } 14 | DbCommand UpdateCommand { get; } 15 | 16 | DbConnection GetConnection(); 17 | 18 | RowUpdatingEventArgs CreateRowUpdatingEvent( DataRow dataRow, DbCommand command, StatementType statementType, DataTableMapping tableMapping ); 19 | RowUpdatedEventArgs CreateRowUpdatedEvent ( DataRow dataRow, DbCommand command, StatementType statementType, DataTableMapping tableMapping ); 20 | 21 | void OnRowUpdating( RowUpdatingEventArgs e ); 22 | void OnRowUpdated ( RowUpdatedEventArgs e ); 23 | 24 | void UpdatingRowStatusErrors( RowUpdatingEventArgs e, DataRow row ); 25 | 26 | Int32 UpdatedRowStatus( RowUpdatedEventArgs e, BatchCommandInfo[] batchCommands, Int32 commandCount ); 27 | 28 | Task UpdateRowExecuteAsync( RowUpdatedEventArgs rowUpdatedEvent, DbCommand dataCommand, StatementType cmdIndex, CancellationToken cancellationToken ); 29 | 30 | Task UpdateConnectionOpenAsync( DbConnection connection, StatementType statementType, DbConnection[] connections, ConnectionState[] connectionStates, bool useSelectConnectionState, CancellationToken cancellationToken ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /AsyncDataAdapter/Data/Core/ParameterInputOutput.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Data.Common; 4 | using System.Diagnostics; 5 | 6 | namespace AsyncDataAdapter.Internal 7 | { 8 | public static class ParameterMethods 9 | { 10 | public static void ParameterInput( MissingMappingAction missingMapping, MissingSchemaAction missingSchema, IDataParameterCollection parameters, StatementType typeIndex, DataRow row, DataTableMapping mappings) 11 | { 12 | foreach (IDataParameter parameter in parameters) 13 | { 14 | if ((null != parameter) && (0 != (ParameterDirection.Input & parameter.Direction))) 15 | { 16 | string columnName = parameter.SourceColumn; 17 | if (!string.IsNullOrEmpty(columnName)) 18 | { 19 | DataColumn dataColumn = mappings.GetDataColumn( sourceColumn: columnName, dataType: null, dataTable: row.Table, missingMapping, missingSchema ); 20 | if (null != dataColumn) 21 | { 22 | DataRowVersion version = GetParameterSourceVersion(typeIndex, parameter); 23 | parameter.Value = row[dataColumn, version]; 24 | } 25 | else 26 | { 27 | parameter.Value = null; 28 | } 29 | 30 | if( parameter is DbParameter p2 && p2.SourceColumnNullMapping ) 31 | { 32 | Debug.Assert(DbType.Int32 == parameter.DbType, "unexpected DbType"); 33 | parameter.Value = Utility.IsNull( parameter.Value ) ? ParameterValueNullValue : ParameterValueNonNullValue; 34 | } 35 | } 36 | } 37 | } 38 | } 39 | 40 | internal static readonly object ParameterValueNonNullValue = 0; 41 | internal static readonly object ParameterValueNullValue = 1; 42 | 43 | private static DataRowVersion GetParameterSourceVersion(StatementType statementType, IDataParameter parameter) 44 | { 45 | switch (statementType) 46 | { 47 | case StatementType.Insert: return DataRowVersion.Current; // ignores parameter.SourceVersion 48 | case StatementType.Update: return parameter.SourceVersion; 49 | case StatementType.Delete: return DataRowVersion.Original; // ignores parameter.SourceVersion 50 | case StatementType.Select: 51 | case StatementType.Batch: 52 | throw new ArgumentException(message: string.Format("Unwanted statement type {0}", statementType.ToString()), paramName: nameof(statementType)); 53 | default: 54 | throw ADP.InvalidStatementType(statementType); 55 | } 56 | } 57 | 58 | public static void ParameterOutput( MissingMappingAction missingMapping, MissingSchemaAction missingSchema, IDataParameterCollection parameters, DataRow row, DataTableMapping mappings ) 59 | { 60 | foreach (IDataParameter parameter in parameters) 61 | { 62 | if (null != parameter) 63 | { 64 | ParameterOutput( parameter, row, mappings, missingMapping, missingSchema ); 65 | } 66 | } 67 | } 68 | 69 | public static void ParameterOutput( IDataParameter parameter, DataRow row, DataTableMapping mappings, MissingMappingAction missingMapping, MissingSchemaAction missingSchema ) 70 | { 71 | if (0 != (ParameterDirection.Output & parameter.Direction)) 72 | { 73 | object value = parameter.Value; 74 | if (null != value) 75 | { 76 | // null means default, meaning we leave the current DataRow value alone 77 | string columnName = parameter.SourceColumn; 78 | if (!string.IsNullOrEmpty(columnName)) 79 | { 80 | DataColumn dataColumn = mappings.GetDataColumn( sourceColumn: columnName, dataType: null, dataTable: row.Table, missingMapping, missingSchema ); 81 | if (null != dataColumn) 82 | { 83 | if (dataColumn.ReadOnly) 84 | { 85 | try 86 | { 87 | dataColumn.ReadOnly = false; 88 | row[dataColumn] = value; 89 | } 90 | finally 91 | { 92 | dataColumn.ReadOnly = true; 93 | } 94 | } 95 | else 96 | { 97 | row[dataColumn] = value; 98 | } 99 | } 100 | } 101 | } 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /AsyncDataAdapter/Data/DbCommandBuilder/IAsyncDbCommandBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Data.Common; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace AsyncDataAdapter 8 | { 9 | public interface IDbCommandBuilder 10 | { 11 | CatalogLocation CatalogLocation { get; set; } 12 | string CatalogSeparator { get; set; } 13 | ConflictOption ConflictOption { get; set; } 14 | DbDataAdapter DataAdapter { get; set; } 15 | string QuotePrefix { get; set; } 16 | string QuoteSuffix { get; set; } 17 | string SchemaSeparator { get; set; } 18 | bool SetAllValues { get; set; } 19 | 20 | DbCommand GetDeleteCommand(); 21 | DbCommand GetDeleteCommand(bool useColumnsForParameterNames); 22 | DbCommand GetInsertCommand(); 23 | DbCommand GetInsertCommand(bool useColumnsForParameterNames); 24 | DbCommand GetUpdateCommand(); 25 | DbCommand GetUpdateCommand(bool useColumnsForParameterNames); 26 | 27 | string QuoteIdentifier(string unquotedIdentifier); 28 | void RefreshSchema(); 29 | string UnquoteIdentifier(string quotedIdentifier); 30 | } 31 | 32 | public interface IAsyncDbCommandBuilder : IDbCommandBuilder 33 | { 34 | new IAsyncDbDataAdapter DataAdapter { get; } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /AsyncDataAdapter/Data/Reflection/DataColumnReflection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Data.Common; 4 | using System.Reflection; 5 | 6 | namespace AsyncDataAdapter.Internal 7 | { 8 | public static class DataColumnReflection 9 | { 10 | private static readonly MethodInfo _IsAutoIncrementType_Type = Reflection.GetStaticMethod("IsAutoIncrementType", typeof(Type)); 11 | private static readonly MethodInfo _EnsureAdditionalCapacity_Int32 = Reflection.GetInstanceMethod("EnsureAdditionalCapacity", typeof(int)); 12 | private static readonly MethodInfo _CreateDataColumnBySchemaAction = Reflection.GetStaticMethod("CreateDataColumnBySchemaAction", typeof(string), typeof(string), typeof(DataTable), typeof(Type), typeof(MissingSchemaAction)); 13 | 14 | /// Exposes static void .IsAutoIncrementType(Type dataType). 15 | public static bool IsAutoIncrementType_(Type dataType) 16 | { 17 | if (dataType is null) throw new ArgumentNullException(nameof(dataType)); 18 | 19 | return _IsAutoIncrementType_Type.InvokeDisallowNull(@this: null, dataType); 20 | } 21 | 22 | /// Exposes void .EnsureAdditionalCapacity(int capacity). 23 | public static void EnsureAdditionalCapacity_(this DataColumnCollection c, int capacity) 24 | { 25 | if (c is null) throw new ArgumentNullException(nameof(c)); 26 | 27 | _EnsureAdditionalCapacity_Int32.InvokeVoid(@this: c, capacity); 28 | } 29 | 30 | /// Exposes static .CreateDataColumnBySchemaAction(string sourceColumn, string dataSetColumn, DataTable dataTable, Type dataType, MissingSchemaAction schemaAction). 31 | public static DataColumn CreateDataColumnBySchemaAction_(string sourceColumn, string dataSetColumn, DataTable dataTable, Type dataType, MissingSchemaAction schemaAction) 32 | { 33 | return _CreateDataColumnBySchemaAction.InvokeAllowNull(@this: null, sourceColumn, dataSetColumn, dataTable, dataType, schemaAction); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /AsyncDataAdapter/Data/Reflection/ReflectedMethods.out.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace AsyncDataAdapter.Internal 5 | { 6 | public static class ReflectedFuncO2O3 7 | { 8 | private static readonly MethodInfo _methodInfo = Reflection.RequireInstanceMethodInfo( typeof(TOwner), name: Reflection.GetName(typeof(TName)), returnType: typeof(TReturn), typeof(TArg0), typeof(TArg1), typeof(TArg2) ); 9 | 10 | public static TReturn Invoke( TOwner instance, TArg0 arg0, out TArg1 arg1, out TArg2 arg2 ) 11 | { 12 | Object[] arguments = new Object[] { arg0, null, null }; 13 | Object value = _methodInfo.Invoke( obj: instance, parameters: arguments ); 14 | TReturn returnValue = Reflection.AssertResult( _methodInfo, value ); 15 | arg1 = (TArg1)arguments[1]; 16 | arg2 = (TArg2)arguments[2]; 17 | return returnValue; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /AsyncDataAdapter/Data/Reflection/ReflectedMethods.tt: -------------------------------------------------------------------------------- 1 | <#@ template debug="false" hostspecific="false" language="C#" #> 2 | <#@ assembly name="System.Core" #> 3 | <#@ import namespace="System.Globalization" #> 4 | <#@ import namespace="System.Linq" #> 5 | <#@ import namespace="System.Text" #> 6 | <#@ import namespace="System.Collections.Generic" #> 7 | <#@ output extension=".cs" #> 8 | <# 9 | 10 | #> 11 | using System; 12 | using System.Reflection; 13 | 14 | namespace AsyncDataAdapter.Internal 15 | { 16 | <# for( Int32 i = 0; i < 8; i++ ) { 17 | String typeParams = ( i > 0 ? "," : "" ) + GetTypeParameters( i, "TArg{0:d}" ); 18 | String invokeParams = ( i > 0 ? "," : "" ) + GetTypeParameters( i, " TArg{0:d} arg{0:d}" ); 19 | String invokeArgs = " new Object[] {" + GetTypeParameters( i, " arg{0:d}" ) + " }"; 20 | String reflectArgs = GetTypeParameters( i, " typeof(TArg{0:d})" ); 21 | 22 | if( i == 0 ) 23 | { 24 | invokeArgs = " Array.Empty()"; 25 | reflectArgs = " parameterTypes: Array.Empty()"; 26 | } 27 | #> 28 | public static class ReflectedAction> 29 | where TName : struct 30 | { 31 | private static readonly MethodInfo _methodInfo = Reflection.RequireVoidInstanceMethodInfo( typeof(TOwner), name: Reflection.GetName(typeof(TName)),<#= reflectArgs #> ); 32 | 33 | public static void Invoke( TOwner instance<#= invokeParams #> ) 34 | { 35 | Object[] arguments =<#= invokeArgs #>; 36 | Object value = _methodInfo.Invoke( obj: instance, parameters: arguments ); 37 | Reflection.AssertVoid( _methodInfo, value ); 38 | } 39 | } 40 | 41 | public static class ReflectedFunc,TReturn> 42 | { 43 | private static readonly MethodInfo _methodInfo = Reflection.RequireInstanceMethodInfo( typeof(TOwner), name: Reflection.GetName(typeof(TName)), returnType: typeof(TReturn),<#= reflectArgs #> ); 44 | 45 | public static TReturn Invoke( TOwner instance<#= invokeParams #> ) 46 | { 47 | Object[] arguments =<#= invokeArgs #>; 48 | Object value = _methodInfo.Invoke( obj: instance, parameters: arguments ); 49 | return Reflection.AssertResult( _methodInfo, value ); 50 | } 51 | } 52 | 53 | <# } #> 54 | } // namespaace 55 | <#+ 56 | 57 | static String GetTypeParameters( Int32 count, String format ) 58 | { 59 | IEnumerable elms = Enumerable 60 | .Range( 0, count ) 61 | .Select( n => String.Format( CultureInfo.InvariantCulture, format, n ) ); 62 | // `StringJoin` needs to be an extension method! 63 | 64 | return String.Join( separator: ",", elms ); 65 | } 66 | 67 | #> -------------------------------------------------------------------------------- /AsyncDataAdapter/Data/Reflection/ReflectedProperty.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Reflection; 5 | using System.Text; 6 | 7 | namespace AsyncDataAdapter.Internal 8 | { 9 | public static class ReflectedProperty 10 | where TName : struct 11 | { 12 | private static readonly PropertyInfo _propertyInfo = Reflection.RequireInstancePropertyInfo( typeof(TOwner), name: Reflection.GetName(typeof(TName)), returnType: typeof(TProperty) ); 13 | private static readonly MethodInfo _getter = _propertyInfo.GetGetMethod( nonPublic: true ); 14 | private static readonly MethodInfo _setter = _propertyInfo.GetSetMethod( nonPublic: true ); 15 | 16 | public static TProperty GetValue( TOwner instance ) 17 | { 18 | Object value = _getter.Invoke( obj: instance, parameters: null ); 19 | return Reflection.AssertResult( _propertyInfo, value ); 20 | } 21 | 22 | public static void SetValue( TOwner instance, TProperty value ) 23 | { 24 | if( _setter is null ) throw new InvalidOperationException( "The " + _propertyInfo.Name + " property does not have a setter." ); 25 | 26 | _ = _setter.Invoke( instance, new Object[] { value } ); 27 | } 28 | } 29 | 30 | public struct ReflectedField 31 | where TName : struct 32 | { 33 | private static readonly FieldInfo _fieldInfo = Reflection.RequireInstanceFieldInfo( typeof(TOwner), name: Reflection.GetName(typeof(TName)), fieldType: typeof(TField) ); 34 | 35 | public static TField GetValue( TOwner instance ) 36 | { 37 | Object value = _fieldInfo.GetValue( obj: instance ); 38 | return Reflection.AssertResult( _fieldInfo, value ); 39 | } 40 | 41 | public static void SetValue( TOwner instance, TField value ) 42 | { 43 | _fieldInfo.SetValue( instance, value ); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /AsyncDataAdapter/Data/Reflection/RowUpdatedEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Data.Common; 4 | using System.Reflection; 5 | 6 | namespace AsyncDataAdapter.Internal 7 | { 8 | public static class RowUpdatedEventArgsReflection 9 | { 10 | private struct _AdapterInit { } 11 | 12 | /// Exposes void .AdapterInit( recordsAffected). 13 | public static void AdapterInit_(this RowUpdatedEventArgs e, int recordsAffected) 14 | { 15 | ReflectedAction.Invoke( instance: e, recordsAffected ); 16 | } 17 | 18 | /// Exposes void .AdapterInit([] dataRows). 19 | public static void AdapterInit_(this RowUpdatedEventArgs e, DataRow[] dataRows) 20 | { 21 | ReflectedAction.Invoke( instance: e, dataRows ); 22 | } 23 | 24 | private struct _Rows { } 25 | 26 | /// Exposes [] .Rows instance property. 27 | public static DataRow[] GetRows_(this RowUpdatedEventArgs e) 28 | { 29 | return ReflectedProperty.GetValue( e ); 30 | } 31 | 32 | /// Convenience shorthand method to index-into elements of the private DataRow array field. 33 | public static DataRow GetRow_(this RowUpdatedEventArgs e, int row) 34 | { 35 | DataRow[] rows = GetRows_( e ); 36 | if (rows is null) 37 | { 38 | throw new InvalidOperationException("The " + nameof(RowUpdatedEventArgs) + " instance has not yet been initialized."); 39 | } 40 | 41 | return rows[row]; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /AsyncDataAdapter/Data/_DataAdapter/AsyncDbDataAdapter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Data.Common; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace AsyncDataAdapter 8 | { 9 | public abstract class AsyncDbDataAdapter : DbDataAdapter 10 | where TDbCommand : DbCommand 11 | { 12 | protected AsyncDbDataAdapter( DbDataAdapter adapter ) 13 | : base( adapter ) 14 | { 15 | } 16 | 17 | public new abstract TDbCommand SelectCommand { get; set; } 18 | 19 | protected abstract Task FillAsync(DataSet dataSet, int startRecord, int maxRecords, string srcTable, TDbCommand command, CommandBehavior behavior, CancellationToken cancellationToken ); 20 | 21 | protected abstract Task FillAsync( DataTable[] dataTables, int startRecord, int maxRecords, TDbCommand command, CommandBehavior behavior, CancellationToken cancellationToken ); 22 | 23 | #region Non-virtual entrypoints 24 | 25 | public Task FillAsync(DataSet dataSet, string srcTable, CancellationToken cancellationToken = default) 26 | { 27 | return this.FillAsync( dataSet: dataSet, startRecord: 0, maxRecords: 0, srcTable: srcTable, command: this.SelectCommand, behavior: this.FillCommandBehavior, cancellationToken: cancellationToken ); 28 | } 29 | 30 | public Task FillAsync(DataSet dataSet, int startRecord, int maxRecords, string srcTable, CancellationToken cancellationToken = default) 31 | { 32 | return this.FillAsync( dataSet: dataSet, startRecord: startRecord, maxRecords: maxRecords, srcTable: srcTable, command: this.SelectCommand, behavior: this.FillCommandBehavior, cancellationToken: cancellationToken ); 33 | } 34 | 35 | public Task FillAsync(DataTable dataTable, CancellationToken cancellationToken = default) 36 | { 37 | DataTable[] dataTables = new DataTable[1] { dataTable }; 38 | 39 | return this.FillAsync( dataTables, startRecord: 0, maxRecords: 0, command: this.SelectCommand, behavior: this.FillCommandBehavior, cancellationToken: cancellationToken ); 40 | } 41 | 42 | public Task FillAsync(int startRecord, int maxRecords, DataTable[] dataTables, CancellationToken cancellationToken = default) 43 | { 44 | return this.FillAsync( dataTables, startRecord: startRecord, maxRecords: maxRecords, command: this.SelectCommand, behavior: this.FillCommandBehavior, cancellationToken: cancellationToken ); 45 | } 46 | 47 | #endregion 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /AsyncDataAdapter/Data/_DataAdapter/ProxyDataAdapter.FillError.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Data; 4 | using System.Data.Common; 5 | using System.Reflection; 6 | 7 | using AsyncDataAdapter.Internal; 8 | 9 | namespace AsyncDataAdapter 10 | { 11 | public static class ProxyDataAdapterReflection 12 | { 13 | /// If the underlying field name for the key changes in a future release of .NET, then set this property before using any types in AsyncDataAdapter. 14 | public static String FillErrorEventKeyName = null; 15 | } 16 | 17 | public abstract partial class ProxyDataAdapter : DataAdapter, IDataAdapter 18 | { 19 | #region FillError 20 | 21 | private static readonly Object _fillErrorEventKeyFieldInfo = GetFillErrorEventKey(); 22 | 23 | private static Object GetFillErrorEventKey() 24 | { 25 | if( ProxyDataAdapterReflection.FillErrorEventKeyName is String customFieldName && Reflection.TryGetStaticFieldInfo( typeof(DataAdapter), name: customFieldName, out FieldInfo customField ) ) 26 | { 27 | return customField.GetValue( obj: null ); 28 | } 29 | else if( Reflection.TryGetStaticFieldInfo( typeof(DataAdapter), name: "EventFillError", out FieldInfo dotNetFramework4x ) ) 30 | { 31 | return dotNetFramework4x.GetValue( obj: null ); 32 | } 33 | else if( Reflection.TryGetStaticFieldInfo( typeof(DataAdapter), name: "s_eventFillError", out FieldInfo dotNetCore31 ) ) 34 | { 35 | return dotNetCore31.GetValue( obj: null ); 36 | } 37 | else 38 | { 39 | throw new InvalidOperationException( "Couldn't find DataAdapter's static event-key field for FillError." ); 40 | } 41 | } 42 | 43 | [DefaultValue(false)] 44 | public bool HasFillErrorHandler 45 | { 46 | get 47 | { 48 | Delegate d = base.Events[ _fillErrorEventKeyFieldInfo ]; 49 | return d != null; 50 | // ah, don't need to check `GetInvocationList().Length > 0`... right? 51 | } 52 | } 53 | 54 | private void OnFillErrorHandler( Exception ex, DataTable dataTable = null, object[] dataValues = null ) 55 | { 56 | FillErrorEventArgs fillErrorEvent = new FillErrorEventArgs( dataTable, dataValues ) 57 | { 58 | Errors = ex 59 | }; 60 | 61 | this.OnFillError( fillErrorEvent ); 62 | 63 | if (!fillErrorEvent.Continue) 64 | { 65 | if (fillErrorEvent.Errors != null) 66 | { 67 | throw fillErrorEvent.Errors; 68 | } 69 | 70 | throw ex; 71 | } 72 | } 73 | 74 | #endregion 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /AsyncDataAdapter/Data/_DataAdapter/ProxyDbDataAdapter.DbCommandBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Data.Common; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace AsyncDataAdapter 8 | { 9 | public abstract partial class ProxyDbDataAdapter 10 | { 11 | public virtual async Task> CreateCommandBuilderAsync( CancellationToken cancellationToken = default ) 12 | { 13 | DbCommandBuilder normalBuilder = this.CreateCommandBuilder(); 14 | 15 | DbCommandBuilder proxiedBuilder = await this.CreateProxiedCommandBuilderAsync( normalBuilder, cancellationToken ).ConfigureAwait(false); 16 | 17 | return proxiedBuilder; 18 | } 19 | 20 | protected abstract DbCommandBuilder CreateCommandBuilder(); 21 | 22 | protected virtual async Task> CreateProxiedCommandBuilderAsync( DbCommandBuilder builder, CancellationToken cancellationToken = default ) 23 | { 24 | DataTable selectCommandResultsSchema; 25 | using( DbDataReader reader = await this.SelectCommand.ExecuteReaderAsync( CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo, cancellationToken ).ConfigureAwait(false) ) 26 | { 27 | selectCommandResultsSchema = reader.GetSchemaTable(); 28 | } 29 | 30 | ProxyDbCommandBuilder proxiedBuilder = new ProxyDbCommandBuilder( 31 | subject : builder, 32 | selectCommandResultsSchema: selectCommandResultsSchema 33 | ); 34 | 35 | return proxiedBuilder; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AsyncDataAdapter/Data/_DataAdapter/ProxyDbDataAdapter.DbDataAdapter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Data.Common; 4 | using System.Reflection; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | using AsyncDataAdapter.Internal; 9 | 10 | namespace AsyncDataAdapter 11 | { 12 | public abstract partial class ProxyDbDataAdapter : IDbDataAdapter 13 | { 14 | #region Reimplement IDbDataAdapter 15 | 16 | // `DbDataAdapter.FooCommand` wraps its `IDbDataAdapter.FooCommand`'s vtable. 17 | // ...which is weird. 18 | /* 19 | 20 | IDbCommand IDbDataAdapter.DeleteCommand 21 | { 22 | get => this.DeleteCommand; 23 | set => this.DeleteCommand = (TDbCommand)value; 24 | } 25 | 26 | IDbCommand IDbDataAdapter.InsertCommand 27 | { 28 | get => this.InsertCommand; 29 | set => this.InsertCommand = (TDbCommand)value; 30 | } 31 | 32 | IDbCommand IDbDataAdapter.SelectCommand 33 | { 34 | get => this.SelectCommand; 35 | set => this.SelectCommand = (TDbCommand)value; 36 | } 37 | 38 | IDbCommand IDbDataAdapter.UpdateCommand 39 | { 40 | get => this.UpdateCommand; 41 | set => this.UpdateCommand = (TDbCommand)value; 42 | } 43 | 44 | */ 45 | 46 | #endregion 47 | 48 | #region DbDataAdapter virtuals 49 | 50 | public override Int32 UpdateBatchSize 51 | { 52 | get => this.Subject.UpdateBatchSize; 53 | set 54 | { 55 | this.Subject.UpdateBatchSize = value; 56 | base.UpdateBatchSize = value; 57 | } 58 | } 59 | 60 | private struct _Fill { } 61 | 62 | protected override Int32 Fill( DataSet dataSet, int startRecord, int maxRecords, string srcTable, IDbCommand command, CommandBehavior behavior ) 63 | { 64 | return ReflectedFunc.Invoke( this.Subject, dataSet, startRecord, maxRecords, srcTable, command, behavior ); 65 | } 66 | 67 | protected override Int32 Fill( DataTable[] dataTables, IDataReader dataReader, Int32 startRecord, Int32 maxRecords ) 68 | { 69 | return ReflectedFunc.Invoke( this.Subject, dataTables, dataReader, startRecord, maxRecords ); 70 | } 71 | 72 | private struct _FillSchema { } 73 | 74 | protected override DataTable[] FillSchema( DataSet dataSet, SchemaType schemaType, IDbCommand command, String srcTable, CommandBehavior behavior ) 75 | { 76 | return ReflectedFunc.Invoke( this.Subject, dataSet, schemaType, command, srcTable, behavior ); 77 | } 78 | 79 | protected override DataTable FillSchema( DataTable dataTable, SchemaType schemaType, IDbCommand command, CommandBehavior behavior ) 80 | { 81 | return ReflectedFunc.Invoke( this.Subject, dataTable, schemaType, command, behavior ); 82 | } 83 | 84 | private struct _Update { } 85 | 86 | protected override Int32 Update( DataRow[] dataRows, DataTableMapping tableMapping ) 87 | { 88 | return ReflectedFunc.Invoke( this.Subject, dataRows, tableMapping ); 89 | } 90 | 91 | #endregion 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /AsyncDataAdapter/Data/_DataAdapter/ProxyDbDataAdapter.FillError.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Data; 4 | using System.Data.Common; 5 | using System.Reflection; 6 | 7 | using AsyncDataAdapter.Internal; 8 | 9 | namespace AsyncDataAdapter 10 | { 11 | public abstract partial class ProxyDbDataAdapter 12 | { 13 | #region FillError 14 | 15 | private static readonly Object _fillErrorEventKeyFieldInfo = GetFillErrorEventKey(); 16 | 17 | private static Object GetFillErrorEventKey() 18 | { 19 | if( Reflection.TryGetStaticFieldInfo( typeof(DataAdapter), name: "EventFillError", out FieldInfo dotNetFramework4x ) ) 20 | { 21 | return dotNetFramework4x.GetValue( obj: null ); 22 | } 23 | else if( Reflection.TryGetStaticFieldInfo( typeof(DataAdapter), name: "s_eventFillError", out FieldInfo dotNetCore31 ) ) 24 | { 25 | return dotNetCore31.GetValue( obj: null ); 26 | } 27 | else 28 | { 29 | throw new InvalidOperationException( "Couldn't find DataAdapter's static event-key field for FillError." ); 30 | } 31 | } 32 | 33 | [DefaultValue(false)] 34 | public bool HasFillErrorHandler 35 | { 36 | get 37 | { 38 | Delegate d = base.Events[ _fillErrorEventKeyFieldInfo ]; 39 | return d != null; 40 | // ah, don't need to check `GetInvocationList().Length > 0`... right? 41 | } 42 | } 43 | 44 | private void OnFillErrorHandler( Exception ex, DataTable dataTable = null, object[] dataValues = null ) 45 | { 46 | FillErrorEventArgs fillErrorEvent = new FillErrorEventArgs( dataTable, dataValues ) 47 | { 48 | Errors = ex 49 | }; 50 | 51 | this.OnFillError( fillErrorEvent ); 52 | 53 | if (!fillErrorEvent.Continue) 54 | { 55 | if (fillErrorEvent.Errors != null) 56 | { 57 | throw fillErrorEvent.Errors; 58 | } 59 | 60 | throw ex; 61 | } 62 | } 63 | 64 | private Action GetCurrentFillErrorHandler() 65 | { 66 | if( this.HasFillErrorHandler ) 67 | { 68 | return this.OnFillErrorHandler; 69 | } 70 | 71 | return null; 72 | } 73 | 74 | #endregion 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /AsyncDataAdapter/Data/_DataAdapter/ProxyDbDataAdapter.FillSchemaAsync.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Data.Common; 4 | using System.Reflection; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | using AsyncDataAdapter.Internal; 9 | 10 | namespace AsyncDataAdapter 11 | { 12 | public abstract partial class ProxyDbDataAdapter 13 | { 14 | public Task FillSchemaAsync( DataTable dataTable, SchemaType schemaType, CancellationToken cancellationToken ) 15 | { 16 | TDbCommand selectCommand = this.SelectCommand; 17 | CommandBehavior fillCommandBehavior = this.FillCommandBehavior; 18 | 19 | return this.FillSchemaAsync( dataTable, schemaType, selectCommand, fillCommandBehavior, cancellationToken ); 20 | } 21 | 22 | public Task FillSchemaAsync( DataSet dataSet, SchemaType schemaType, string srcTable, CancellationToken cancellationToken ) 23 | { 24 | TDbCommand selectCommand = this.SelectCommand; 25 | CommandBehavior fillCommandBehavior = this.FillCommandBehavior; 26 | 27 | return this.FillSchemaAsync( dataSet, schemaType, selectCommand, srcTable, fillCommandBehavior, cancellationToken ); 28 | } 29 | 30 | protected virtual async Task FillSchemaAsync( DataSet dataSet, SchemaType schemaType, TDbCommand command, string srcTable, CommandBehavior behavior, CancellationToken cancellationToken ) 31 | { 32 | if (dataSet is null) throw new ArgumentNullException(nameof(dataSet)); 33 | if (SchemaType.Source != schemaType && SchemaType.Mapped != schemaType) throw ADP.InvalidSchemaType(schemaType); 34 | if (String.IsNullOrEmpty(srcTable)) throw ADP.FillSchemaRequiresSourceTableName("srcTable"); 35 | if (command == null) throw ADP.MissingSelectCommand("FillSchema"); 36 | 37 | // 38 | 39 | var arr = await this.FillSchemaInternalAsync( dataSet, null, schemaType, command, srcTable, behavior, cancellationToken ).ConfigureAwait(false); 40 | 41 | return (DataTable[])arr; 42 | } 43 | 44 | protected virtual async Task FillSchemaAsync( DataTable dataTable, SchemaType schemaType, TDbCommand command, CommandBehavior behavior, CancellationToken cancellationToken ) 45 | { 46 | if (dataTable is null) throw new ArgumentNullException(nameof(dataTable)); 47 | if (SchemaType.Source != schemaType && SchemaType.Mapped != schemaType) throw ADP.InvalidSchemaType(schemaType); 48 | if (command == null) throw ADP.MissingSelectCommand("FillSchema"); 49 | 50 | string srcTable = dataTable.TableName; 51 | 52 | IAdaSchemaMappingAdapter self = this; 53 | 54 | int num = self.IndexOfDataSetTable( srcTable ); 55 | if (-1 != num) 56 | { 57 | srcTable = base.TableMappings[num].SourceTable; 58 | } 59 | 60 | behavior = behavior | CommandBehavior.SingleResult; 61 | 62 | Object table = await this.FillSchemaInternalAsync( dataset: null, dataTable, schemaType, command, srcTable: srcTable, behavior, cancellationToken ).ConfigureAwait(false); 63 | return (DataTable)table; 64 | } 65 | 66 | /// Returns either (when is set) or [] (when is set). 67 | private async Task FillSchemaInternalAsync( DataSet dataset, DataTable datatable, SchemaType schemaType, TDbCommand command, string srcTable, CommandBehavior behavior, CancellationToken cancellationToken ) 68 | { 69 | TDbConnection connection = GetConnection( command ); 70 | ConnectionState originalState = ConnectionState.Open; 71 | 72 | behavior = behavior | CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo; 73 | 74 | try 75 | { 76 | originalState = await QuietOpenAsync( connection, cancellationToken ).ConfigureAwait(false); 77 | using( TDbDataReader dataReader = await ExecuteReaderAsync( command, behavior, cancellationToken ).ConfigureAwait(false) ) 78 | { 79 | if (null != datatable) 80 | { 81 | // delegate to next set of protected FillSchema methods 82 | DataTable singleTable = await this.FillSchemaAsync( datatable, schemaType, dataReader, cancellationToken ).ConfigureAwait(false); 83 | return singleTable; 84 | } 85 | else 86 | { 87 | DataTable[] tables = await this.FillSchemaAsync( dataset, schemaType, srcTable, dataReader, cancellationToken ).ConfigureAwait(false); 88 | return tables; 89 | } 90 | } 91 | } 92 | finally 93 | { 94 | QuietClose( connection, originalState); 95 | } 96 | } 97 | 98 | protected virtual Task FillSchemaAsync(DataSet dataSet, SchemaType schemaType, string srcTable, TDbDataReader dataReader, CancellationToken cancellationToken ) 99 | { 100 | return AsyncDataReaderMethods.FillSchemaAsync( adapter: this, this.ReturnProviderSpecificTypes, dataSet: dataSet, schemaType, srcTable, dataReader, cancellationToken ); 101 | } 102 | 103 | protected virtual Task FillSchemaAsync(DataTable dataTable, SchemaType schemaType, TDbDataReader dataReader, CancellationToken cancellationToken ) 104 | { 105 | return AsyncDataReaderMethods.FillSchemaAsync( adapter: this, this.ReturnProviderSpecificTypes, dataTable: dataTable, schemaType, dataReader, cancellationToken ); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /AsyncDataAdapter/Data/_DataAdapter/ProxyDbDataAdapter.UpdateAsync.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Data.Common; 4 | using System.Diagnostics; 5 | using System.Reflection; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | using AsyncDataAdapter.Internal; 10 | 11 | namespace AsyncDataAdapter 12 | { 13 | public abstract partial class ProxyDbDataAdapter 14 | { 15 | private DataTableMapping GetTableMapping( DataTable dataTable ) 16 | { 17 | IAdaSchemaMappingAdapter self = this; 18 | 19 | DataTableMapping tableMapping = null; 20 | int index = self.IndexOfDataSetTable(dataTable.TableName); 21 | if (-1 != index) 22 | { 23 | tableMapping = this.TableMappings[index]; 24 | } 25 | 26 | if (null == tableMapping) 27 | { 28 | if (this.MissingMappingAction == MissingMappingAction.Error) 29 | { 30 | throw ADP.MissingTableMappingDestination(dataTable.TableName); 31 | } 32 | 33 | tableMapping = new DataTableMapping(dataTable.TableName, dataTable.TableName); 34 | } 35 | 36 | return tableMapping; 37 | } 38 | 39 | #region UpdateAsync 40 | 41 | protected virtual RowUpdatedEventArgs CreateRowUpdatedEvent(DataRow dataRow, DbCommand command, StatementType statementType, DataTableMapping tableMapping) 42 | { 43 | return new RowUpdatedEventArgs(dataRow, command, statementType, tableMapping); 44 | } 45 | 46 | protected virtual RowUpdatingEventArgs CreateRowUpdatingEvent(DataRow dataRow, DbCommand command, StatementType statementType, DataTableMapping tableMapping) 47 | { 48 | return new RowUpdatingEventArgs(dataRow, command, statementType, tableMapping); 49 | } 50 | 51 | // protected virtual void OnRowUpdated(RowUpdatedEventArgs value) 52 | // { 53 | // } 54 | // 55 | // protected virtual void OnRowUpdating(RowUpdatingEventArgs value) 56 | // { 57 | // } 58 | 59 | public async Task UpdateAsync(DataRow[] dataRows, CancellationToken cancellationToken) 60 | { 61 | int rowsAffected = 0; 62 | if (null == dataRows) throw new ArgumentNullException(nameof(dataRows)); 63 | 64 | if (0 != dataRows.Length) 65 | { 66 | DataTable dataTable = null; 67 | for (int i = 0; i < dataRows.Length; ++i) 68 | { 69 | if ((null != dataRows[i]) && (dataTable != dataRows[i].Table)) 70 | { 71 | if (null != dataTable) throw new ArgumentException(string.Format("DataRow[{0}] is from a different DataTable than DataRow[0].", i)); 72 | dataTable = dataRows[i].Table; 73 | } 74 | } 75 | 76 | if (null != dataTable) 77 | { 78 | DataTableMapping tableMapping = this.GetTableMapping(dataTable); 79 | rowsAffected = await this.UpdateAsync( dataRows, tableMapping, cancellationToken ).ConfigureAwait(false); 80 | } 81 | } 82 | 83 | return rowsAffected; 84 | } 85 | 86 | protected virtual Task UpdateAsync( DataRow[] dataRows, DataTableMapping tableMapping, CancellationToken cancellationToken ) 87 | { 88 | return AsyncDataReaderUpdateMethods.UpdateAsync( this, dataRows, tableMapping, cancellationToken ); 89 | } 90 | 91 | public Task UpdateAsync( DataTable dataTable, CancellationToken cancellationToken ) 92 | { 93 | return AsyncDataReaderUpdateMethods.UpdateAsync( self: this, dataTable, cancellationToken ); 94 | } 95 | 96 | public Task UpdateAsync( DataSet dataSet, string srcTable, CancellationToken cancellationToken ) 97 | { 98 | return AsyncDataReaderUpdateMethods.UpdateAsync( self: this, dataSet, srcTable, cancellationToken ); 99 | } 100 | 101 | #endregion 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /DbSetup.sql: -------------------------------------------------------------------------------- 1 | SET NOCOUNT ON 2 | GO 3 | 4 | IF EXISTS(SELECT * FROM sys.tables where name = 'Tab1') 5 | BEGIN 6 | DROP TABLE Tab1 7 | END 8 | GO 9 | 10 | CREATE TABLE Tab1 ( 11 | Id INT NOT NULL, 12 | Txt NTEXT, 13 | StartDate DATETIME NOT NULL, 14 | DecVal DECIMAL(10, 3), 15 | FltVal FLOAT, 16 | 17 | PRIMARY KEY(Id) 18 | ) 19 | GO 20 | 21 | IF EXISTS(SELECT * FROM sys.objects WHERE type = 'P' AND name = 'GetFast') 22 | BEGIN 23 | DROP PROCEDURE GetFast 24 | END 25 | GO 26 | 27 | CREATE PROCEDURE GetFast 28 | @Number AS INT 29 | AS 30 | BEGIN 31 | SELECT * FROM Tab1 WHERE Id > @Number ORDER BY Id 32 | END 33 | GO 34 | 35 | 36 | IF EXISTS(SELECT * FROM sys.objects WHERE type = 'P' AND name = 'GetMulti') 37 | BEGIN 38 | DROP PROCEDURE GetMulti 39 | END 40 | GO 41 | 42 | CREATE PROCEDURE GetMulti 43 | @Number1 AS INT, 44 | @Number2 AS INT, 45 | @Number3 AS INT 46 | AS 47 | BEGIN 48 | SELECT TOP 50000 * FROM Tab1 WHERE Id > @Number1 ORDER BY Id 49 | 50 | WAITFOR DELAY '00:00:01' 51 | 52 | SELECT TOP 50000 * FROM Tab1 WHERE Id > @Number2 ORDER BY Id 53 | 54 | WAITFOR DELAY '00:00:01' 55 | 56 | SELECT TOP 50000 * FROM Tab1 WHERE Id > @Number3 ORDER BY Id 57 | 58 | WAITFOR DELAY '00:00:01' 59 | 60 | SELECT TOP 50000 * FROM Tab1 WHERE Id > @Number3 ORDER BY Id 61 | 62 | WAITFOR DELAY '00:00:01' 63 | 64 | SELECT TOP 0 * FROM Tab1 ORDER BY Id 65 | 66 | WAITFOR DELAY '00:00:01' 67 | 68 | SELECT TOP 50000 * FROM Tab1 WHERE Id > @Number3 ORDER BY Id 69 | 70 | WAITFOR DELAY '00:00:01' 71 | 72 | SELECT TOP 50000 * FROM Tab1 WHERE Id > @Number3 ORDER BY Id 73 | 74 | WAITFOR DELAY '00:00:01' 75 | 76 | SELECT TOP 50000 * FROM Tab1 WHERE Id > @Number3 ORDER BY Id 77 | END 78 | GO 79 | 80 | CREATE PROCEDURE dbo.ResetTab1 81 | AS 82 | BEGIN 83 | 84 | SET NOCOUNT ON 85 | 86 | TRUNCATE TABLE dbo.Tab1; 87 | 88 | INSERT INTO dbo.Tab1( Id, Txt, StartDate, DecVal, FltVal ) 89 | SELECT 90 | r.n + 1 AS Id, 91 | 'aaaaa' AS Txt, 92 | '2016-10-28' AS StartDate, 93 | 2.0 + ( CONVERT( decimal(10,3), r.n ) * 0.1 ) AS DecVal, 94 | 1.0 + ( CONVERT( float , r.n ) * 0.1 ) AS FltVal 95 | FROM 96 | ( 97 | SELECT 98 | t.n AS n 99 | FROM 100 | ( 101 | SELECT 102 | ( 103 | 1 * ones.n + 104 | 10 * tens.n + 105 | 100 * hundreds.n + 106 | 1000 * thousands.n + 107 | 10000 * ten_thousands.n + 108 | 100000 * hun_thousands.n + 109 | 1000000 * millions.n 110 | ) AS n 111 | FROM 112 | ( VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9) ) ones(n), 113 | ( VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9) ) tens(n), 114 | ( VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9) ) hundreds(n), 115 | ( VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9) ) thousands(n), 116 | ( VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9) ) ten_thousands(n), 117 | ( VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9) ) hun_thousands(n), 118 | ( VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9) ) millions(n) 119 | ) AS t 120 | WHERE 121 | t.n < 1000000 122 | ) AS r; 123 | 124 | RETURN 0; 125 | 126 | END 127 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-2021 Microsoft Corporation, Vladimir Kloz (voloda), Jeremy Kruer (jkruer01), Dai Rees (Jehoel) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Properties.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildProjectDirectory)\packages\MSBuildTasks.1.5.0.214\tools 5 | Release 6 | TMP 7 | . 8 | . 9 | nunit3-console.exe 10 | nuget.exe 11 | 12 | 13 | -------------------------------------------------------------------------------- /Tools/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daiplusplus/AsyncDataAdapter/401af0585801986456d48fd60aa2e00aeda20e98/Tools/NuGet.exe -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 2.0.{build} 2 | skip_tags: true 3 | image: 4 | - Visual Studio 2017 5 | 6 | services: 7 | - mssql2017 8 | 9 | install: 10 | - 'ECHO Install: %APPVEYOR_PULL_REQUEST_NUMBER%,%APPVEYOR_REPO_BRANCH%,' 11 | - IF "%APPVEYOR_PULL_REQUEST_NUMBER%" == "" IF "%APPVEYOR_REPO_BRANCH%" == "master" SET CreateBinaries=true 12 | 13 | configuration: Release 14 | 15 | build_script: 16 | - ECHO Create databases 17 | - 'nuget.exe restore AsyncDataAdapter.sln' 18 | - 'dotnet restore AsyncDataAdapter.sln' 19 | - 'msbuild.exe AsyncDataAdapter.msbuild /p:BuildNumber=%appveyor_build_version% /t:CI' 20 | 21 | artifacts: 22 | - path: .\AsyncDataAdapter\**\*.nupkg 23 | name: NugetPackages 24 | 25 | test: off 26 | --------------------------------------------------------------------------------