diff --git a/README.md b/README.md index c7a743d..f1fb335 100644 --- a/README.md +++ b/README.md @@ -7,40 +7,38 @@ The service was developed primary for syncing on premise SQL server data to Azur ## Prerequisites -- .NET 7 SDK - https://dotnet.microsoft.com/en-us/download -- Azure Functions Core Tools version 4.0.4785, or a later version. - https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local#v2 -- Azure CLI version 2.20, or a later version. - https://docs.microsoft.com/en-us/cli/azure/install-azure-cli +- .NET 10 SDK - https://dotnet.microsoft.com/en-us/download +- Azure Functions Core Tools version 4.0, or a later version - https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local#v2 +- Azure CLI version 2.50, or a later version - https://docs.microsoft.com/en-us/cli/azure/install-azure-cli - IDE - - Visual Studio - 17.4.2, or a later version - - VS Code - 1.73.1, or a later version + - Visual Studio 2022 17.10, or a later version + - VS Code 1.90, or a later version ## Configuration The function is configured through Azure App Settings / Environment variables, you can have multiple sync source/targets configures, and multiple tables per sync job. -| Key | Description | Example | -|-----------------------------------------------------|-------------------------------------------|------------------------------------------------------------------------------| -| `ProcessGlobalChangeTrackingSchedule` | Custom schedule cron expression | `0 */5 * * * *` | -| `SyncJobsConfig:Jobs:[key]:Source:ConnectionString` | Source database connection string | `Server=my.dbserver.net;Initial Catalog=MySourceDb;Integrated Security=True` | -| `SyncJobsConfig:Jobs:[key]:Source:ManagedIdentity` | Flag for if managed identity used | `false` | -| `SyncJobsConfig:Jobs:[key]:Source:TenantId` | Azure tenant ID used for managed identity | `46b41530-1e0d-4403-b815-24815944aa6a` | -| `SyncJobsConfig:Jobs:[key]:Target:ConnectionString` | Target database connection string | `Server=my.dbserver.net;Initial Catalog=MyTargetDb;Integrated Security=True` | -| `SyncJobsConfig:Jobs:[key]:Target:ManagedIdentity` | Flag for if managed identity used | `true` | -| `SyncJobsConfig:Jobs:[key]:Target:TenantId` | Azure tenant ID used for managed identity | `46b41530-1e0d-4403-b815-24815944aa6a` | -| `SyncJobsConfig:Jobs:[key]:BatchSize` | Bulk sync batch size | `1000` | -| `SyncJobsConfig:Jobs:[key]:Area` | Area name, used to manually trigger sync | `Development` | -| `SyncJobsConfig:Jobs:[key]:Manual` | Flag is sync excluded from schedules | `true` | -| `SyncJobsConfig:Jobs:[key]:Schedules:[key]` | Optional opt-in/out schedules | `true` | -| `SyncJobsConfig:Jobs:[key]:Tables:[key]` | Fully qualified name of table to sync | `dbo.MyTable` | +| Key | Description | Example | +|---------------------------------------------------------------------|-------------------------------------------|------------------------------------------------------------------------------| +| `ProcessGlobalChangeTrackingSchedule` | Custom schedule cron expression | `0 */5 * * * *` | +| `SyncJobsConfig__Jobs__[key]__Source__ConnectionString` | Source database connection string | `Server=my.dbserver.net;Initial Catalog=MySourceDb;Integrated Security=True` | +| `SyncJobsConfig__Jobs__[key]__Source__ManagedIdentity` | Flag for if managed identity used | `false` | +| `SyncJobsConfig__Jobs__[key]__Source__TenantId` | Azure tenant ID used for managed identity | `46b41530-1e0d-4403-b815-24815944aa6a` | +| `SyncJobsConfig__Jobs__[key]__Target__ConnectionString` | Target database connection string | `Server=my.dbserver.net;Initial Catalog=MyTargetDb;Integrated Security=True` | +| `SyncJobsConfig__Jobs__[key]__Target__ManagedIdentity` | Flag for if managed identity used | `true` | +| `SyncJobsConfig__Jobs__[key]__Target__TenantId` | Azure tenant ID used for managed identity | `46b41530-1e0d-4403-b815-24815944aa6a` | +| `SyncJobsConfig__Jobs__[key]__BatchSize` | Bulk sync batch size | `1000` | +| `SyncJobsConfig__Jobs__[key]__Area` | Area name, used to manually trigger sync | `Development` | +| `SyncJobsConfig__Jobs__[key]__Manual` | Flag is sync excluded from schedules | `true` | +| `SyncJobsConfig__Jobs__[key]__Schedules__[key]` | Optional opt-in/out schedules | `true` | +| `SyncJobsConfig__Jobs__[key]__Tables__[key]` | Fully qualified name of table to sync | `dbo.MyTable` | > Note: > -> Replace `[key]` with unique name of sync job / table config i.e. `MySync` / `MyTable` would result in `SyncJobsConfig:Jobs:MySync:Tables:MyTable`=`dbo.MyTable` +> Replace `[key]` with unique name of sync job / table config i.e. `MySync` / `MyTable` would result in `SyncJobsConfig__Jobs__MySync__Tables__MyTable`=`dbo.MyTable` > -> Non-Windows operating systems you'll need to replace `:` with `__`, i.e. `SyncJobsConfig__Jobs__MySync__Tables__MyTable` -> -> Configuration from KeyVault replace `:` with `--` i.e. `SyncJobsConfig--Jobs--MySync--Tables--MyTable` +> Configuration from KeyVault replace `__` with `--` i.e. `SyncJobsConfig--Jobs--MySync--Tables--MyTable` ## Schedules @@ -71,24 +69,50 @@ To quicker get started testing the function locally example configuration and da "AzureWebJobsStorage": "UseDevelopmentStorage=true", "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", "ProcessGlobalChangeTrackingSchedule": "0 23 11 * * *", - "SyncJobsConfig:Jobs:SyncTest:Area": "SyncTest", - "SyncJobsConfig:Jobs:SyncTest:Source:ConnectionString": "Server=localhost;Initial Catalog=SyncTest;Integrated Security=True", - "SyncJobsConfig:Jobs:SyncTest:Source:ManagedIdentity": false, - "SyncJobsConfig:Jobs:SyncTest:Target:ConnectionString": "Server=localhost;Initial Catalog=SyncTest;Integrated Security=True", - "SyncJobsConfig:Jobs:SyncTest:Target:ManagedIdentity": false, - "SyncJobsConfig:Jobs:SyncTest:BatchSize": 1000, - "SyncJobsConfig:Jobs:SyncTest:Manual": false, - "SyncJobsConfig:Jobs:SyncTest:Tables:Test": "source.[Test]", - "SyncJobsConfig:Jobs:SyncTest:TargetTables:Test": "target.[Test]", - "SyncJobsConfig:Jobs:SyncTest:Schedules:Custom": true, - "SyncJobsConfig:Jobs:SyncTest:Schedules:Noon": true, - "SyncJobsConfig:Jobs:SyncTest:Schedules:Midnight": true, - "SyncJobsConfig:Jobs:SyncTest:Schedules:EveryFiveMinutes": true, - "SyncJobsConfig:Jobs:SyncTest:Schedules:EveryHour": true + "SyncJobsConfig__Jobs__SyncTest__Area": "SyncTest", + "SyncJobsConfig__Jobs__SyncTest__Source__ConnectionString": "Server=localhost;Initial Catalog=SyncTest;Integrated Security=True", + "SyncJobsConfig__Jobs__SyncTest__Source__ManagedIdentity": false, + "SyncJobsConfig__Jobs__SyncTest__Target__ConnectionString": "Server=localhost;Initial Catalog=SyncTest;Integrated Security=True", + "SyncJobsConfig__Jobs__SyncTest__Target__ManagedIdentity": false, + "SyncJobsConfig__Jobs__SyncTest__BatchSize": 1000, + "SyncJobsConfig__Jobs__SyncTest__Manual": false, + "SyncJobsConfig__Jobs__SyncTest__Tables__Test": "source.[Test]", + "SyncJobsConfig__Jobs__SyncTest__TargetTables__Test": "target.[Test]", + "SyncJobsConfig__Jobs__SyncTest__Schedules__Custom": true, + "SyncJobsConfig__Jobs__SyncTest__Schedules__Noon": true, + "SyncJobsConfig__Jobs__SyncTest__Schedules__Midnight": true, + "SyncJobsConfig__Jobs__SyncTest__Schedules__EveryFiveMinutes": true, + "SyncJobsConfig__Jobs__SyncTest__Schedules__EveryHour": true } } ``` +### Example .env file for Docker + +``` +# Environment variables for running SqlBulkSyncFunction in Docker + +AzureWebJobsStorage=UseDevelopmentStorage=true +FUNCTIONS_WORKER_RUNTIME=dotnet-isolated +ProcessGlobalChangeTrackingSchedule=0 23 11 * * * +SyncJobsConfig__Jobs__SyncTest__Area=SyncTest +SyncJobsConfig__Jobs__SyncTest__Source__ConnectionString=Server=localhost;Initial Catalog=SyncTest;Integrated Security=True +SyncJobsConfig__Jobs__SyncTest__Source__ManagedIdentity=false +SyncJobsConfig__Jobs__SyncTest__Target__ConnectionString=Server=localhost;Initial Catalog=SyncTest;Integrated Security=True +SyncJobsConfig__Jobs__SyncTest__Target__ManagedIdentity=false +SyncJobsConfig__Jobs__SyncTest__BatchSize=1000 +SyncJobsConfig__Jobs__SyncTest__Manual=false +SyncJobsConfig__Jobs__SyncTest__Tables__Test=source.[Test] +SyncJobsConfig__Jobs__SyncTest__TargetTables__Test=target.[Test] +SyncJobsConfig__Jobs__SyncTest__Schedules__Custom=true +SyncJobsConfig__Jobs__SyncTest__Schedules__Noon=true +SyncJobsConfig__Jobs__SyncTest__Schedules__Midnight=true +SyncJobsConfig__Jobs__SyncTest__Schedules__EveryFiveMinutes=true +SyncJobsConfig__Jobs__SyncTest__Schedules__EveryHour=true +``` + + + ### Example database seed script ```sql diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 37b7367..db073b5 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,46 +1,51 @@ name: $(Year:yyyy).$(Month).$(DayOfMonth)$(Rev:.r) + trigger: - main -pool: - vmImage: 'windows-latest' - +pool: WCOM + variables: - solution: '**/*.sln' - buildPlatform: 'Any CPU' - buildConfiguration: 'Release' - artifactName: 'SqlBulkSyncFunction' - -steps: -- task: UseDotNet@2.263.0 - displayName: 'Use .NET SDK global.json' - inputs: - packageType: sdk - useGlobalJson: true - -- script: | - dotnet publish -p:Version=$(Build.BuildNumber) --configuration $(BuildConfiguration) -p:GenerateFullPaths=true --output "$(Build.ArtifactStagingDirectory)/Output" src/SqlBulkSyncFunction/SqlBulkSyncFunction.csproj - displayName: .NET Publish $(ArtifactName) - -- script: | - dotnet pack -p:Version=$(Build.BuildNumber) --configuration $(BuildConfiguration) -p:GenerateFullPaths=true --output "$(Build.ArtifactStagingDirectory)/NuGet" src/SqlBulkSyncFunction/SqlBulkSyncFunction.csproj - displayName: .NET Pack $(ArtifactName) - -- task: ArchiveFiles@2.265.0 - displayName: Archive Output $(ArtifactName) - inputs: - rootFolderOrFile: $(Build.ArtifactStagingDirectory)/Output - includeRootFolder: false - archiveFile: $(Build.ArtifactStagingDirectory)/$(ArtifactName).zip - -- task: PublishPipelineArtifact@1.242.0 - displayName: Publish Pipeline Artifact $(ArtifactName) Zip - inputs: - artifactName: $(ArtifactName) - path: $(Build.ArtifactStagingDirectory)/$(ArtifactName).zip - -- task: PublishPipelineArtifact@1.242.0 - displayName: Publish Pipeline Artifact $(ArtifactName) NuGet - inputs: - artifactName: "$(ArtifactName)NuGet" - path: $(Build.ArtifactStagingDirectory)/NuGet + - group: common-build-variables + +resources: + repositories: + - repository: templates + type: github + endpoint: GitHubPublic + name: WCOMAB/WCOM.AzurePipelines.YamlTemplates + ref: refs/heads/main + +stages: +- template: dotnetweb/stages.yml@templates + parameters: + devopsOrg: 'wcom' + system: 'sqlbulksync' + suffix: 'sqlbulksync' + webAppName: 'sqlbulksync' + build: Development + useDotNetSDK: + skipTask: true + skipTests: true + projectSrc: ./src/SqlBulkSyncFunction + buildParameters: + - '-c Release' + shouldDeploy: eq(variables['Build.SourceBranch'], 'refs/heads/main') + dpi: + report: eq(variables['Build.SourceBranch'], 'refs/heads/main') + WorkspaceId: $(API.DEV.LogId) + SharedKey: $(API.DEV.LogKey) + environments: + - env: dev + name: Development + deploy: false + container: + csproj: './src/SqlBulkSyncFunction/SqlBulkSyncFunction.csproj' + artifact: 'wcom-sqlbulksync-image' + repository: 'wcom.sqlbulksync' + baseimage: 'mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated10.0' + latest: true + servers: + - name: 'wcompublicacr.azurecr.io' + username: 'wcompublicacr' + password: '$(wcompublicacr.azurecr.io)' diff --git a/nuget.config b/nuget.config new file mode 100644 index 0000000..805dfb2 --- /dev/null +++ b/nuget.config @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/.editorconfig b/src/.editorconfig new file mode 100644 index 0000000..a855bb9 --- /dev/null +++ b/src/.editorconfig @@ -0,0 +1,429 @@ +# Version: 1.3.2 (Using https://semver.org/) +# Updated: 2019-08-04 +# See https://github.com/RehanSaeed/EditorConfig/releases for release notes. +# See https://github.com/RehanSaeed/EditorConfig for updates to this file. +# See http://EditorConfig.org for more information about .editorconfig files. +# Modified by marbjo@wcom.se + +########################################## +# Common Settings +########################################## + +# This file is the top-most EditorConfig file +root = true + +# All Files +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +########################################## +# File Extension Settings +########################################## + +# Visual Studio Solution Files +[*.{sln,slnx}] +indent_style = tab + +# Visual Studio XML Project Files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj,sqlproj}] +indent_size = 2 + +# Various XML Configuration Files +[*.{xml,config,props,targets,nuspec,resx,ruleset,vsixmanifest,vsct}] +indent_size = 2 + +# JSON Files +[*.{json,json5}] +indent_size = 2 + +# YAML Files +[*.{yml,yaml}] +indent_size = 2 + +# Markdown Files +[*.md] +trim_trailing_whitespace = false + +# Web Files +[*.{htm,html,js,ts,tsx,css,sass,scss,less,svg,vue}] +indent_size = 2 + +# Batch Files +[*.{cmd,bat}] +end_of_line = crlf + +# Bash Files +[*.sh] +end_of_line = lf + +########################################## +# .NET Language Conventions +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions +########################################## + +# .NET Code Style Settings +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#net-code-style-settings +[*.{cs,csx,cake,vb}] +# "this." and "Me." qualifiers +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#this-and-me +dotnet_style_qualification_for_field = false:warning +dotnet_style_qualification_for_property = false:warning +dotnet_style_qualification_for_method = false:warning +dotnet_style_qualification_for_event = false:warning +# Language keywords instead of framework type names for type references +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#language-keywords +dotnet_style_predefined_type_for_locals_parameters_members = true:warning +dotnet_style_predefined_type_for_member_access = true:warning +# Modifier preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#normalize-modifiers +dotnet_style_require_accessibility_modifiers = always:warning +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async +visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async +dotnet_style_readonly_field = true:warning +# Parentheses preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#parentheses-preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion +# Expression-level preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#expression-level-preferences +dotnet_style_object_initializer = true:warning +dotnet_style_collection_initializer = true:warning +dotnet_style_explicit_tuple_names = true:warning +dotnet_style_prefer_inferred_tuple_names = true:warning +dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning +dotnet_style_prefer_auto_properties = true:warning +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning +dotnet_style_prefer_conditional_expression_over_assignment = false:suggestion +dotnet_style_prefer_conditional_expression_over_return = false:suggestion +dotnet_style_prefer_compound_assignment = true:warning +# Null-checking preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#null-checking-preferences +dotnet_style_coalesce_expression = true:warning +dotnet_style_null_propagation = true:warning +# Parameter preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#parameter-preferences +dotnet_code_quality_unused_parameters = all:warning +# More style options (Undocumented) +# https://github.com/MicrosoftDocs/visualstudio-docs/issues/3641 +dotnet_style_operator_placement_when_wrapping = end_of_line + +# C# Code Style Settings +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#c-code-style-settings +[*.{cs,csx,cake}] +# Implicit and explicit types +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#implicit-and-explicit-types +csharp_style_var_for_built_in_types = true:warning +csharp_style_var_when_type_is_apparent = true:warning +csharp_style_var_elsewhere = true:warning +# Expression-bodied members +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#expression-bodied-members +csharp_style_expression_bodied_methods = true:warning +csharp_style_expression_bodied_constructors = true:warning +csharp_style_expression_bodied_operators = true:warning +csharp_style_expression_bodied_properties = true:warning +csharp_style_expression_bodied_indexers = true:warning +csharp_style_expression_bodied_accessors = true:warning +csharp_style_expression_bodied_lambdas = true:warning +csharp_style_expression_bodied_local_functions = true:warning +# Pattern matching +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#pattern-matching +csharp_style_pattern_matching_over_is_with_cast_check = true:warning +csharp_style_pattern_matching_over_as_with_null_check = true:warning +# Inlined variable declarations +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#inlined-variable-declarations +csharp_style_inlined_variable_declaration = true:warning +# Expression-level preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#expression-level-preferences +csharp_prefer_simple_default_expression = true:warning +# "Null" checking preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#c-null-checking-preferences +csharp_style_throw_expression = true:warning +csharp_style_conditional_delegate_call = true:warning +# Code block preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#code-block-preferences +csharp_prefer_braces = true:warning +# Unused value preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#unused-value-preferences +csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +# Index and range preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#index-and-range-preferences +csharp_style_prefer_index_operator = true:warning +csharp_style_prefer_range_operator = true:warning +# Miscellaneous preferences +# https://docs.microsoft.com/visualstudio/ide/editorconfig-language-conventions#miscellaneous-preferences +csharp_style_deconstructed_variable_declaration = true:warning +csharp_style_pattern_local_over_anonymous_function = true:warning +csharp_using_directive_placement = outside_namespace:silent +csharp_prefer_static_local_function = true:warning +csharp_prefer_simple_using_statement = true:warning + +########################################## +# .NET Formatting Conventions +# https://docs.microsoft.com/visualstudio/ide/editorconfig-code-style-settings-reference#formatting-conventions +########################################## + +# Organize usings +# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#organize-using-directives +dotnet_sort_system_directives_first = true +# Newline options +# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#new-line-options +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true +# Indentation options +# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#indentation-options +csharp_indent_case_contents = true +csharp_indent_switch_labels = true +csharp_indent_labels = no_change +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents_when_block = false +# Spacing options +# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#spacing-options +csharp_space_after_cast = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_parentheses = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_around_binary_operators = before_and_after +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_after_comma = true +csharp_space_before_comma = false +csharp_space_after_dot = false +csharp_space_before_dot = false +csharp_space_after_semicolon_in_for_statement = true +csharp_space_before_semicolon_in_for_statement = false +csharp_space_around_declaration_statements = false +csharp_space_before_open_square_brackets = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_square_brackets = false +# Wrapping options +# https://docs.microsoft.com/visualstudio/ide/editorconfig-formatting-conventions#wrap-options +csharp_preserve_single_line_statements = false +csharp_preserve_single_line_blocks = true + +########################################## +# .NET Naming Conventions +# https://docs.microsoft.com/visualstudio/ide/editorconfig-naming-conventions +########################################## + +[*.{cs,csx,cake,vb}] + +########################################## +# Styles +########################################## + +# camel_case_style - Define the camelCase style +dotnet_naming_style.camel_case_style.capitalization = camel_case +# pascal_case_style - Define the PascalCase style +dotnet_naming_style.pascal_case_style.capitalization = pascal_case +# first_upper_style - The first character must start with an upper-case character +dotnet_naming_style.first_upper_style.capitalization = first_word_upper +# prefix_interface_with_i_style - Interfaces must be PascalCase and the first character of an interface must be an 'I' +dotnet_naming_style.prefix_interface_with_i_style.capitalization = pascal_case +dotnet_naming_style.prefix_interface_with_i_style.required_prefix = I +# prefix_type_parameters_with_t_style - Generic Type Parameters must be PascalCase and the first character must be a 'T' +dotnet_naming_style.prefix_type_parameters_with_t_style.capitalization = pascal_case +dotnet_naming_style.prefix_type_parameters_with_t_style.required_prefix = T +# disallowed_style - Anything that has this style applied is marked as disallowed +dotnet_naming_style.disallowed_style.capitalization = pascal_case +dotnet_naming_style.disallowed_style.required_prefix = ____RULE_VIOLATION____ +dotnet_naming_style.disallowed_style.required_suffix = ____RULE_VIOLATION____ +# internal_error_style - This style should never occur... if it does, it's indicates a bug in file or in the parser using the file +dotnet_naming_style.internal_error_style.capitalization = pascal_case +dotnet_naming_style.internal_error_style.required_prefix = ____INTERNAL_ERROR____ +dotnet_naming_style.internal_error_style.required_suffix = ____INTERNAL_ERROR____ + +########################################## +# .NET Design Guideline Field Naming Rules +# Naming rules for fields follow the .NET Framework design guidelines +# https://docs.microsoft.com/dotnet/standard/design-guidelines/index +########################################## + +# All public/protected/protected_internal constant fields must be PascalCase +# https://docs.microsoft.com/dotnet/standard/design-guidelines/field +dotnet_naming_symbols.public_protected_constant_fields_group.applicable_accessibilities = public, protected, protected_internal +dotnet_naming_symbols.public_protected_constant_fields_group.required_modifiers = const +dotnet_naming_symbols.public_protected_constant_fields_group.applicable_kinds = field +dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.symbols = public_protected_constant_fields_group +dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.style = pascal_case_style +dotnet_naming_rule.public_protected_constant_fields_must_be_pascal_case_rule.severity = warning + +# All public/protected/protected_internal static readonly fields must be PascalCase +# https://docs.microsoft.com/dotnet/standard/design-guidelines/field +dotnet_naming_symbols.public_protected_static_readonly_fields_group.applicable_accessibilities = public, protected, protected_internal +dotnet_naming_symbols.public_protected_static_readonly_fields_group.required_modifiers = static, readonly +dotnet_naming_symbols.public_protected_static_readonly_fields_group.applicable_kinds = field +dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.symbols = public_protected_static_readonly_fields_group +dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.style = pascal_case_style +dotnet_naming_rule.public_protected_static_readonly_fields_must_be_pascal_case_rule.severity = warning + +# No other public/protected/protected_internal fields are allowed +# https://docs.microsoft.com/dotnet/standard/design-guidelines/field +dotnet_naming_symbols.other_public_protected_fields_group.applicable_accessibilities = public, protected, protected_internal +dotnet_naming_symbols.other_public_protected_fields_group.applicable_kinds = field +dotnet_naming_rule.other_public_protected_fields_disallowed_rule.symbols = other_public_protected_fields_group +dotnet_naming_rule.other_public_protected_fields_disallowed_rule.style = disallowed_style +dotnet_naming_rule.other_public_protected_fields_disallowed_rule.severity = error + +########################################## +# StyleCop Field Naming Rules +# Naming rules for fields follow the StyleCop analyzers +# This does not override any rules using disallowed_style above +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers +########################################## + +# All constant fields must be PascalCase +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1303.md +dotnet_naming_symbols.stylecop_constant_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected, private +dotnet_naming_symbols.stylecop_constant_fields_group.required_modifiers = const +dotnet_naming_symbols.stylecop_constant_fields_group.applicable_kinds = field +dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.symbols = stylecop_constant_fields_group +dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.style = pascal_case_style +dotnet_naming_rule.stylecop_constant_fields_must_be_pascal_case_rule.severity = warning + +# All static readonly fields must be PascalCase +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1311.md +dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected, private +dotnet_naming_symbols.stylecop_static_readonly_fields_group.required_modifiers = static, readonly +dotnet_naming_symbols.stylecop_static_readonly_fields_group.applicable_kinds = field +dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.symbols = stylecop_static_readonly_fields_group +dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.style = pascal_case_style +dotnet_naming_rule.stylecop_static_readonly_fields_must_be_pascal_case_rule.severity = warning + +# No non-private instance fields are allowed +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1401.md +dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_accessibilities = public, internal, protected_internal, protected, private_protected +dotnet_naming_symbols.stylecop_fields_must_be_private_group.applicable_kinds = field +dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.symbols = stylecop_fields_must_be_private_group +dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.style = disallowed_style +dotnet_naming_rule.stylecop_instance_fields_must_be_private_rule.severity = error + +# Private fields must have underscore +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1306.md +dotnet_naming_rule.private_members_with_underscore.symbols = private_fields +dotnet_naming_rule.private_members_with_underscore.style = prefix_underscore +dotnet_naming_rule.private_members_with_underscore.severity = suggestion +dotnet_naming_symbols.private_fields.applicable_kinds = field +dotnet_naming_symbols.private_fields.applicable_accessibilities = private +dotnet_naming_style.prefix_underscore.capitalization = camel_case +dotnet_naming_style.prefix_underscore.required_prefix = _ + +# Local variables must be camelCase +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1312.md +dotnet_naming_symbols.stylecop_local_fields_group.applicable_accessibilities = local +dotnet_naming_symbols.stylecop_local_fields_group.applicable_kinds = local +dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.symbols = stylecop_local_fields_group +dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.style = camel_case_style +dotnet_naming_rule.stylecop_local_fields_must_be_camel_case_rule.severity = silent + +# This rule should never fire. However, it's included for at least two purposes: +# First, it helps to understand, reason about, and root-case certain types of issues, such as bugs in .editorconfig parsers. +# Second, it helps to raise immediate awareness if a new field type is added (as occurred recently in C#). +dotnet_naming_symbols.sanity_check_uncovered_field_case_group.applicable_accessibilities = * +dotnet_naming_symbols.sanity_check_uncovered_field_case_group.applicable_kinds = field +dotnet_naming_rule.sanity_check_uncovered_field_case_rule.symbols = sanity_check_uncovered_field_case_group +dotnet_naming_rule.sanity_check_uncovered_field_case_rule.style = internal_error_style +dotnet_naming_rule.sanity_check_uncovered_field_case_rule.severity = error + + +########################################## +# Other Naming Rules +########################################## + +# All of the following must be PascalCase: +# - Namespaces +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-namespaces +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md +# - Classes and Enumerations +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces +# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1300.md +# - Delegates +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces#names-of-common-types +# - Constructors, Properties, Events, Methods +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-type-members +dotnet_naming_symbols.element_group.applicable_kinds = namespace, class, enum, struct, delegate, event, method, property +dotnet_naming_rule.element_rule.symbols = element_group +dotnet_naming_rule.element_rule.style = pascal_case_style +dotnet_naming_rule.element_rule.severity = warning + +# Interfaces use PascalCase and are prefixed with uppercase 'I' +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces +dotnet_naming_symbols.interface_group.applicable_kinds = interface +dotnet_naming_rule.interface_rule.symbols = interface_group +dotnet_naming_rule.interface_rule.style = prefix_interface_with_i_style +dotnet_naming_rule.interface_rule.severity = warning + +# Generics Type Parameters use PascalCase and are prefixed with uppercase 'T' +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces +dotnet_naming_symbols.type_parameter_group.applicable_kinds = type_parameter +dotnet_naming_rule.type_parameter_rule.symbols = type_parameter_group +dotnet_naming_rule.type_parameter_rule.style = prefix_type_parameters_with_t_style +dotnet_naming_rule.type_parameter_rule.severity = warning + +# Function parameters use camelCase +# https://docs.microsoft.com/dotnet/standard/design-guidelines/naming-parameters +dotnet_naming_symbols.parameters_group.applicable_kinds = parameter +dotnet_naming_rule.parameters_rule.symbols = parameters_group +dotnet_naming_rule.parameters_rule.style = camel_case_style +dotnet_naming_rule.parameters_rule.severity = warning + +########################################## +# License +########################################## +# The following applies as to the .editorconfig file ONLY, and is +# included below for reference, per the requirements of the license +# corresponding to this .editorconfig file. +# See: https://github.com/RehanSaeed/EditorConfig +# +# MIT License +# +# Copyright (c) 2017-2019 Muhammad Rehan Saeed +# Copyright (c) 2019 Henry Gabryjelski +# +# Permission is hereby granted, free of charge, to any +# person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the +# Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, +# sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject +# to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +########################################## + +# CA1040: Avoid empty interfaces +dotnet_diagnostic.CA1040.severity = warning + +# CA1014: Mark assemblies with CLSCompliant +dotnet_diagnostic.CA1014.severity = none + +# IDE0005: Using directive is unnecessary +dotnet_diagnostic.IDE0005.severity = warning \ No newline at end of file diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props new file mode 100644 index 0000000..232f613 --- /dev/null +++ b/src/Directory.Packages.props @@ -0,0 +1,17 @@ + + + true + + + + + + + + + + + + + + diff --git a/src/SqlBulkSyncFunction.sln b/src/SqlBulkSyncFunction.sln deleted file mode 100644 index 035762f..0000000 --- a/src/SqlBulkSyncFunction.sln +++ /dev/null @@ -1,34 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.6.30114.105 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SqlBulkSyncFunction", "SqlBulkSyncFunction\SqlBulkSyncFunction.csproj", "{39549960-540B-4118-82A7-1E8DEE9D6362}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {39549960-540B-4118-82A7-1E8DEE9D6362}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {39549960-540B-4118-82A7-1E8DEE9D6362}.Debug|Any CPU.Build.0 = Debug|Any CPU - {39549960-540B-4118-82A7-1E8DEE9D6362}.Debug|x64.ActiveCfg = Debug|Any CPU - {39549960-540B-4118-82A7-1E8DEE9D6362}.Debug|x64.Build.0 = Debug|Any CPU - {39549960-540B-4118-82A7-1E8DEE9D6362}.Debug|x86.ActiveCfg = Debug|Any CPU - {39549960-540B-4118-82A7-1E8DEE9D6362}.Debug|x86.Build.0 = Debug|Any CPU - {39549960-540B-4118-82A7-1E8DEE9D6362}.Release|Any CPU.ActiveCfg = Release|Any CPU - {39549960-540B-4118-82A7-1E8DEE9D6362}.Release|Any CPU.Build.0 = Release|Any CPU - {39549960-540B-4118-82A7-1E8DEE9D6362}.Release|x64.ActiveCfg = Release|Any CPU - {39549960-540B-4118-82A7-1E8DEE9D6362}.Release|x64.Build.0 = Release|Any CPU - {39549960-540B-4118-82A7-1E8DEE9D6362}.Release|x86.ActiveCfg = Release|Any CPU - {39549960-540B-4118-82A7-1E8DEE9D6362}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal diff --git a/src/SqlBulkSyncFunction.slnx b/src/SqlBulkSyncFunction.slnx new file mode 100644 index 0000000..6502b97 --- /dev/null +++ b/src/SqlBulkSyncFunction.slnx @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/SqlBulkSyncFunction/Constants.cs b/src/SqlBulkSyncFunction/Constants.cs new file mode 100644 index 0000000..b47a4d5 --- /dev/null +++ b/src/SqlBulkSyncFunction/Constants.cs @@ -0,0 +1,12 @@ +namespace SqlBulkSyncFunction; + +/// +/// Constants used throughout the application. +/// +public static class Constants +{ + /// + /// Queue name for processing global change tracking jobs. + /// + public const string ProcessGlobalChangeTrackingQueue = "processglobalchangetrackingqueue"; +} diff --git a/src/SqlBulkSyncFunction/Functions/ProcessGlobalChangeTrackingQueue.cs b/src/SqlBulkSyncFunction/Functions/ProcessGlobalChangeTrackingQueue.cs index 8e259ac..486dd64 100644 --- a/src/SqlBulkSyncFunction/Functions/ProcessGlobalChangeTrackingQueue.cs +++ b/src/SqlBulkSyncFunction/Functions/ProcessGlobalChangeTrackingQueue.cs @@ -15,10 +15,10 @@ IProcessSyncJobService ProcessSyncJobService { [Function(nameof(ProcessGlobalChangeTrackingQueue))] - - public async Task Run([QueueTrigger(nameof(ProcessGlobalChangeTrackingQueue))] SyncJob syncJob) + + public async Task Run([QueueTrigger(SqlBulkSyncFunction.Constants.ProcessGlobalChangeTrackingQueue)] SyncJob syncJob) { - if(syncJob == null) + if (syncJob == null) { return; } diff --git a/src/SqlBulkSyncFunction/Functions/ProcessGlobalChangeTrackingSchedule.cs b/src/SqlBulkSyncFunction/Functions/ProcessGlobalChangeTrackingSchedule.cs index 043b1d6..9802094 100644 --- a/src/SqlBulkSyncFunction/Functions/ProcessGlobalChangeTrackingSchedule.cs +++ b/src/SqlBulkSyncFunction/Functions/ProcessGlobalChangeTrackingSchedule.cs @@ -5,11 +5,10 @@ using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using SqlBulkSyncFunction.Helpers; using SqlBulkSyncFunction.Models; using SqlBulkSyncFunction.Models.Job; using SqlBulkSyncFunction.Services; -using SqlBulkSyncFunction.Helpers; -using System.Collections.Generic; // ReSharper disable UnusedMember.Global namespace SqlBulkSyncFunction.Functions diff --git a/src/SqlBulkSyncFunction/Functions/QueueGlobalChangeTracking.cs b/src/SqlBulkSyncFunction/Functions/QueueGlobalChangeTracking.cs index 35ea960..6d7fd76 100644 --- a/src/SqlBulkSyncFunction/Functions/QueueGlobalChangeTracking.cs +++ b/src/SqlBulkSyncFunction/Functions/QueueGlobalChangeTracking.cs @@ -1,6 +1,5 @@ using System; using System.Net; -using System.Text.Json; using System.Threading.Tasks; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; @@ -11,7 +10,7 @@ using SqlBulkSyncFunction.Models.Job; using SqlBulkSyncFunction.Services; -namespace SqlBulkSyncFunction.Functions +namespace SqlBulkSyncFunction.Functions { public record QueueGlobalChangeTracking( ILogger Logger, @@ -28,10 +27,7 @@ public async Task Queue( )] HttpRequestData req, string area, string id - ) - { - return await GetQueueGlobalChangeTrackingResult(req, area, id); - } + ) => await GetQueueGlobalChangeTrackingResult(req, area, id); [Function(nameof(QueueGlobalChangeTracking) + nameof(Seed))] public async Task Seed( @@ -43,10 +39,7 @@ public async Task Seed( string area, string id, bool seed - ) - { - return await GetQueueGlobalChangeTrackingResult(req, area, id, seed); - } + ) => await GetQueueGlobalChangeTrackingResult(req, area, id, seed); private async Task GetQueueGlobalChangeTrackingResult( HttpRequestData req, @@ -72,7 +65,7 @@ private async Task GetQueueGlobalChangeTracking expires: DateTimeOffset.UtcNow.AddMinutes(4), id: id, schedule: nameof(jobConfig.Manual), - seed:seed + seed: seed ), req.CreateResponse(HttpStatusCode.Accepted) ); diff --git a/src/SqlBulkSyncFunction/Helpers/SchemaExtensions.cs b/src/SqlBulkSyncFunction/Helpers/SchemaExtensions.cs index 6ada8ac..f361c76 100644 --- a/src/SqlBulkSyncFunction/Helpers/SchemaExtensions.cs +++ b/src/SqlBulkSyncFunction/Helpers/SchemaExtensions.cs @@ -74,7 +74,8 @@ string tableName { var tableVersion = conn.Query( commandTimeout: 180, - param: new { + param: new + { TableName = tableName }, sql: @"SELECT TableName, @@ -86,11 +87,11 @@ FROM sync.TableVersion ) .SingleOrDefault() ?? new TableVersion - { - TableName = tableName, - CurrentVersion = -1, - MinValidVersion = -1 - }; + { + TableName = tableName, + CurrentVersion = -1, + MinValidVersion = -1 + }; return tableVersion; } @@ -116,9 +117,7 @@ Column[] columns return result; } - public static Column[] GetColumns(this IDbConnection sourceConn, string tableName) - { - return sourceConn + public static Column[] GetColumns(this IDbConnection sourceConn, string tableName) => sourceConn .Query( commandTimeout: 5000, param: new { TableName = tableName }, @@ -175,6 +174,5 @@ END AS IsPrimary tp.name <> 'timestamp'" ) .ToArray(); - } } } diff --git a/src/SqlBulkSyncFunction/Helpers/SqlCommandExtensions.cs b/src/SqlBulkSyncFunction/Helpers/SqlCommandExtensions.cs index 63f1612..6a86b47 100644 --- a/src/SqlBulkSyncFunction/Helpers/SqlCommandExtensions.cs +++ b/src/SqlBulkSyncFunction/Helpers/SqlCommandExtensions.cs @@ -15,9 +15,7 @@ public static void DropSyncTables( TableSchema tableSchema, object scope, ILogger logger - ) - { - Array.ForEach( + ) => Array.ForEach( new[] { new @@ -34,7 +32,10 @@ ILogger logger table => { if (string.IsNullOrEmpty(tableSchema?.SyncNewOrUpdatedTableName)) + { return; + } + try { targetConn.Execute( @@ -56,7 +57,6 @@ ILogger logger } } ); - } public static void MergeData( this SqlConnection targetConn, @@ -92,9 +92,7 @@ public static void BulkCopyData( TableSchema tableSchema, object scope, ILogger logger - ) - { - Array.ForEach( + ) => Array.ForEach( new[] { new @@ -128,7 +126,7 @@ ILogger logger BatchSize = tableSchema.BatchSize, NotifyAfter = tableSchema.BatchSize, BulkCopyTimeout = 300, - EnableStreaming= true + EnableStreaming = true }; bcp.SqlRowsCopied += (s, e) => logger.LogInformation("{Scope} {TableName} {RowsCopied} {Description} rows copied", scope, table.Name, e.RowsCopied, table.Description); @@ -136,28 +134,22 @@ ILogger logger logger.LogInformation("{Scope} Bulk copy complete for {Description}.", scope, table.Description); } ); - } public static bool SyncTablesExist( this SqlConnection targetConn, TableSchema tableSchema - ) - { - return targetConn.Query( + ) => targetConn.Query( commandType: CommandType.Text, commandTimeout: 500, sql: tableSchema.SyncTableExistStatement ).First(); - } public static void CreateSyncTables( this SqlConnection targetConn, TableSchema tableSchema, object scope, ILogger logger - ) - { - Array.ForEach( + ) => Array.ForEach( new[] { new @@ -181,7 +173,6 @@ ILogger logger logger.LogInformation("{Scope} Sync table {Name} created.", scope, table.Name); } ); - } public static void EnsureSyncSchemaAndTableExists( this SqlConnection targetConn, diff --git a/src/SqlBulkSyncFunction/Helpers/SqlStatementExtensions.cs b/src/SqlBulkSyncFunction/Helpers/SqlStatementExtensions.cs index a494411..79b35f7 100644 --- a/src/SqlBulkSyncFunction/Helpers/SqlStatementExtensions.cs +++ b/src/SqlBulkSyncFunction/Helpers/SqlStatementExtensions.cs @@ -45,16 +45,13 @@ FROM CHANGETABLE(VERSION {0}, ({1}), ({1})) as t", ); } - public static string GetDropStatement(this string tableName) - { - return string.Format( + public static string GetDropStatement(this string tableName) => string.Format( @"IF OBJECT_ID('{0}') IS NOT NULL BEGIN DROP TABLE {0} END", tableName ); - } public static string GetNewOrUpdatedMergeStatement(this TableSchema tableSchema) { diff --git a/src/SqlBulkSyncFunction/Helpers/SyncJobConfigExtensions.cs b/src/SqlBulkSyncFunction/Helpers/SyncJobConfigExtensions.cs index fe2132a..e47dc3a 100644 --- a/src/SqlBulkSyncFunction/Helpers/SyncJobConfigExtensions.cs +++ b/src/SqlBulkSyncFunction/Helpers/SyncJobConfigExtensions.cs @@ -14,7 +14,7 @@ public static SyncJob ToSyncJob( ConcurrentDictionary tokenCache, DateTimeOffset expires, bool seed - ) => new ( + ) => new( id, schedule, job.Area, @@ -41,18 +41,15 @@ private static SyncJobTable[] ToSyncJobTables(this SyncJobConfig job) sourceTable.Value, targetTableLookup?[sourceTable.Key].FirstOrDefault() switch { - { Length:>0 } overrideTargetTable => overrideTargetTable, - _=> sourceTable.Value + { Length: > 0 } overrideTargetTable => overrideTargetTable, + _ => sourceTable.Value } ) ).ToArray(); } - private static string TryGetToken(SyncJobConfigDataSource dataSource, ConcurrentDictionary tokenCache) - { - return dataSource.ManagedIdentity && tokenCache.TryGetValue(dataSource.TenantId ?? string.Empty, out var sourceToken) + private static string TryGetToken(SyncJobConfigDataSource dataSource, ConcurrentDictionary tokenCache) => dataSource.ManagedIdentity && tokenCache.TryGetValue(dataSource.TenantId ?? string.Empty, out var sourceToken) ? sourceToken : null; - } } } diff --git a/src/SqlBulkSyncFunction/Models/Job/SyncJobConfig.cs b/src/SqlBulkSyncFunction/Models/Job/SyncJobConfig.cs index ebab20d..16e6c46 100644 --- a/src/SqlBulkSyncFunction/Models/Job/SyncJobConfig.cs +++ b/src/SqlBulkSyncFunction/Models/Job/SyncJobConfig.cs @@ -13,4 +13,4 @@ public record SyncJobConfig public bool? Manual { get; init; } public Dictionary Schedules { get; init; } } -} \ No newline at end of file +} diff --git a/src/SqlBulkSyncFunction/Models/Job/SyncJobConfigDataSource.cs b/src/SqlBulkSyncFunction/Models/Job/SyncJobConfigDataSource.cs index 55cfbb7..81bfbe8 100644 --- a/src/SqlBulkSyncFunction/Models/Job/SyncJobConfigDataSource.cs +++ b/src/SqlBulkSyncFunction/Models/Job/SyncJobConfigDataSource.cs @@ -6,4 +6,4 @@ public record SyncJobConfigDataSource public bool ManagedIdentity { get; set; } public string TenantId { get; set; } } -} \ No newline at end of file +} diff --git a/src/SqlBulkSyncFunction/Models/Job/SyncJobTable.cs b/src/SqlBulkSyncFunction/Models/Job/SyncJobTable.cs index 327b30e..90c8d51 100644 --- a/src/SqlBulkSyncFunction/Models/Job/SyncJobTable.cs +++ b/src/SqlBulkSyncFunction/Models/Job/SyncJobTable.cs @@ -1,4 +1,4 @@ namespace SqlBulkSyncFunction.Models.Job { public record SyncJobTable(string Source, string Target); -} \ No newline at end of file +} diff --git a/src/SqlBulkSyncFunction/Models/Job/SyncJobsConfig.cs b/src/SqlBulkSyncFunction/Models/Job/SyncJobsConfig.cs index 6f6b9c8..81a2a36 100644 --- a/src/SqlBulkSyncFunction/Models/Job/SyncJobsConfig.cs +++ b/src/SqlBulkSyncFunction/Models/Job/SyncJobsConfig.cs @@ -6,9 +6,9 @@ namespace SqlBulkSyncFunction.Models.Job { public record SyncJobsConfig { - private static readonly string[] DefaultJobSchedules = {"Custom"}; + private static readonly string[] DefaultJobSchedules = { "Custom" }; - public Dictionary Jobs { get; init; } + public Dictionary Jobs { get; init; } public Lazy> ScheduledJobs { get; } private static Dictionary Empty { get; } = new Dictionary(0); @@ -33,12 +33,9 @@ private static IEnumerable GetJobSchedules(KeyValuePairvalue.Value) - .Select(key=>key.Key); + .Where(value => value.Value) + .Select(key => key.Key); - public SyncJobsConfig() - { - ScheduledJobs = new Lazy>(GetScheduledJobs); - } + public SyncJobsConfig() => ScheduledJobs = new Lazy>(GetScheduledJobs); } -} \ No newline at end of file +} diff --git a/src/SqlBulkSyncFunction/Models/ProcessGlobalChangeTrackingResult.cs b/src/SqlBulkSyncFunction/Models/ProcessGlobalChangeTrackingResult.cs index 9820b1c..591c442 100644 --- a/src/SqlBulkSyncFunction/Models/ProcessGlobalChangeTrackingResult.cs +++ b/src/SqlBulkSyncFunction/Models/ProcessGlobalChangeTrackingResult.cs @@ -1,13 +1,12 @@ using Microsoft.Azure.Functions.Worker; -using SqlBulkSyncFunction.Functions; using SqlBulkSyncFunction.Models.Job; namespace SqlBulkSyncFunction.Models { public record ProcessGlobalChangeTrackingResult( - [property:QueueOutput(nameof(ProcessGlobalChangeTrackingQueue))]params SyncJob[] SyncJobs + [property: QueueOutput(SqlBulkSyncFunction.Constants.ProcessGlobalChangeTrackingQueue)] params SyncJob[] SyncJobs ) { public static ProcessGlobalChangeTrackingResult Empty { get; } = new(); } -} \ No newline at end of file +} diff --git a/src/SqlBulkSyncFunction/Models/QueueGlobalChangeTrackingResult.cs b/src/SqlBulkSyncFunction/Models/QueueGlobalChangeTrackingResult.cs index cc91890..5afb913 100644 --- a/src/SqlBulkSyncFunction/Models/QueueGlobalChangeTrackingResult.cs +++ b/src/SqlBulkSyncFunction/Models/QueueGlobalChangeTrackingResult.cs @@ -1,13 +1,12 @@ using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; -using SqlBulkSyncFunction.Functions; using SqlBulkSyncFunction.Models.Job; namespace SqlBulkSyncFunction.Models { public record QueueGlobalChangeTrackingResult( - [property:QueueOutput(nameof(ProcessGlobalChangeTrackingQueue))] + [property:QueueOutput(SqlBulkSyncFunction.Constants.ProcessGlobalChangeTrackingQueue)] SyncJob SyncJob, HttpResponseData HttpResponseData ); -} \ No newline at end of file +} diff --git a/src/SqlBulkSyncFunction/Models/Schema/Column.cs b/src/SqlBulkSyncFunction/Models/Schema/Column.cs index 4f481ae..b8781d5 100644 --- a/src/SqlBulkSyncFunction/Models/Schema/Column.cs +++ b/src/SqlBulkSyncFunction/Models/Schema/Column.cs @@ -1,6 +1,7 @@ namespace SqlBulkSyncFunction.Models.Schema { - public record Column { + public record Column + { public string Name { get; set; } public string QuoteName { get; set; } public string Type { get; set; } diff --git a/src/SqlBulkSyncFunction/Models/Schema/TableVersion.cs b/src/SqlBulkSyncFunction/Models/Schema/TableVersion.cs index dc5fa6c..46b5c88 100644 --- a/src/SqlBulkSyncFunction/Models/Schema/TableVersion.cs +++ b/src/SqlBulkSyncFunction/Models/Schema/TableVersion.cs @@ -9,4 +9,4 @@ public record TableVersion public long MinValidVersion { get; set; } public DateTimeOffset Queried { get; set; } }; -} \ No newline at end of file +} diff --git a/src/SqlBulkSyncFunction/Program.cs b/src/SqlBulkSyncFunction/Program.cs index 064f2b9..a526d68 100644 --- a/src/SqlBulkSyncFunction/Program.cs +++ b/src/SqlBulkSyncFunction/Program.cs @@ -1,13 +1,14 @@ using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using SqlBulkSyncFunction.Models.Job; using SqlBulkSyncFunction.Services; await new HostBuilder() .ConfigureFunctionsWorkerDefaults() .ConfigureServices( - configure => { + configure => + { configure.AddOptions() .Configure( (settings, configuration) => configuration.GetSection(nameof(SyncJobsConfig)).Bind(settings)); @@ -19,4 +20,4 @@ .AddSingleton(); }) .Build() - .RunAsync(); \ No newline at end of file + .RunAsync(); diff --git a/src/SqlBulkSyncFunction/Services/AzureSqlTokenService.cs b/src/SqlBulkSyncFunction/Services/AzureSqlTokenService.cs index 0bdd46e..bef8372 100644 --- a/src/SqlBulkSyncFunction/Services/AzureSqlTokenService.cs +++ b/src/SqlBulkSyncFunction/Services/AzureSqlTokenService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading.Tasks; using Azure.Core; using Azure.Identity; diff --git a/src/SqlBulkSyncFunction/Services/IAzureSqlTokenService.cs b/src/SqlBulkSyncFunction/Services/IAzureSqlTokenService.cs index 0a0dd0d..f1b7995 100644 --- a/src/SqlBulkSyncFunction/Services/IAzureSqlTokenService.cs +++ b/src/SqlBulkSyncFunction/Services/IAzureSqlTokenService.cs @@ -1,9 +1,9 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; namespace SqlBulkSyncFunction.Services { public interface IAzureSqlTokenService { - Task GetAccessToken(string tenantId); + public Task GetAccessToken(string tenantId); } -} \ No newline at end of file +} diff --git a/src/SqlBulkSyncFunction/Services/IProcessSyncJobService.cs b/src/SqlBulkSyncFunction/Services/IProcessSyncJobService.cs index 9fac908..24c0d58 100644 --- a/src/SqlBulkSyncFunction/Services/IProcessSyncJobService.cs +++ b/src/SqlBulkSyncFunction/Services/IProcessSyncJobService.cs @@ -1,10 +1,10 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using SqlBulkSyncFunction.Models.Job; namespace SqlBulkSyncFunction.Services { public interface IProcessSyncJobService { - Task ProcessSyncJob(SyncJob syncJob, bool globalChangeTracking); + public Task ProcessSyncJob(SyncJob syncJob, bool globalChangeTracking); } -} \ No newline at end of file +} diff --git a/src/SqlBulkSyncFunction/Services/ITokenCacheService.cs b/src/SqlBulkSyncFunction/Services/ITokenCacheService.cs index 883ec19..a595931 100644 --- a/src/SqlBulkSyncFunction/Services/ITokenCacheService.cs +++ b/src/SqlBulkSyncFunction/Services/ITokenCacheService.cs @@ -1,4 +1,4 @@ -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading.Tasks; using SqlBulkSyncFunction.Models.Job; @@ -7,7 +7,7 @@ namespace SqlBulkSyncFunction.Services { public interface ITokenCacheService { - Task> GetTokenCache(IEnumerable jobs); - Task> GetTokenCache(SyncJobConfig job); + public Task> GetTokenCache(IEnumerable jobs); + public Task> GetTokenCache(SyncJobConfig job); } -} \ No newline at end of file +} diff --git a/src/SqlBulkSyncFunction/Services/ProcessSyncJobService.cs b/src/SqlBulkSyncFunction/Services/ProcessSyncJobService.cs index 6a3bff7..5877e69 100644 --- a/src/SqlBulkSyncFunction/Services/ProcessSyncJobService.cs +++ b/src/SqlBulkSyncFunction/Services/ProcessSyncJobService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -20,8 +20,8 @@ public async Task ProcessSyncJob(SyncJob syncJob, bool globalChangeTracking) using (Logger.BeginScope(scope)) { await using SqlConnection - sourceConn = new(syncJob.SourceDbConnection) {AccessToken = syncJob.SourceDbAccessToken}, - targetConn = new(syncJob.TargetDbConnection) {AccessToken = syncJob.TargetDbAccessToken}; + sourceConn = new(syncJob.SourceDbConnection) { AccessToken = syncJob.SourceDbAccessToken }, + targetConn = new(syncJob.TargetDbConnection) { AccessToken = syncJob.TargetDbAccessToken }; using IDisposable from = Logger.BeginScope($"{sourceConn.DataSource}.{sourceConn.Database}"), to = Logger.BeginScope($"{targetConn.DataSource}.{targetConn.Database}"); @@ -76,7 +76,7 @@ public async Task ProcessSyncJob(SyncJob syncJob, bool globalChangeTracking) using (Logger.BeginScope(tableSchema.Scope)) { Logger.LogInformation("{Scope} Begin {TableSchemaScope}", scope, tableSchema.Scope); - + if (syncJob.Seed) { SeedTable(targetConn, tableSchema, sourceConn, scope); diff --git a/src/SqlBulkSyncFunction/Services/TokenCacheService.cs b/src/SqlBulkSyncFunction/Services/TokenCacheService.cs index a51ac70..2a781cb 100644 --- a/src/SqlBulkSyncFunction/Services/TokenCacheService.cs +++ b/src/SqlBulkSyncFunction/Services/TokenCacheService.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; @@ -42,14 +42,10 @@ await dataSources .Distinct(StringComparer.OrdinalIgnoreCase) .ParallelForEachAsync( maxDegreeOfParallelism: 4, - asyncItemAction: async tenant => - { - tokenCache.TryAdd( + asyncItemAction: async tenant => tokenCache.TryAdd( tenant, await AzureSqlTokenService.GetAccessToken(tenant) - ); - } - ); + )); return tokenCache; } diff --git a/src/SqlBulkSyncFunction/SqlBulkSyncFunction.csproj b/src/SqlBulkSyncFunction/SqlBulkSyncFunction.csproj index 437e340..7f07ff5 100644 --- a/src/SqlBulkSyncFunction/SqlBulkSyncFunction.csproj +++ b/src/SqlBulkSyncFunction/SqlBulkSyncFunction.csproj @@ -1,21 +1,21 @@ - net9.0 + net10.0 latest v4 Exe <_FunctionsSkipCleanOutput>true - - - - - - - - - + + + + + + + + + @@ -29,6 +29,7 @@ true false + false contentFiles Apache-2.0 WCOM128x128_squares.png @@ -43,11 +44,7 @@ A lightweight, performant non-intrusive SQL Server data sync service. - - all - runtime; build; native; contentfiles; analyzers - - +