├── .gitignore ├── 01-DatabaseForTheModernDeveloper └── README.md ├── 02-AzureSQLKickstart ├── 01-CreateAzureSQLDatabase.sh ├── 02-UsingSampleDatabases.sh ├── README.md ├── RestoreFromBacPac │ ├── README.md │ ├── RestoreBacPac.bat │ ├── RestoreBacPac.ps1 │ └── RestoreBacPac.sh └── RestoreFromBackup │ └── RestoreFromBackup.sql ├── 03-ConnectingAndQueryingAzureSQL ├── .vscode │ └── settings.json ├── 1-GettingStarted-CSharp │ ├── .editorconfig │ ├── .vscode │ │ ├── launch.json │ │ ├── settings.json │ │ └── tasks.json │ ├── Controllers │ │ └── OrderController.cs │ ├── Order.cs │ ├── OrderRepository.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Startup.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ └── gettingstarted.csproj ├── 10-Dapper-CSharp │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── Program.cs │ └── dappercsharp.csproj ├── 2-GettingStarted-Java │ ├── .vscode │ │ └── settings.json │ └── AzureSqlSample │ │ ├── .classpath │ │ ├── .project │ │ ├── .settings │ │ ├── org.eclipse.jdt.apt.core.prefs │ │ ├── org.eclipse.jdt.core.prefs │ │ └── org.eclipse.m2e.core.prefs │ │ ├── pom.xml │ │ └── src │ │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── azuresqlsamples │ │ │ └── App.java │ │ └── test │ │ └── java │ │ └── com │ │ └── azuresqlsamples │ │ └── AppTest.java ├── 3-GettingStarted-Python-Flask │ ├── app.py │ └── requirements.txt ├── 4-GettingStarted-Node │ ├── connect.js │ ├── package-lock.json │ ├── package.json │ └── server.js ├── 5-RetryLogic-CSharp │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── Program.cs │ └── retrylogic.csproj ├── 6-RetryLogic-TFH │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── Program.cs │ ├── app.json │ └── retrylogictfh.csproj ├── 7-RetryLogic-Python │ ├── requirements.txt │ └── retry.py ├── 8-SQLAlchemy-Python │ ├── main.py │ └── requirements.txt ├── 9-EFCore-CSharp │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── Program.cs │ └── efcore.csproj └── README.md ├── 04-DevelopingWithAzureSQL-Foundations ├── 01-select.sql ├── 02-select-with-join.sql ├── 03-subqueries.sql ├── 04-cte.sql ├── 05-union.sql ├── 06-dml-samples.sql ├── 07-output-clause.sql ├── 08-identity-and-sequences.sql ├── 09-top-offset-fetch.sql ├── 10-group-by.sql ├── 11-grouping-sets.sql ├── 12-windowing-functions.sql ├── BulkCopy │ └── DotNetBulkCopy │ │ ├── .env.template │ │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ │ ├── DotNetBulkCopy.csproj │ │ ├── Program.cs │ │ ├── README.md │ │ └── sql │ │ └── setup.sql └── README.md ├── 05-DevelopingWithAzureSQL-Advanced ├── 01-views.sql ├── 02-functions.sql ├── 03-stored-procedures.sql ├── 04-triggers.sql ├── 05-tvp.sql ├── 06-csv.sql ├── 07-json.sql ├── 08-change-tracking.sql ├── 09-rls.sql ├── 10-dynamic-data-masking.sql ├── README.md └── TableValuedParameters │ └── DotNetTVP │ ├── .env.template │ ├── .vscode │ ├── launch.json │ └── tasks.json │ ├── DotNetTVP.csproj │ └── Program.cs ├── 06-PracticalUseOfTableAndIndexes ├── 01-Create table.sql ├── 02-Change column collation.sql ├── 03-Add computed column.sql ├── 04-Query spatial column.sql ├── 05-Add foreign key constriants.sql ├── 06-Secure your table.sql ├── 07-Query that can be optimized with index.sql ├── 08-Create index.sql ├── 09-Rebuild index.sql ├── README.md └── todo.txt ├── 07-ScalabilityConsistencyAndPerformances ├── 1-PythonTransactions │ ├── .gitignore │ ├── .vscode │ │ ├── launch.json │ │ └── settings.json │ └── app.py ├── 2-TSQL-Transactions │ └── transactions.sql ├── 3-DotNET-Savepoints │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── Program.cs │ └── savepoints.csproj ├── 4-DistributedTransactions │ ├── Program.cs │ ├── disttran.csproj │ └── setup.sql ├── 5-NativeCompilation │ └── nativecompilation.sql └── README.md ├── 08-MultiModelCapabilities ├── 01-Get JSON data.cs ├── 02-Storing JSON documents.sql ├── 03-Query JSON data.sql ├── 04-Import JSON data.sql ├── 05-Create graph table.sql ├── 06-Import graph data.sql ├── 07-Query graph data.sql ├── 08-Graph and spatial data.sql ├── 09-Query spatial data.sql ├── 10-Spatial index.sql ├── 11-Spatial SRID.sql ├── 12-XML query.sql ├── 12-XQuery.sql ├── 13-XML index.sql ├── 14-Key-value cache.sql ├── 15-Query text.sql ├── 16-Full-text index.sql ├── 17-Full-text search.sql ├── 18-Query JSON text using full-text search.sql └── README.md ├── 09-MoreThanTables ├── 00-Setup script.sql ├── 01-Analytic query.sql ├── 02-Query columnstore structure.sql ├── 03-Create table with clustered columnstore index.sql ├── 04-Add clustered columnstore index on table.sql ├── 05-Reorganize columnstore index.sql ├── 06-Create table with nonclustered columnstore index.sql ├── 07-Create memory-optimized table.sql ├── 08-Create non-durable memory-optimized table.sql ├── 09-Create memory-optimized hash table.sql ├── 10-Create memory-optimized columnstore table.sql ├── 11-Memory-optimized snapshot hint.sql ├── 12-Natively compiled procedure template.sql ├── 13-Natively compiled JSON function.sql ├── 14-Temporal query on system versioned table.sql ├── 15-Error correction in system versioned table.sql ├── 16-Optimize temporal table.sql ├── 17-Set retention period on temporal table.sql └── README.md ├── 10-MonitoringAndDebugging ├── 1-DMV │ └── dmv.sql ├── 2-AppInsightSpring │ ├── .vscode │ │ └── settings.json │ └── demo │ │ ├── .gitignore │ │ ├── .mvn │ │ └── wrapper │ │ │ ├── MavenWrapperDownloader.java │ │ │ ├── maven-wrapper.jar │ │ │ └── maven-wrapper.properties │ │ ├── mvnw │ │ ├── pom.xml │ │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── practicalsqldb │ │ │ │ └── demo │ │ │ │ ├── DemoApplication.java │ │ │ │ ├── controllers │ │ │ │ └── CustomerController.java │ │ │ │ ├── models │ │ │ │ └── Customer.java │ │ │ │ └── repositories │ │ │ │ └── CustomerRepository.java │ │ └── resources │ │ │ └── application.properties │ │ └── test │ │ └── java │ │ └── com │ │ └── practicalsqldb │ │ └── demo │ │ └── DemoApplicationTests.java ├── 3-AppInsightDotnet │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── Contexts │ │ └── WWImportersContext.cs │ ├── Controllers │ │ └── CustomerController.cs │ ├── Models │ │ └── Customer.cs │ ├── Program.cs │ ├── Repositories │ │ └── CustomerRepository.cs │ ├── Startup.cs │ ├── appinsight.csproj │ ├── applicationInsights.config │ ├── appsettings.Development.json │ └── appsettings.json └── README.md ├── 11-DevOpsWithAzureSQL ├── .github │ └── workflows │ │ └── main.yml ├── .gitignore ├── README.md ├── ch11.sln ├── db-deploy │ ├── .env.template │ ├── Program.cs │ ├── db-deploy.csproj │ └── sql │ │ ├── 01-change-tracking-setup.sql │ │ ├── 02-stored-procedure.sql │ │ ├── 03-data.sql │ │ └── 04-fix-stored-procedure.sql.fix └── db-test │ ├── .env.template │ ├── .vscode │ ├── launch.json │ └── tasks.json │ ├── Tests.cs │ └── db-test.csproj ├── LICENSE ├── README.md └── practical-azure-sql-database-for-modern-developers-small.jpg /01-DatabaseForTheModernDeveloper/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 1 - A database for the modern developer 2 | 3 | Samples for to Chapter 1 of [Practical Azure SQL Database for Modern Developers](https://www.apress.com/gp/book/9781484263693) book. 4 | 5 | There are no samples for Chapter 1 -------------------------------------------------------------------------------- /02-AzureSQLKickstart/01-CreateAzureSQLDatabase.sh: -------------------------------------------------------------------------------- 1 | ########################################################################### 2 | # How to deploy Azure SQL Database using the Azure CLI 3 | ########################################################################### 4 | 5 | # You can use either a Linux Shell, WSL or Azure Cloud Shell (in-browser) 6 | # WSL: https://docs.microsoft.com/en-us/windows/wsl/install-win10 7 | # Azure Cloud Shell: https://shell.azure.com/ 8 | # Note - you must have an Azure subscription first 9 | 10 | ########################################################################### 11 | # Step 1: Set up Azure CLI and create a resource group 12 | ########################################################################### 13 | # log in to your Azure account 14 | az login 15 | 16 | # view your available subscriptions 17 | az account list -o table 18 | 19 | # copy the subscription ID you want to use and set it as default 20 | az account set --subscription 21 | 22 | # view regions available to your account, note the "name" of the region you want to use (for example, "westus") 23 | az account list-locations -o table 24 | 25 | # create a resource group (specifying region and name) 26 | az group create --location --name 27 | 28 | ########################################################################### 29 | # Step 2: Create an Azure SQL Database 30 | ########################################################################### 31 | # create a logical server 32 | az sql server create \ 33 | --admin-password \ 34 | --admin-user \ 35 | --name \ 36 | --resource-group \ 37 | --location 38 | 39 | # create an Azure SQL Database with the default configuration 40 | az sql db create \ 41 | --name \ 42 | --resource-group \ 43 | --server 44 | 45 | ########################################################################### 46 | # Step 3: Create an Azure SQL Database with a sample 47 | ########################################################################### 48 | # create an Azure SQL Database with the default configuration 49 | # and the AdventureWorksLT sample database 50 | az sql db create \ 51 | --name \ 52 | --resource-group \ 53 | --server \ 54 | --sample-name AdventureWorksLT 55 | 56 | ########################################################################### 57 | # Step 4: Create firewall rules 58 | ########################################################################### 59 | # get your external ip, for example: 60 | curl https://api.myip.com -s 61 | 62 | # create firewall rules to allow connections from your ip 63 | az sql server firewall-rule create \ 64 | --name \ 65 | --resource-group \ 66 | --server \ 67 | --start-ip-address \ 68 | --end-ip-address 69 | 70 | ########################################################################### 71 | # Step 5: (extra) Create Azure SQL managed instance 72 | ########################################################################### 73 | # Note: you'll first need to create a virtual network for Managed instance 74 | # Recommend using the guidance in the documentation 75 | # https://docs.microsoft.com/en-us/azure/sql-database/sql-database-managed-instance-create-vnet-subnet 76 | 77 | # create an azure sql managed instance with default parameters 78 | az sql mi create \ 79 | -g \ 80 | -n \ 81 | -l \ 82 | -i -u -p \ 83 | --subnet /subscriptions//resourceGroups//providers/Microsoft.Network/virtualNetworks//subnets/ 84 | 85 | # create a new managed database within an existing azure sql managed instance 86 | az sql midb create \ 87 | -g \ 88 | --mi \ 89 | -n 90 | 91 | -------------------------------------------------------------------------------- /02-AzureSQLKickstart/02-UsingSampleDatabases.sh: -------------------------------------------------------------------------------- 1 | ########################################################################### 2 | # Various ways of using samples in Azure SQL 3 | ########################################################################### 4 | 5 | # You can use either Linux Shell, WSL or Azure Cloud Shell (in-browser) 6 | # WSL: https://docs.microsoft.com/en-us/windows/wsl/install-win10 7 | # Azure Cloud Shell: https://shell.azure.com/ 8 | # Note - you must have an Azure subscription first 9 | # You can download the sample databases from here: 10 | # https://github.com/Microsoft/sql-server-samples/releases/tag/wide-world-importers-v1.0 11 | 12 | ########################################################################### 13 | # Option 1: Deploy Azure SQL Database with AdventureWorksLT 14 | ########################################################################### 15 | # create an Azure SQL Database with the default configuration 16 | # and the AdventureWorksLT sample database 17 | az sql db create \ 18 | --name \ 19 | --resource-group \ 20 | --server \ 21 | --sample-name AdventureWorksLT 22 | 23 | ########################################################################### 24 | # Option 2: Import a bacpac into Azure SQL Database 25 | ########################################################################### 26 | # Note - you must already have a database as well as a 27 | # bacpac file in an Azure Storage Account 28 | az sql db import \ 29 | -g \ 30 | -s \ 31 | -n \ 32 | -u \ 33 | -p \ 34 | --storage-key-type StorageAccessKey \ 35 | --storage-key \ 36 | --storage-uri https://.blob.core.windows.net/ 37 | 38 | # Note: A more complete sample is available in "RestoreFromBacPac" folder 39 | 40 | ########################################################################### 41 | # Option 3: Restoring a backup file from URL to Azure SQL Managed Instance 42 | ########################################################################### 43 | # Note - you must already have an instance deployed 44 | # You must already have a .bak file in an Azure Storage Account 45 | # Details in "RestoreFromBackup" folder -------------------------------------------------------------------------------- /02-AzureSQLKickstart/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 2 - Azure SQL Kickstart 2 | 3 | Samples for to Chapter 2 of [Practical Azure SQL Database for Modern Developers](https://www.apress.com/gp/book/9781484263693) book: 4 | 5 | - Creating an Azure SQL database 6 | - Creating a sample Azure SQL database 7 | - Restoring an Azure SQL database from .bacpac 8 | - Restoring an Azure SQL Managed Instance database from .bak 9 | 10 | -------------------------------------------------------------------------------- /02-AzureSQLKickstart/RestoreFromBacPac/RestoreBacPac.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | 4 | @rem Make sure you have the latest DacFx version from 5 | @rem https://docs.microsoft.com/en-us/sql/tools/sqlpackage?view=sql-server-ver15#deployreport-parameters-and-properties 6 | 7 | @rem Update the following variables to set the correct Azure SQL tier 8 | @rem depending on the sample you want to import. 9 | @rem WideWorldImporters-Full requires Premium or BusinessCritical, while 10 | @rem WideWorldImporters-Standard requires Standard or GeneralPurpose or Hyperscale 11 | set azure_sql_tier="Hyperscale" 12 | set azure_sql_slo="HS_Gen5_2" 13 | 14 | @rem Update the following three variables 15 | @rem to match your Azure environment and file position 16 | set azure_sql_database=WideWorldImportersStandard 17 | set sqlpackage_path="C:\Program Files\Microsoft SQL Server\150\DAC\bin\SqlPackage.exe" 18 | set bacpac_path="C:\Work\_dbs\WideWorldImporters-Standard.bacpac" 19 | 20 | @rem Validate input parameters 21 | set azure_sql_server=%1 22 | set azure_sql_login=%2 23 | set azure_sql_password=%3 24 | 25 | if "%azure_sql_server%"=="" ( 26 | goto USAGE 27 | ) 28 | 29 | if "%azure_sql_login%"=="" ( 30 | goto USAGE 31 | ) 32 | 33 | if "%azure_sql_password%"=="" ( 34 | goto USAGE 35 | ) 36 | 37 | echo Running... 38 | %sqlpackage_path% /a:import /tcs:"Data Source=%azure_sql_server%.database.windows.net;Initial Catalog=%azure_sql_database%;User Id=%azure_sql_login%;Password=%azure_sql_password%" /sf:%bacpac_path% /p:DatabaseEdition=%azure_sql_tier% /p:DatabaseServiceObjective=%azure_sql_slo% 39 | goto END 40 | 41 | :USAGE 42 | echo Usage: 43 | echo %~nx0 azure_sql_server azure-sql-login azure-sql-password 44 | echo Example: 45 | echo restore-bacpac.bat sql4devs sql4devAdmin sql4devPassw0rd! 46 | echo Note: 47 | echo Make sure to edit %~nx0 so that variables point to the correct Azure SQL and .bacpac file. 48 | exit /B 1 49 | 50 | :END 51 | endlocal 52 | @echo on 53 | @exit /b 0 54 | -------------------------------------------------------------------------------- /02-AzureSQLKickstart/RestoreFromBacPac/RestoreBacPac.ps1: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Update the following variable 4 | # to download the Full or Standart database sample 5 | # See https://github.com/Microsoft/sql-server-samples/releases/tag/wide-world-importers-v1.0 6 | # to have a list of available sample bacpacs 7 | $bacpacFile="WideWorldImporters-Full.bacpac" 8 | 9 | # Update the following variables to set the correct Azure SQL tier and the sample you want to import. 10 | # WideWorldImporters-Full requires Premium or BusinessCritical, while 11 | # WideWorldImporters-Standard requires Standard or GeneralPurpose or Hyperscale 12 | $sqlLogin="" 13 | $sqlPassword="" 14 | $sqlResourceGroup="" 15 | $sqlServer="" 16 | $sqlDatabase="WWIFULLRestoreTest" 17 | $sqlEdition="BusinessCritical" 18 | $sqlSLO="BC_Gen5_2" 19 | 20 | # Set the location to be in the same Region where your Azure SQL is 21 | $storageLocation="WestUS2" 22 | 23 | # Generate temporary storage account and resource group 24 | $storageGroup=-join ((97..122) | Get-Random -Count 12 | % {[char]$_}) 25 | $storageAccount=-join ((97..122) | Get-Random -Count 12 | % {[char]$_}) 26 | 27 | # Store local path as DownloadFile needs it 28 | $localFolder=Convert-Path . 29 | $localBacpacFile=(Join-Path $localFolder $bacpacFile) 30 | 31 | Write-Output "Downloading $bacpacFile..." 32 | (New-Object System.Net.WebClient).DownloadFile( 33 | "https://github.com/Microsoft/sql-server-samples/releases/download/wide-world-importers-v1.0/$bacpacFile", 34 | "$localBacpacFile" 35 | ) 36 | 37 | Write-Output "Creating temporary Resource Group '$storageGroup'..." 38 | $sgo = New-AzResourceGroup -Name $storageGroup -Location $storageLocation 39 | 40 | Write-Output "Creating temporary Storage Account '$storageAccount'..." 41 | $sao = New-AzStorageAccount -ResourceGroupName $storageGroup -Name $storageAccount -SkuName Standard_LRS -Location $storageLocation 42 | $ctx = $sao.Context 43 | 44 | Write-Output "Creating Container..." 45 | New-AzStorageContainer -Name "bacpac" -Context $ctx -Permission Blob 46 | 47 | Write-Output "Uploading bacpac..." 48 | Set-AzStorageBlobContent -File "$localBacpacFile" -Container "bacpac" -Blob $bacpacFile -Context $ctx 49 | 50 | Write-Output "Getting Storage Access Key..." 51 | $storageKey = $(Get-AzStorageAccountKey -ResourceGroupName $storageGroup -StorageAccountName $storageAccount).Value[0] 52 | 53 | Write-Output "Creating Azure SQL database..." 54 | New-AzSqlDatabase -ResourceGroupName $sqlResourceGroup -ServerName $sqlServer -DatabaseName $sqlDatabase -RequestedServiceObjectiveName $sqlSLO 55 | 56 | Write-Output "Importing bacpac..." 57 | $importRequest = New-AzSqlDatabaseImport ` 58 | -ResourceGroupName "$sqlResourceGroup" ` 59 | -ServerName "$sqlServer" ` 60 | -DatabaseName "$sqlDatabase" ` 61 | -DatabaseMaxSizeBytes "$(10 * 1024 * 1024 * 1024)" ` 62 | -StorageKeyType "StorageAccessKey" ` 63 | -StorageKey "$storageKey" ` 64 | -StorageUri "https://$storageAccount.blob.core.windows.net/bacpac/$bacpacFile" ` 65 | -AdministratorLogin "$sqlLogin" ` 66 | -AdministratorLoginPassword $(ConvertTo-SecureString -String "$sqlPassword" -AsPlainText -Force) ` 67 | -Edition $sqlEdition ` 68 | -ServiceObjectiveName $sqlSLO 69 | 70 | do { 71 | $importStatus = Get-AzSqlDatabaseImportExportStatus -OperationStatusLink $importRequest.OperationStatusLink 72 | Start-Sleep -s 10 73 | } while ($importStatus.Status -eq "InProgress") 74 | 75 | # Delete temporary resources 76 | Write-Output "Deleting temporary resources..." 77 | Remove-AzResourceGroup -Name $storageGroup -Force -AsJob 78 | 79 | Write-Output "Done." -------------------------------------------------------------------------------- /02-AzureSQLKickstart/RestoreFromBacPac/RestoreBacPac.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Strict mode, fail on any error 4 | set -euo pipefail 5 | 6 | # Update the following variable 7 | # to download the Full or Standart database sample 8 | # See https://github.com/Microsoft/sql-server-samples/releases/tag/wide-world-importers-v1.0 9 | # to have a list of available sample bacpacs 10 | declare bacpacFile="WideWorldImporters-Full.bacpac" 11 | 12 | # Update the following variables to set the correct Azure SQL tier and the sample you want to import. 13 | # WideWorldImporters-Full requires Premium or BusinessCritical, while 14 | # WideWorldImporters-Standard requires Standard or GeneralPurpose or Hyperscale 15 | declare sqlLogin="" 16 | declare sqlPassword="" 17 | declare sqlResourceGroup="" 18 | declare sqlServer="" 19 | declare sqlDatabase="WWIFULLRestoreTest" 20 | declare sqlServiceObjective="BC_Gen5_2" 21 | 22 | # Set the location to be in the same Region where your Azure SQL is 23 | declare storageLocation="WestUS2" 24 | 25 | # Generate temporary storage account and resource group 26 | declare storageGroup=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 12 | head -n 1) 27 | declare storageAccount=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 12 | head -n 1) 28 | 29 | # Cleanup temporary resources on catch 30 | set -e 31 | trap 'catch $? $LINENO' EXIT 32 | 33 | catch() { 34 | if [ "$1" != "0" ]; then 35 | echo "Removing temporary resources..." 36 | az group delete -n $storageGroup --no-wait --yes 37 | fi 38 | } 39 | 40 | echo "Downloading $bacpacFile..." 41 | rm $bacpacFile -f 42 | curl https://github.com/Microsoft/sql-server-samples/releases/download/wide-world-importers-v1.0/$bacpacFile -o ./$bacpacFile -L 43 | 44 | echo "Creating temporary Resource Group '$storageGroup'..." 45 | az group create -n $storageGroup -l $storageLocation 46 | 47 | echo "Creating temporary Storage Account '$storageAccount'..." 48 | az storage account create -g $storageGroup -n $storageAccount --sku Standard_LRS 49 | 50 | echo "Getting created Storage Key..." 51 | declare storageKey=$(az storage account keys list -g $storageGroup -n $storageAccount --query "[0].value" -o tsv) 52 | 53 | # export account account and key to 54 | export AZURE_STORAGE_KEY=$storageKey 55 | export AZURE_STORAGE_ACCOUNT=$storageAccount 56 | 57 | echo "Creating Container..." 58 | az storage container create -n "bacpac" 59 | 60 | echo "Uploading bacpac..." 61 | az storage blob upload -f ./$bacpacFile -c "bacpac" -n $bacpacFile 62 | 63 | echo "Creating Azure SQL database..." 64 | az sql db create -g $sqlResourceGroup -s $sqlServer -n $sqlDatabase --service-objective $sqlServiceObjective 65 | 66 | echo "Importing bacpac..." 67 | az sql db import\ 68 | -g $sqlResourceGroup \ 69 | -s $sqlServer \ 70 | -n $sqlDatabase \ 71 | -u $sqlLogin \ 72 | -p $sqlPassword \ 73 | --storage-key-type StorageAccessKey \ 74 | --storage-key $storageKey \ 75 | --storage-uri https://$storageAccount.blob.core.windows.net/bacpac/$bacpacFile 76 | 77 | 78 | # Delete temporary resources 79 | echo "Deleting temporary resources..." 80 | az group delete -n $storageGroup --no-wait --yes 81 | 82 | echo "Done." -------------------------------------------------------------------------------- /02-AzureSQLKickstart/RestoreFromBackup/RestoreFromBackup.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Sample Environment: 3 | 4 | Backup File is 'WideWorldImporters-Standard.bak' and is available in 'mybakstore1.blob.core.windows.net/db-backups' Azure Blob Store. 5 | 6 | Generate a SAS token on the db-backups folder with at least READ and LIST permission (for example using Azure Storage Explorer) 7 | */ 8 | 9 | -- Create a credential to allow Azure SQL MI to access the backup 10 | 11 | CREATE CREDENTIAL [https://mybakstore1.blob.core.windows.net/db-backups] 12 | WITH 13 | IDENTITY = 'SHARED ACCESS SIGNATURE', 14 | SECRET = '' -- REMOVE THE LEADING '?' if present in SAS token string 15 | 16 | -- View the files in a backup 17 | RESTORE FILELISTONLY FROM URL = 'https://mybakstore1.blob.core.windows.net/db-backups/WideWorldImporters-Standard.bak' 18 | 19 | -- Restore the database from a URL 20 | RESTORE DATABASE [WideWorldImporters] 21 | FROM URL = 'https://mybakstore1.blob.core.windows.net/db-backups/WideWorldImporters-Standard.bak' 22 | -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.ignoreLimitWarning": true 3 | } -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/1-GettingStarted-CSharp/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/bin/Debug/netcoreapp3.1/gettingstarted.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}", 16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console 17 | "console": "internalConsole", 18 | "stopAtEntry": false 19 | }, 20 | { 21 | "name": ".NET Core Attach", 22 | "type": "coreclr", 23 | "request": "attach", 24 | "processId": "${command:pickProcess}" 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/1-GettingStarted-CSharp/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.ignoreLimitWarning": true 3 | } -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/1-GettingStarted-CSharp/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/gettingstarted.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/gettingstarted.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "${workspaceFolder}/gettingstarted.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/1-GettingStarted-CSharp/Controllers/OrderController.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace gettingstarted 7 | { 8 | [ApiController] 9 | [Produces("application/json")] 10 | [Route("[controller]")] 11 | public class OrderController : ControllerBase 12 | { 13 | private readonly ILogger _logger; 14 | private readonly IConfiguration _config; 15 | public OrderController(ILogger logger, IConfiguration config) 16 | { 17 | _logger = logger; 18 | _config = config; 19 | } 20 | 21 | #if OLDWAY 22 | /// 23 | /// get ALL the data from Db then return at once 24 | /// 25 | /// 26 | /// long wait before any response, and likely memory-hog 27 | /// 28 | /// 29 | /// IEnumerable 30 | /// 31 | /// 32 | 33 | [HttpGet] 34 | public IEnumerable Get() 35 | { 36 | OrderRepository or = new OrderRepository(_config); 37 | 38 | return or.GetOrders().Result; 39 | } 40 | 41 | #else 42 | /// 43 | /// stream data rather than batch-up the entire entity 44 | /// 45 | /// 46 | /// NET Core 3 and C# 8.0 adds IAsyncEnumerable (aka async streams) incl ASP.NET 47 | /// 48 | /// 49 | /// IAsyncEnumerable 50 | /// 51 | /// 52 | /// 53 | [HttpGet] 54 | public IAsyncEnumerable GetOrders() 55 | { 56 | OrderRepository or = new OrderRepository(_config); 57 | return or.GetOrdersAsync(); 58 | } 59 | #endif 60 | } 61 | } -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/1-GettingStarted-CSharp/Order.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace gettingstarted 4 | { 5 | public class Order 6 | { 7 | public int OrderID { get; set; } 8 | public DateTime OrderDate { get; set; } 9 | public string CustomerName { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/1-GettingStarted-CSharp/OrderRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Microsoft.Data.SqlClient; 5 | using Microsoft.Extensions.Configuration; 6 | 7 | namespace gettingstarted 8 | { 9 | public class OrderRepository 10 | { 11 | private readonly IConfiguration _config; 12 | 13 | public OrderRepository(IConfiguration config) => 14 | _config = config; 15 | public async Task> GetOrders() 16 | { 17 | List orders = new List(); 18 | using (SqlConnection cnn = 19 | new SqlConnection(_config.GetConnectionString("DefaultConnection"))) 20 | { 21 | SqlCommand cmd = new SqlCommand 22 | (@"SELECT TOP 5 -- always specify order (no generic 'pint of lager' accepted in my pub) 23 | [o].[OrderID], [o].[OrderDate], [c].[CustomerName] 24 | FROM [Sales].[Orders] AS [o] 25 | INNER JOIN [Sales].[Customers] AS [c] 26 | ON [o].[CustomerID] = [c].[CustomerID] 27 | ORDER BY o.OrderDate desc, c.CustomerName", 28 | cnn); 29 | await cnn.OpenAsync(); 30 | SqlDataReader dr = await cmd.ExecuteReaderAsync(); 31 | while (await dr.ReadAsync()) 32 | { 33 | orders.Add(new Order() 34 | { 35 | OrderID = Convert.ToInt32(dr[0]), 36 | OrderDate = Convert.ToDateTime(dr[1]), 37 | CustomerName = Convert.ToString(dr[2]) 38 | }); 39 | } 40 | } 41 | return orders; 42 | } 43 | 44 | public async IAsyncEnumerable GetOrdersAsync() 45 | { 46 | using (SqlConnection cnn = 47 | new SqlConnection(_config.GetConnectionString("DefaultConnection"))) 48 | { 49 | SqlCommand cmd = new SqlCommand 50 | (@"SELECT TOP 5 -- always specify order (no generic 'pint of lager' accepted in my pub) 51 | [o].[OrderID], [o].[OrderDate], [c].[CustomerName] 52 | FROM [Sales].[Orders] AS [o] 53 | INNER JOIN [Sales].[Customers] AS [c] 54 | ON [o].[CustomerID] = [c].[CustomerID] 55 | ORDER BY o.OrderDate desc, c.CustomerName", 56 | cnn); 57 | await cnn.OpenAsync(); 58 | SqlDataReader dr = await cmd.ExecuteReaderAsync(); 59 | while (await dr.ReadAsync()) 60 | { 61 | yield return new Order 62 | { 63 | OrderID = Convert.ToInt32(dr[0]), 64 | OrderDate = Convert.ToDateTime(dr[1]), 65 | CustomerName = Convert.ToString(dr[2]) 66 | }; 67 | } 68 | } 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/1-GettingStarted-CSharp/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace gettingstarted 5 | { 6 | public static class Program 7 | { 8 | public static void Main(string[] args) => 9 | CreateHostBuilder(args).Build().Run(); 10 | 11 | public static IHostBuilder CreateHostBuilder(string[] args) => 12 | Host.CreateDefaultBuilder(args) 13 | .ConfigureWebHostDefaults(webBuilder => 14 | webBuilder.UseStartup()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/1-GettingStarted-CSharp/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:59161/", 7 | "sslPort": 44336 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "gettingstarted": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/1-GettingStarted-CSharp/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.Hosting; 7 | 8 | [assembly: ApiController] 9 | [assembly: ApiConventionType(typeof(DefaultApiConventions))] 10 | namespace gettingstarted 11 | { 12 | public class Startup 13 | { 14 | public Startup(IConfiguration configuration) => 15 | Configuration = configuration; 16 | 17 | public IConfiguration Configuration { get; } 18 | 19 | // This method gets called by the runtime. Use this method to add services to the container. 20 | public void ConfigureServices(IServiceCollection services) 21 | { 22 | services.AddControllers(); 23 | services.AddSwaggerDocument(); 24 | } 25 | 26 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 27 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 28 | { 29 | if (env.IsDevelopment()) 30 | { 31 | app.UseDeveloperExceptionPage(); 32 | app.UseOpenApi(); 33 | app.UseSwaggerUi3(); 34 | } 35 | 36 | app.UseHttpsRedirection(); 37 | 38 | app.UseRouting(); 39 | 40 | app.UseEndpoints(endpoints => 41 | endpoints.MapControllers()); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/1-GettingStarted-CSharp/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/1-GettingStarted-CSharp/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "Server=DELLTOSH;Initial Catalog=WideWorldImporters;Connect Timeout=10;Integrated Security=True;Persist Security Info=False;Pooling=False;Encrypt=False" 4 | }, 5 | "Logging": { 6 | "LogLevel": { 7 | "Default": "Information", 8 | "Microsoft": "Warning", 9 | "Microsoft.Hosting.Lifetime": "Information" 10 | } 11 | }, 12 | "AllowedHosts": "*" 13 | } 14 | -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/1-GettingStarted-CSharp/gettingstarted.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/10-Dapper-CSharp/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/bin/Debug/netcoreapp3.1/dappercsharp.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}", 16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console 17 | "console": "internalConsole", 18 | "stopAtEntry": false 19 | }, 20 | { 21 | "name": ".NET Core Attach", 22 | "type": "coreclr", 23 | "request": "attach", 24 | "processId": "${command:pickProcess}" 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/10-Dapper-CSharp/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/dappercsharp.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/dappercsharp.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "${workspaceFolder}/dappercsharp.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/10-Dapper-CSharp/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Data.SqlClient; 3 | using Dapper; 4 | 5 | namespace dappercsharp 6 | { 7 | class Program 8 | { 9 | static void Main(string[] args) 10 | { 11 | using (SqlConnection cnn = 12 | new SqlConnection( 13 | "Server=tcp:.database.windows.net,"+ 14 | "1433;Initial Catalog=WideWorldImporters-Full;"+ 15 | "User ID=;Password=;")) 16 | { 17 | var orders = cnn.Query("SELECT TOP 10 OrderID, OrderDate, CustomerID FROM Sales.Orders;"); 18 | foreach (var o in orders) 19 | { 20 | Console.WriteLine("OrderId: {0} - OrderDate: {1} - CustomerId: {2}",o.OrderID,o.OrderDate,o.CustomerID); 21 | 22 | } 23 | } 24 | } 25 | } 26 | class Order 27 | { 28 | public int OrderID {get;set;} 29 | public DateTime OrderDate {get;set;} 30 | public int CustomerID {get;set;} 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/10-Dapper-CSharp/dappercsharp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/2-GettingStarted-Java/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/.classpath": true, 4 | "**/.project": true, 5 | "**/.settings": true, 6 | "**/.factorypath": true 7 | }, 8 | "java.configuration.updateBuildConfiguration": "automatic" 9 | } -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/2-GettingStarted-Java/AzureSqlSample/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 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 | -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/2-GettingStarted-Java/AzureSqlSample/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | AzureSqlSample 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.m2e.core.maven2Builder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.m2e.core.maven2Nature 22 | 23 | 24 | -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/2-GettingStarted-Java/AzureSqlSample/.settings/org.eclipse.jdt.apt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.apt.aptEnabled=false 3 | -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/2-GettingStarted-Java/AzureSqlSample/.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 3 | org.eclipse.jdt.core.compiler.compliance=11 4 | org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled 5 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning 6 | org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore 7 | org.eclipse.jdt.core.compiler.processAnnotations=disabled 8 | org.eclipse.jdt.core.compiler.release=disabled 9 | org.eclipse.jdt.core.compiler.source=11 10 | -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/2-GettingStarted-Java/AzureSqlSample/.settings/org.eclipse.m2e.core.prefs: -------------------------------------------------------------------------------- 1 | activeProfiles= 2 | eclipse.preferences.version=1 3 | resolveWorkspaceProjects=true 4 | version=1 5 | -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/2-GettingStarted-Java/AzureSqlSample/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | com.azuresqlsamples 5 | AzureSQLSample 6 | jar 7 | 1.0.0 8 | AzureSQLSample 9 | http://maven.apache.org 10 | 11 | 12 | junit 13 | junit 14 | 3.8.1 15 | test 16 | 17 | 18 | 19 | com.microsoft.sqlserver 20 | mssql-jdbc 21 | 7.4.1.jre11 22 | 23 | 24 | 25 | 26 | 11 27 | 11 28 | 29 | -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/2-GettingStarted-Java/AzureSqlSample/src/main/java/com/azuresqlsamples/App.java: -------------------------------------------------------------------------------- 1 | package com.azuresqlsamples; 2 | 3 | import java.sql.Connection; 4 | import java.sql.Statement; 5 | import java.sql.ResultSet; 6 | import java.sql.DriverManager; 7 | 8 | public class App { 9 | public static void main(String[] args) { 10 | //Update connection string information 11 | String connectionUrl = 12 | "jdbc:sqlserver://.database.windows.net:1433;" 13 | + "database=WideWorldImporters-Full;user=@;" 14 | + "password=;loginTimeout=30;"; 15 | 16 | try { 17 | try (Connection connection = 18 | DriverManager.getConnection(connectionUrl)) { 19 | 20 | String sql = "SELECT @@VERSION;"; 21 | try (Statement statement = 22 | connection.createStatement(); 23 | ResultSet resultSet = 24 | statement.executeQuery(sql)) { 25 | while (resultSet.next()) { 26 | System.out.println( 27 | resultSet.getString(1)); 28 | } 29 | } 30 | connection.close(); 31 | } 32 | } catch (Exception e) { 33 | System.out.println(); 34 | e.printStackTrace(); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/2-GettingStarted-Java/AzureSqlSample/src/test/java/com/azuresqlsamples/AppTest.java: -------------------------------------------------------------------------------- 1 | package com.azuresqlsamples; 2 | 3 | import junit.framework.Test; 4 | import junit.framework.TestCase; 5 | import junit.framework.TestSuite; 6 | 7 | /** 8 | * Unit test for simple App. 9 | */ 10 | public class AppTest 11 | extends TestCase 12 | { 13 | /** 14 | * Create the test case 15 | * 16 | * @param testName name of the test case 17 | */ 18 | public AppTest( String testName ) 19 | { 20 | super( testName ); 21 | } 22 | 23 | /** 24 | * @return the suite of tests being tested 25 | */ 26 | public static Test suite() 27 | { 28 | return new TestSuite( AppTest.class ); 29 | } 30 | 31 | /** 32 | * Rigourous Test :-) 33 | */ 34 | public void testApp() 35 | { 36 | assertTrue( true ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/3-GettingStarted-Python-Flask/app.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | from flask import Flask 4 | import json 5 | import pyodbc 6 | import collections 7 | 8 | from flask import Flask 9 | app = Flask(__name__) 10 | 11 | conn = pyodbc.connect(os.environ['CONNSTR']) 12 | 13 | @app.route('/') 14 | def hello(): 15 | return "Hello World!" 16 | 17 | @app.route('/order') 18 | def getorders(): 19 | 20 | cursor = conn.cursor() 21 | 22 | tsql = "SELECT TOP 5 " \ 23 | " [o].[OrderID], [o].[OrderDate]," \ 24 | " [c].[CustomerName]" \ 25 | " FROM [Sales].[Orders] AS [o]" \ 26 | " INNER JOIN [Sales].[Customers] AS [c]" \ 27 | " ON [o].[CustomerID] = [c].[CustomerID]" 28 | 29 | rows = cursor.execute(tsql).fetchall() 30 | 31 | order_list = [] 32 | for row in rows: 33 | d = collections.OrderedDict() 34 | d['orderID'] = row.OrderID 35 | d['orderDate'] = str(row.OrderDate) 36 | d['customerName'] = row.CustomerName 37 | order_list.append(d) 38 | 39 | return json.dumps(order_list) 40 | 41 | if __name__ == '__main__': 42 | app.run() 43 | -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/3-GettingStarted-Python-Flask/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask 2 | pyodbc 3 | python-dotenv -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/4-GettingStarted-Node/connect.js: -------------------------------------------------------------------------------- 1 | exports.getorders = function(callback) { 2 | 3 | const { Connection, Request } = require("tedious"); 4 | 5 | const config = { 6 | server: ".database.windows.net", 7 | options: { 8 | port: 1433, 9 | database: "WideWorldImporters-Full", 10 | encrypt: true, 11 | trustServerCertificate: false, 12 | validateBulkLoadParameters: true}, 13 | authentication: { 14 | type: "default", 15 | options: { 16 | userName: "", 17 | password: "", 18 | } 19 | } 20 | }; 21 | 22 | const connection = new Connection(config); 23 | connection.connect() 24 | 25 | connection.on("connect", err => { 26 | if (err) { 27 | console.error(err); 28 | } else { 29 | getOrders(function(res) { 30 | return callback(res); 31 | }); 32 | } 33 | }); 34 | 35 | function getOrders(callback) { 36 | var data = [] 37 | const request = new Request( 38 | `SELECT TOP 5 [o].[OrderID], 39 | [o].[OrderDate], [c].[CustomerName] 40 | FROM [Sales].[Orders] AS [o] 41 | INNER JOIN [Sales].[Customers] AS [c] 42 | ON [o].[CustomerID] = [c].[CustomerID]`, 43 | (err, rowCount) => { 44 | if (err) { 45 | console.error(err.message); 46 | } 47 | else { 48 | //console.log(`${rowCount} row(s) returned`); 49 | //res.send({ status: 200, data: data, message: "OK"}) 50 | //console.log (data); 51 | return callback(data); 52 | } 53 | } 54 | ); 55 | request.on("row", function(row) { 56 | data.push({ 57 | orderid: row[0].value, 58 | orderdate: row[1].value, 59 | customername: row[2].value 60 | }) 61 | }); 62 | connection.execSql(request); 63 | } 64 | }; -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/4-GettingStarted-Node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "4-GettingStarted-Node", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "async": "^3.2.0", 14 | "express": "^4.17.1", 15 | "tedious": "^9.2.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/4-GettingStarted-Node/server.js: -------------------------------------------------------------------------------- 1 | var express = require("express"); 2 | var app = express(); 3 | 4 | var db = require('./connect'); 5 | 6 | app.get("/customer", (req, res, next) => { 7 | db.getorders(function(response) { 8 | res.send(response); 9 | }); 10 | }); 11 | 12 | app.listen(9999, () => { 13 | console.log("Server running on port 9999"); 14 | }); 15 | -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/5-RetryLogic-CSharp/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | "program": "${workspaceFolder}/bin/Debug/netcoreapp3.1/retrylogic.dll", 13 | "args": [], 14 | "cwd": "${workspaceFolder}", 15 | "console": "internalConsole", 16 | "stopAtEntry": false 17 | }, 18 | { 19 | "name": ".NET Core Attach", 20 | "type": "coreclr", 21 | "request": "attach", 22 | "processId": "${command:pickProcess}" 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/5-RetryLogic-CSharp/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/retrylogic.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/retrylogic.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "${workspaceFolder}/retrylogic.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/5-RetryLogic-CSharp/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using System.Collections.Generic; 5 | using Microsoft.Data.SqlClient; 6 | 7 | namespace retrylogic 8 | { 9 | class Program 10 | { 11 | static async Task Main(string[] args) 12 | { 13 | bool success = false; 14 | int retryCount = 3; 15 | int retryInterval = 8; 16 | 17 | List RetriableCodes = 18 | new List { 4060, 40197, 40501, 40613, 19 | 49918, 49919, 49920, 11001,208,18456 }; 20 | 21 | for (int retries = 1; retries <= retryCount; retries++) 22 | { 23 | try 24 | { 25 | if (retries > 1) 26 | { 27 | Thread.Sleep(1000 * retryInterval); 28 | retryInterval = Convert.ToInt32 (retryInterval * 1.5); 29 | } 30 | await MyDatabaseOperation(); 31 | success = true; 32 | break; 33 | } 34 | catch (SqlException se) 35 | { 36 | if (RetriableCodes.Contains (se.Number) == true) 37 | { 38 | Console.WriteLine("\n[{0}] -- Retriable App Exception!!!" + 39 | " -- SQL Error: {1} -- Exception Message: {2} \n\n", 40 | DateTime.Now.ToUniversalTime(), se.Number, se.Message); 41 | continue; 42 | } 43 | else 44 | { 45 | Console.WriteLine("\n[{0}] -- Non-retriable App Exception!!!" + 46 | " -- SQL Error: {1} -- Exception Message: {2} \n\n", 47 | DateTime.Now.ToUniversalTime(), se.Number, se.Message); 48 | success = false; 49 | break; 50 | } 51 | } 52 | catch (Exception e) 53 | { 54 | Console.WriteLine("\n[{0}] -- Non-retriable App "+ 55 | "Exception!!! -- Exception Message: {1} \n\n", 56 | DateTime.Now.ToUniversalTime(), e.Message); 57 | success = false; 58 | break; 59 | } 60 | } 61 | } 62 | static async Task MyDatabaseOperation() 63 | { 64 | using (SqlConnection cnn = 65 | new SqlConnection( 66 | "Server=tcp:.database.windows.net,"+ 67 | "1433;Initial Catalog=WideWorldImporters-Full;"+ 68 | "User ID=;Password=;"+ 69 | "Connect Timeout=10;")) 70 | { 71 | SqlCommand cmd = 72 | new SqlCommand("SELECT * FROM sysobjectz",cnn); 73 | await cnn.OpenAsync(); 74 | 75 | await cmd.ExecuteReaderAsync(); 76 | } 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/5-RetryLogic-CSharp/retrylogic.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | latest 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/6-RetryLogic-TFH/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/bin/Debug/netcoreapp3.1/retrylogictfh.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}", 16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console 17 | "console": "internalConsole", 18 | "stopAtEntry": false 19 | }, 20 | { 21 | "name": ".NET Core Attach", 22 | "type": "coreclr", 23 | "request": "attach", 24 | "processId": "${command:pickProcess}" 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/6-RetryLogic-TFH/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/retrylogictfh.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/retrylogictfh.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "${workspaceFolder}/retrylogictfh.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/6-RetryLogic-TFH/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "retryStrategy": { 3 | "MyFixedStrategy": { 4 | "fastFirstRetry": "false", 5 | "retryCount": 3, 6 | "retryInterval": "00:00:10" 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/6-RetryLogic-TFH/retrylogictfh.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/7-RetryLogic-Python/requirements.txt: -------------------------------------------------------------------------------- 1 | pyodbc 2 | tenacity -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/7-RetryLogic-Python/retry.py: -------------------------------------------------------------------------------- 1 | import pyodbc 2 | import random 3 | from tenacity import * 4 | import logging 5 | 6 | logging.basicConfig(stream=sys.stderr, level=logging.DEBUG) 7 | logger = logging.getLogger(__name__) 8 | 9 | server = 'tcp:.database.windows.net,1433' 10 | database = 'WideWorldImporters-Full' 11 | username = '' 12 | password = '' 13 | 14 | 15 | def is_retriable(value): 16 | # Define all retriable error codes from https://docs.microsoft.com/en-us/azure/sql-database/troubleshoot-connectivity-issues-microsoft-azure-sql-database 17 | RETRY_CODES = [ 18 | 1204, 19 | 1205, 20 | 1222, 21 | 49918, 22 | 49919, 23 | 49920, 24 | 4060, 25 | 4221, 26 | 40143, 27 | 40613, 28 | 40501, 29 | 40540, 30 | 40197, 31 | 10929, 32 | 10928, 33 | 10060, 34 | 10054, 35 | 10053, 36 | 233, 37 | 208, 38 | 42000, 39 | 64, 40 | 20, 41 | 0 42 | ] 43 | ret = value in RETRY_CODES 44 | return ret 45 | 46 | @retry(stop=stop_after_attempt(3), wait=wait_fixed(10), after=after_log(logger, logging.DEBUG)) 47 | def my_database_operation(): 48 | 49 | global server 50 | global realserver 51 | global database 52 | global username 53 | global password 54 | 55 | try: 56 | cnxn = pyodbc.connect('DRIVER={ODBC Driver 17 for SQL Server};SERVER='+server+';DATABASE='+database+';UID='+username+';PWD='+ password+';Connect Timeout=10;') 57 | cursor = cnxn.cursor() 58 | 59 | tsql = "SELECT @@VERSIONZZZ;" 60 | 61 | with cursor.execute(tsql): 62 | row = cursor.fetchone() 63 | print (str(row[0])) 64 | except Exception as e: 65 | if isinstance(e,pyodbc.ProgrammingError) or isinstance(e,pyodbc.OperationalError): 66 | if is_retriable(int(e.args[0])): 67 | raise 68 | pass 69 | 70 | my_database_operation() -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/8-SQLAlchemy-Python/main.py: -------------------------------------------------------------------------------- 1 | import pyodbc 2 | import logging 3 | import urllib 4 | from sqlalchemy import * 5 | 6 | server = 'tcp:.database.windows.net,1433' 7 | database = 'WideWorldImporters-Full' 8 | username = '' 9 | password = '' 10 | 11 | logging.basicConfig() 12 | logging.getLogger('sqlalchemy.engine').setLevel(logging.WARN) 13 | 14 | def my_database_operation(): 15 | 16 | global server 17 | global realserver 18 | global database 19 | global username 20 | global password 21 | 22 | cnnString = urllib.parse.quote_plus('DRIVER={ODBC Driver 17 for SQL Server};SERVER='+server+';DATABASE='+database+';UID='+username+';PWD='+ password+';Connect Timeout=10;') 23 | 24 | engine = create_engine("mssql+pyodbc:///?odbc_connect=%s" % cnnString) 25 | 26 | metadata = MetaData() 27 | 28 | orders = Table('Orders', metadata, 29 | Column('orderid', Integer, primary_key=True), 30 | Column('customerid', Integer), 31 | Column('orderdate', Date), 32 | schema='Sales' 33 | ) 34 | 35 | customers = Table('Customers', metadata, 36 | Column('customerid',Integer,primary_key=True), 37 | Column('customername',String), 38 | schema='Sales' 39 | ) 40 | 41 | s = select([orders.c.orderid,orders.c.orderdate,customers.c.customername]).\ 42 | select_from(orders.join(customers, orders.c.customerid==customers.c.customerid)).\ 43 | limit(10) 44 | 45 | try: 46 | cnn = engine.connect() 47 | res = cnn.execute(s) 48 | 49 | for row in res: 50 | print(row) 51 | 52 | cnn.close() 53 | except Exception as e: 54 | print (e) 55 | pass 56 | 57 | my_database_operation() -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/8-SQLAlchemy-Python/requirements.txt: -------------------------------------------------------------------------------- 1 | pyodbc 2 | sqlalchemy -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/9-EFCore-CSharp/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/bin/Debug/netcoreapp3.1/efcore.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}", 16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console 17 | "console": "internalConsole", 18 | "stopAtEntry": false 19 | }, 20 | { 21 | "name": ".NET Core Attach", 22 | "type": "coreclr", 23 | "request": "attach", 24 | "processId": "${command:pickProcess}" 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/9-EFCore-CSharp/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/efcore.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/efcore.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "${workspaceFolder}/efcore.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/9-EFCore-CSharp/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace efcore 7 | { 8 | class Program 9 | { 10 | static void Main(string[] args) 11 | { 12 | using(var dbctx = new WWImportersContext()) 13 | { 14 | var res = dbctx.Orders 15 | .Include("Customer") 16 | .Select (o => new {o.OrderID,o.OrderDate,o.Customer.CustomerName}) 17 | .ToList().Take(10); 18 | 19 | foreach(var o in res) 20 | { 21 | Console.WriteLine("OrderId: {0} - OrderDate: {1} - CustomerName: {2}" 22 | ,o.OrderID,o.OrderDate,o.CustomerName); 23 | } 24 | } 25 | } 26 | } 27 | class Order 28 | { 29 | public int OrderID {get;set;} 30 | public DateTime OrderDate {get;set;} 31 | public int CustomerID {get;set;} 32 | public Customer Customer {get;set;} 33 | } 34 | class Customer 35 | { 36 | public int CustomerID {get;set;} 37 | public String CustomerName {get;set;} 38 | } 39 | class WWImportersContext : DbContext 40 | { 41 | public static readonly ILoggerFactory MyLoggerFactory 42 | = LoggerFactory.Create(builder => { 43 | builder 44 | .AddFilter((category, level) => 45 | category == DbLoggerCategory.Database.Command.Name 46 | && level == LogLevel.Information) 47 | .AddConsole(); 48 | }); 49 | public DbSet Orders {get;set;} 50 | public DbSet Customers {get;set;} 51 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 52 | { 53 | optionsBuilder 54 | .UseLoggerFactory(MyLoggerFactory) // Warning: Do not create a new ILoggerFactory instance each time 55 | .UseSqlServer( 56 | @"Server=tcp:.database.windows.net," + 57 | "1433;Initial Catalog=WideWorldImporters-Full;" + 58 | "User ID=;Password="); 59 | } 60 | protected override void OnModelCreating(ModelBuilder modelBuilder) 61 | { 62 | modelBuilder.Entity(o => o.ToTable("Orders","Sales")); 63 | modelBuilder.Entity(o => o.ToTable("Customers","Sales")); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/9-EFCore-CSharp/efcore.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /03-ConnectingAndQueryingAzureSQL/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 3 - Connecting and Querying Azure SQL 2 | 3 | ## 1-GettingStarted-CSharp 4 | 5 | Simple .NET Core REST API example using ADO.NET classes like Connection, Command and DataReader. Implementing a Repository pattern to return a list of Orders as Plain Old CLR Objects (POCO). 6 | 7 | ## 2-GettingStarted-Java 8 | 9 | Simple Java app connecting to Azure SQL through JDBC interfaces like Connection, Statement and ResultSet. 10 | 11 | ## 3-GettingStarted-Python-Flask 12 | 13 | Simple Python example using Flask to implement a REST API that connects to Azure SQL through pyodbc. 14 | 15 | ## 4-GettingStarted-Node 16 | 17 | Simple REST API built with NodeJS and using Tedious to connect to Azure SQL. 18 | 19 | ## 5-RetryLogic-CSharp 20 | 21 | Custom Retry Logic approach using ADO.NET base classes, intercepting retriable error codes and using a fixed retry strategy. 22 | 23 | ## 6-RetryLogic-TFH 24 | 25 | Configurable retry logic in a .NET Core application using Transient Fault Handling application block, a reusable library. 26 | 27 | ## 7-RetryLogic-Python 28 | 29 | Simple Python app using Tenacity to implement configurable retry logic. 30 | 31 | ## 8-SQLAlchemy-Python 32 | 33 | Python sample using SQLAlchemy as Object Relational Mapper (ORM) to connect to Azure SQL. 34 | 35 | ## 9-EFCore-CSharp 36 | 37 | Entity Framework Core example in a .NET application to demonstrate how to use ORM libraries. 38 | 39 | ## 10-Dapper-CSharp 40 | 41 | Lightweight ORM (Micro-ORM) demonstration using Dapper in a .NET Core application. -------------------------------------------------------------------------------- /04-DevelopingWithAzureSQL-Foundations/01-select.sql: -------------------------------------------------------------------------------- 1 | -- Use WideWorldImporters database 2 | 3 | /* 4 | Simple Select 5 | */ 6 | SELECT 7 | InvoiceID, 8 | InvoiceDate, 9 | DeliveryInstructions, 10 | ConfirmedDeliveryTime 11 | FROM 12 | Sales.Invoices 13 | WHERE 14 | CustomerID = 998 15 | ORDER BY 16 | ConfirmedDeliveryTime 17 | ; -------------------------------------------------------------------------------- /04-DevelopingWithAzureSQL-Foundations/02-select-with-join.sql: -------------------------------------------------------------------------------- 1 | -- Use WideWorldImporters database 2 | 3 | /* 4 | Join between two tables 5 | */ 6 | SELECT 7 | il.InvoiceLineID AS LineID, 8 | i.InvoiceID, 9 | il.[Description], 10 | il.Quantity, 11 | il.UnitPrice, 12 | il.UnitPrice * il.Quantity AS TotalPrice, 13 | i.ConfirmedDeliveryTime 14 | FROM 15 | Sales.Invoices AS i 16 | INNER JOIN 17 | Sales.InvoiceLines AS il ON i.InvoiceID = il.InvoiceID 18 | WHERE 19 | i.CustomerID = 998 20 | AND 21 | il.[Description] LIKE N'%red shirt%' 22 | AND 23 | CAST(i.ConfirmedDeliveryTime AS DATE) BETWEEN '2016-01-01' and '2016-03-31' 24 | ; 25 | 26 | /* 27 | Cascading joins 28 | */ 29 | SELECT 30 | c.CustomerName, 31 | il.InvoiceLineID AS LineID, 32 | i.InvoiceID, 33 | il.[Description], 34 | il.Quantity, 35 | il.UnitPrice, 36 | il.UnitPrice * il.Quantity AS TotalPrice, 37 | i.ConfirmedDeliveryTime 38 | FROM 39 | Sales.Customers AS c 40 | INNER JOIN 41 | Sales.Invoices AS i ON i.CustomerID = c.CustomerID 42 | INNER JOIN 43 | Sales.InvoiceLines AS il ON i.InvoiceID = il.InvoiceID 44 | WHERE 45 | i.CustomerID = 998 46 | AND 47 | il.[Description] LIKE N'%red shirt%' 48 | AND 49 | CAST(i.ConfirmedDeliveryTime AS DATE) BETWEEN '2016-01-01' AND '2016-03-31' 50 | ; -------------------------------------------------------------------------------- /04-DevelopingWithAzureSQL-Foundations/03-subqueries.sql: -------------------------------------------------------------------------------- 1 | -- Use WideWorldImporters database 2 | 3 | /* 4 | Query with three subqueries 5 | */ 6 | SELECT 7 | OrderId, 8 | OrderDate, 9 | (SELECT COUNT(*) FROM Sales.[OrderLines] AS ol WHERE ol.[OrderID] = o.OrderId) AS OrderSize 10 | FROM 11 | (SELECT * FROM Sales.[Orders] WHERE SalespersonPersonID = 2) AS o 12 | WHERE 13 | o.[CustomerID] IN 14 | ( 15 | SELECT 16 | c.CustomerID 17 | FROM 18 | Sales.[Customers] AS c 19 | WHERE 20 | [CustomerName] = 'Daniel Martensson' 21 | ) 22 | AND 23 | OrderDate >= '2015-01-01' 24 | ORDER BY 25 | [o].[OrderID] 26 | ; 27 | 28 | /* 29 | Some subqueries can be rewritten as join 30 | */ 31 | SELECT 32 | o.OrderId, 33 | o.OrderDate, 34 | (SELECT COUNT(*) FROM Sales.[OrderLines] AS ol WHERE ol.[OrderID] = o.OrderId) AS OrderSize 35 | FROM 36 | Sales.[Orders] AS o 37 | INNER JOIN 38 | Sales.[Customers] AS c ON o.[CustomerID] = c.CustomerID 39 | WHERE 40 | o.[OrderDate] >= '2015-01-01' 41 | AND 42 | c.[CustomerName] = 'Daniel Martensson' 43 | AND 44 | o.[SalespersonPersonID] = 2 45 | GROUP BY 46 | o.[OrderID], [o].[OrderDate] 47 | ORDER BY 48 | [o].[OrderID] 49 | ; 50 | 51 | /* 52 | Another way to rewrite the query 53 | TODO: highlight different query plans 54 | */ 55 | SELECT 56 | o.OrderId, 57 | o.OrderDate, 58 | COUNT(*) AS OrderSize 59 | FROM 60 | Sales.[Orders] AS o 61 | INNER JOIN 62 | Sales.[Customers] AS c ON o.[CustomerID] = c.CustomerID 63 | INNER JOIN 64 | Sales.[OrderLines] AS ol ON o.[OrderID] = ol.[OrderID] 65 | WHERE 66 | o.[OrderDate] >= '2015-01-01' 67 | AND 68 | c.[CustomerName] = 'Daniel Martensson' 69 | AND 70 | o.[SalespersonPersonID] = 2 71 | GROUP BY 72 | o.[OrderID], [o].[OrderDate] 73 | ORDER BY 74 | [o].[OrderID] 75 | ; -------------------------------------------------------------------------------- /04-DevelopingWithAzureSQL-Foundations/04-cte.sql: -------------------------------------------------------------------------------- 1 | -- Use WideWorldImporters database 2 | 3 | /* 4 | CTEs sample 5 | */ 6 | WITH cteOrders AS 7 | ( 8 | SELECT * FROM Sales.[Orders] WHERE SalespersonPersonID = 2 9 | ), 10 | cteCustomers AS 11 | ( 12 | SELECT 13 | c.CustomerID 14 | FROM 15 | Sales.[Customers] AS c 16 | WHERE 17 | [CustomerName] = 'Daniel Martensson' 18 | ) 19 | SELECT 20 | OrderId, 21 | OrderDate, 22 | (SELECT COUNT(*) FROM Sales.[OrderLines] AS ol WHERE ol.[OrderID] = o.OrderId) AS OrderSize 23 | FROM 24 | cteOrders AS o 25 | INNER JOIN 26 | cteCustomers c ON [c].[CustomerID] = [o].[CustomerID] 27 | AND 28 | OrderDate >= '2015-01-01' 29 | ORDER BY 30 | [o].[OrderID] 31 | GO 32 | 33 | /* 34 | Recursive CTE sample 35 | Sample from https://docs.microsoft.com/en-us/sql/t-sql/queries/with-common-table-expression-transact-sql?view=sql-server-ver15#examples 36 | */ 37 | 38 | -- Create an Employee table, where a parent-child hierarchy 39 | -- is defined via EmployeeID and ManagerID columns 40 | CREATE TABLE dbo.MyEmployees 41 | ( 42 | EmployeeID smallint NOT NULL, 43 | FirstName nvarchar(30) NOT NULL, 44 | LastName nvarchar(40) NOT NULL, 45 | Title nvarchar(50) NOT NULL, 46 | DeptID smallint NOT NULL, 47 | ManagerID int NULL, 48 | constraint PK_EmployeeID PRIMARY KEY CLUSTERED (EmployeeID ASC) 49 | ); 50 | go 51 | 52 | -- Populate the table with values 53 | insert into dbo.MyEmployees 54 | ([EmployeeID], [FirstName], [LastName], [Title], [DeptID], [ManagerID]) 55 | values 56 | (1, N'Ken', N'Sánchez', N'Chief Executive Officer',16,NULL) 57 | ,(273, N'Brian', N'Welcker', N'Vice President of Sales',3,1) 58 | ,(274, N'Stephen', N'Jiang', N'North American Sales Manager',3,273) 59 | ,(275, N'Michael', N'Blythe', N'Sales Representative',3,274) 60 | ,(276, N'Linda', N'Mitchell', N'Sales Representative',3,274) 61 | ,(285, N'Syed', N'Abbas', N'Pacific Sales Manager',3,273) 62 | ,(286, N'Lynn', N'Tsoflias', N'Sales Representative',3,285) 63 | ,(16, N'David',N'Bradley', N'Marketing Manager', 4, 273) 64 | ,(23, N'Mary', N'Gibson', N'Marketing Specialist', 4, 16); 65 | go 66 | 67 | -- Return all employees each one with a "path" string that 68 | -- shows their position in the organization chart 69 | with [cte] as 70 | ( 71 | -- Anchor Query 72 | select 73 | [ManagerID] 74 | ,[EmployeeID] 75 | ,[Title] 76 | ,0 as [EmployeeLevel] 77 | ,cast('/' as nvarchar(max)) as [ReportPath] 78 | from 79 | [dbo].[MyEmployees] 80 | where 81 | [ManagerID] is null 82 | 83 | union all 84 | 85 | -- Recursive Query 86 | select 87 | [e].[ManagerID] 88 | ,[e].[EmployeeID] 89 | ,[e].[Title] 90 | ,[EmployeeLevel] + 1 91 | ,cast(([ReportPath] + cast([e].[EmployeeID] as nvarchar(9)) + N'/') as nvarchar(max)) as [ReportPath] 92 | from 93 | [dbo].[MyEmployees] as [e] 94 | inner join 95 | [cte] as [d] on [e].[ManagerID] = [d].[EmployeeID] 96 | ) 97 | select 98 | [ManagerID] 99 | ,[EmployeeID] 100 | ,[Title] 101 | ,[EmployeeLevel] 102 | ,[ReportPath] 103 | from 104 | [cte] 105 | order by 106 | [ManagerID]; 107 | 108 | -- More details on recursive CTE here 109 | -- https://docs.microsoft.com/en-us/sql/t-sql/queries/with-common-table-expression-transact-sql?view=sql-server-ver15#guidelines-for-defining-and-using-recursive-common-table-expressions 110 | 111 | -- Beside recursive CTE, if you need to represent hierarchical structures you can also use the HIERARCHYID data type: 112 | -- https://docs.microsoft.com/en-us/sql/relational-databases/hierarchical-data-sql-server?view=sql-server-ver15 113 | 114 | -------------------------------------------------------------------------------- /04-DevelopingWithAzureSQL-Foundations/05-union.sql: -------------------------------------------------------------------------------- 1 | -- Use WideWorldImporters database 2 | 3 | /* 4 | Union Sample 5 | */ 6 | WITH cteContacts AS 7 | ( 8 | SELECT [CustomerID], [PrimaryContactPersonID] AS ContactPersonId, 'Primary' AS [ContactType] FROM Sales.[Customers] 9 | UNION 10 | SELECT [CustomerID], [AlternateContactPersonID], 'Alternate' AS [ContactType] FROM Sales.[Customers] 11 | ) 12 | SELECT 13 | [ContactPersonId], 14 | [ContactType] 15 | FROM 16 | [cteContacts] c 17 | WHERE 18 | c.CustomerId = 42 -------------------------------------------------------------------------------- /04-DevelopingWithAzureSQL-Foundations/06-dml-samples.sql: -------------------------------------------------------------------------------- 1 | -- Use WideWorldImporters database 2 | -- Use WideWorldImporters database 3 | 4 | /* 5 | Take a look at existing values 6 | */ 7 | SELECT * FROM [Warehouse].[Colors]; 8 | 9 | /* 10 | Insert a new value 11 | */ 12 | INSERT INTO 13 | [Warehouse].[Colors] ([ColorID], [ColorName], [LastEditedBy]) 14 | VALUES 15 | (99, 'Unknown', 1); 16 | 17 | /* 18 | Update inserted color 19 | */ 20 | UPDATE 21 | [Warehouse].[Colors] 22 | SET 23 | [ColorName] = 'Out of space' 24 | WHERE 25 | [ColorID] = 99; 26 | 27 | /* 28 | Delete color 29 | */ 30 | DELETE FROM 31 | [Warehouse].[Colors] 32 | WHERE 33 | [ColorID] = 99; 34 | 35 | /* 36 | Merge several colors into existing table 37 | */ 38 | MERGE INTO 39 | [Warehouse].[Colors] AS [target] 40 | USING 41 | (VALUES 42 | (50, 'Deep Sea Blue'), 43 | (51, 'Deep Sea Light Blue'), 44 | (52, 'Deep Sea Dark Blue') 45 | ) [source](Id, [Name]) 46 | ON 47 | [target].[ColorID] = [source].[Id] 48 | WHEN MATCHED THEN 49 | UPDATE SET [target].[ColorName] = [source].[Name] 50 | WHEN NOT MATCHED THEN 51 | INSERT ([ColorID], [ColorName], [LastEditedBy]) VALUES ([source].Id, [source].[Name], 1) 52 | WHEN NOT MATCHED BY SOURCE AND [target].[ColorID] BETWEEN 50 AND 100 THEN 53 | DELETE 54 | ; 55 | -------------------------------------------------------------------------------- /04-DevelopingWithAzureSQL-Foundations/07-output-clause.sql: -------------------------------------------------------------------------------- 1 | -- Use WideWorldImporters database 2 | 3 | /* 4 | Make sure table is in the expected state 5 | */ 6 | DELETE FROM [Warehouse].[Colors] WHERE [ColorID] = 99; 7 | GO 8 | 9 | /* 10 | Insert a new row and output the inserted values 11 | */ 12 | INSERT INTO 13 | [Warehouse].[Colors] ([ColorID], [ColorName], [LastEditedBy]) 14 | OUTPUT 15 | [Inserted].* 16 | VALUES 17 | (99, 'Unknown', 1) 18 | GO 19 | 20 | /* 21 | Update date and output current and previous value 22 | */ 23 | UPDATE 24 | [Warehouse].[Colors] 25 | SET 26 | [ColorName] = 'Out of space' 27 | OUTPUT 28 | [Inserted].[ColorID], 29 | [Inserted].[ColorName] AS CurrentColorName, 30 | [Deleted].[ColorName] AS OldColorName 31 | WHERE 32 | [ColorID] = 99; 33 | GO 34 | 35 | /* 36 | Delete sample data and return deleted values 37 | */ 38 | DELETE FROM [Warehouse].[Colors] 39 | OUTPUT [Deleted].* 40 | WHERE [ColorID] = 99; 41 | -------------------------------------------------------------------------------- /04-DevelopingWithAzureSQL-Foundations/08-identity-and-sequences.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Create a sample table 3 | with a IDENTITY column 4 | */ 5 | DROP TABLE IF EXISTS dbo.SampleID; 6 | CREATE TABLE dbo.SampleID 7 | ( 8 | Id INT IDENTITY(1,1) NOT NULL, 9 | OtherColums NVARCHAR(100) NULL 10 | ) 11 | GO 12 | 13 | /* 14 | Insert a new row. Value for Id column 15 | will be added automatically 16 | */ 17 | INSERT INTO dbo.SampleID (OtherColums) 18 | VALUES ('Some value here'); 19 | 20 | SELECT * FROM dbo.SampleID 21 | GO 22 | 23 | /* 24 | Create a sample table 25 | now using a SEQUENCE 26 | */ 27 | DROP SEQUENCE IF EXISTS dbo.BookSequence; 28 | CREATE SEQUENCE dbo.BookSequence 29 | AS BIGINT 30 | START WITH 1 31 | INCREMENT BY 1; 32 | 33 | DROP TABLE IF EXISTS dbo.SampleID; 34 | CREATE TABLE dbo.SampleID 35 | ( 36 | Id INT NOT NULL DEFAULT(NEXT VALUE FOR dbo.BookSequence), 37 | OtherColums NVARCHAR(100) NULL 38 | ); 39 | 40 | /* 41 | Insert a new row. Value for Id column 42 | will be added automatically 43 | */ 44 | INSERT INTO dbo.SampleID (OtherColums) 45 | VALUES ('Some value here'); 46 | 47 | SELECT * FROM dbo.SampleID 48 | GO 49 | -------------------------------------------------------------------------------- /04-DevelopingWithAzureSQL-Foundations/09-top-offset-fetch.sql: -------------------------------------------------------------------------------- 1 | -- Use WideWorldImporters 2 | 3 | -- The Sales.Orders table has a lot of rows 4 | SELECT FORMAT(COUNT(*), '0,#') AS [RowCount] FROM [Sales].[Orders]; 5 | 6 | -- Return only the first 100 rows 7 | SELECT TOP (100) * FROM [Sales].[Orders]; 8 | 9 | -- Return only the first 100 rows ordered by ExpectedDeliveryDate 10 | SELECT TOP (100) 11 | * 12 | FROM 13 | [Sales].[Orders] 14 | ORDER BY 15 | [ExpectedDeliveryDate] DESC; 16 | 17 | -- Return only 50 rows ordered by ExpectedDeliveryDate, 18 | -- Skipping the first 50 rows 19 | SELECT 20 | * 21 | FROM 22 | [Sales].[Orders] 23 | ORDER BY 24 | [ExpectedDeliveryDate] DESC 25 | OFFSET 26 | 50 ROWS 27 | FETCH 28 | NEXT 50 ROWS ONLY 29 | ; 30 | 31 | -------------------------------------------------------------------------------- /04-DevelopingWithAzureSQL-Foundations/10-group-by.sql: -------------------------------------------------------------------------------- 1 | -- Use WideWorldImporters 2 | 3 | -- Base GROUP BY Query 4 | SELECT 5 | [SupplierID], 6 | [ColorID], 7 | COUNT(*) AS ProductsInStock, 8 | SUM(QuantityPerOuter) AS ProductsQuantity 9 | FROM 10 | [Warehouse].[StockItems] 11 | GROUP BY 12 | [SupplierID], [ColorID] 13 | ORDER BY 14 | [SupplierID], [ColorID]; 15 | GO 16 | 17 | -- JOIN the StockItems table with Suppliers and Colors 18 | -- to get Suppliers Colors name. 19 | -- This means that Azure SQL will first procceed to join the tables 20 | -- and then will execute the aggregation. 21 | SELECT 22 | si.[SupplierID], 23 | s.[SupplierName], 24 | si.[ColorID], 25 | c.[ColorName], 26 | COUNT(*) AS ProductsInStock, 27 | SUM(QuantityPerOuter) AS ProductsQuantity 28 | FROM 29 | [Warehouse].[StockItems] AS si 30 | INNER JOIN 31 | [Purchasing].[Suppliers] s ON si.[SupplierID] = s.[SupplierID] 32 | LEFT OUTER JOIN 33 | [Warehouse].[Colors] c ON si.[ColorID] = c.[ColorID] 34 | GROUP BY 35 | si.[SupplierID], si.[ColorID], c.[ColorName], s.[SupplierName] 36 | ORDER BY 37 | si.[SupplierID], si.[ColorID] 38 | GO 39 | 40 | -- Using a subquery (this time via a CTE) 41 | -- In this case we also help Azure SQL do a better job 42 | -- As we indicate that data can be joined after being 43 | -- aggregated, hereby reducing the amout of rows 44 | -- that will be used for the JOIN 45 | WITH cte AS ( 46 | SELECT 47 | si.[SupplierID], 48 | si.[ColorID], 49 | COUNT(*) AS ProductsInStock, 50 | SUM(QuantityPerOuter) AS ProductsQuantity 51 | FROM 52 | [Warehouse].[StockItems] AS si 53 | GROUP BY 54 | si.[SupplierID], si.[ColorID] 55 | ) 56 | SELECT 57 | s.[SupplierID], 58 | s.[SupplierName], 59 | c.[ColorID], 60 | c.[ColorName], 61 | g.[ProductsInStock], 62 | g.[ProductsQuantity] 63 | FROM 64 | [cte] AS g 65 | INNER JOIN 66 | [Purchasing].[Suppliers] s ON g.[SupplierID] = s.[SupplierID] 67 | LEFT OUTER JOIN 68 | [Warehouse].[Colors] c ON g.[ColorID] = c.[ColorID] 69 | ORDER BY 70 | g.[SupplierID], g.[ColorID] 71 | GO 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /04-DevelopingWithAzureSQL-Foundations/11-grouping-sets.sql: -------------------------------------------------------------------------------- 1 | -- Use WideWorldImporters 2 | 3 | -- Use GROUP BY with GROUPING SETS 4 | -- Aggregating values by 5 | -- SupplierID & ColorID 6 | -- SupplierID 7 | -- ColorID 8 | -- and calculate overall total in just one pass. 9 | -- Also use GROUPING funcion to discriminate NULL 10 | -- values between two meanings: when it means color 11 | -- is unknown or not applicable and when the line 12 | -- represent an aggregation of all colors 13 | SELECT 14 | [SupplierID], 15 | [ColorID], 16 | COUNT(*) AS ProductsInStock, 17 | SUM(QuantityPerOuter) AS ProductsQuantity, 18 | GROUPING(ColorID) as IsAllColors, 19 | GROUPING(SupplierID) as IsAllSuppliers 20 | FROM 21 | [Warehouse].[StockItems] 22 | GROUP BY 23 | GROUPING SETS 24 | ( 25 | ([SupplierID], [ColorID]), 26 | ([SupplierID]), 27 | ([ColorID]), 28 | () 29 | ) 30 | ORDER BY 31 | IsAllColors, IsAllSuppliers 32 | ; -------------------------------------------------------------------------------- /04-DevelopingWithAzureSQL-Foundations/12-windowing-functions.sql: -------------------------------------------------------------------------------- 1 | -- Use WideWorldImporters 2 | 3 | SELECT * FROM [Sales].[OrderLines] 4 | 5 | -- Calculate Running Total for a specific order 6 | SELECT 7 | [OrderLineID], 8 | [Description], 9 | [Quantity], 10 | SUM(Quantity) OVER (ORDER BY [OrderLineID] ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS RunningTotal 11 | FROM 12 | [Sales].[OrderLines] 13 | WHERE 14 | [OrderID] = 37 15 | 16 | GO 17 | 18 | -- Calculate Running Total for different orders 19 | SELECT 20 | [OrderID], 21 | [OrderLineID], 22 | [Description], 23 | [Quantity], 24 | SUM(Quantity) OVER ( 25 | PARTITION BY [OrderID] 26 | ORDER BY [OrderLineID] ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 27 | ) AS RunningTotal 28 | FROM 29 | [Sales].[OrderLines] 30 | WHERE 31 | [OrderID] in (37, 39) 32 | GO 33 | 34 | -- Calculate how many days passes between each order 35 | -- for a specific customer 36 | SELECT 37 | [OrderID], 38 | [OrderDate], 39 | LAG(OrderDate, 1) OVER (ORDER BY [OrderDate]) AS PrevOrderDate, 40 | DATEDIFF( 41 | [DAY], 42 | LAG(OrderDate, 1) OVER (ORDER BY [OrderDate]), 43 | [OrderDate] 44 | ) AS ElapsedDays 45 | FROM 46 | [Sales].[Orders] 47 | WHERE 48 | [CustomerID] = 832 49 | ORDER BY 50 | [OrderDate] 51 | ; 52 | 53 | -- Calculate how many days passes between each order 54 | -- for customers and calculate the moving average acroos the last 10 orders 55 | WITH cte AS ( 56 | SELECT 57 | [CustomerID], 58 | [OrderID], 59 | [OrderDate], 60 | LAG(OrderDate, 1) OVER (PARTITION BY [CustomerID] ORDER BY [OrderDate]) AS PrevOrderDate 61 | FROM 62 | [Sales].[Orders] 63 | ), 64 | cte2 AS ( 65 | SELECT 66 | *, 67 | DATEDIFF( 68 | [DAY], 69 | LAG(OrderDate, 1) OVER (PARTITION BY CustomerID ORDER BY [OrderDate]), 70 | [OrderDate] 71 | ) AS ElapsedDays 72 | FROM 73 | [cte] 74 | ) 75 | SELECT 76 | *, 77 | AVG([cte2].[ElapsedDays]) OVER (ORDER BY [OrderID] ROWS BETWEEN 10 PRECEDING AND CURRENT ROW) AS MovingAvg 78 | FROM 79 | cte2 80 | WHERE 81 | [CustomerID] = 832 82 | ORDER BY 83 | [OrderDate] 84 | ; 85 | -------------------------------------------------------------------------------- /04-DevelopingWithAzureSQL-Foundations/BulkCopy/DotNetBulkCopy/.env.template: -------------------------------------------------------------------------------- 1 | CS_AzureSQL='Server=;Initial Catalog=;User ID=;Password=;Persist Security Info=False;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;' -------------------------------------------------------------------------------- /04-DevelopingWithAzureSQL-Foundations/BulkCopy/DotNetBulkCopy/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | "program": "${workspaceFolder}/bin/Debug/netcoreapp3.1/DotNetBulkCopy.dll", 13 | "args": [], 14 | "cwd": "${workspaceFolder}", 15 | "console": "internalConsole", 16 | "envFile": "${workspaceFolder}/.env", 17 | "stopAtEntry": false 18 | }, 19 | { 20 | "name": ".NET Core Attach", 21 | "type": "coreclr", 22 | "request": "attach", 23 | "processId": "${command:pickProcess}" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /04-DevelopingWithAzureSQL-Foundations/BulkCopy/DotNetBulkCopy/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/DotNetBulkCopy.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/DotNetBulkCopy.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "${workspaceFolder}/DotNetBulkCopy.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /04-DevelopingWithAzureSQL-Foundations/BulkCopy/DotNetBulkCopy/DotNetBulkCopy.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /04-DevelopingWithAzureSQL-Foundations/BulkCopy/DotNetBulkCopy/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Data; 4 | using Microsoft.Data.SqlClient; 5 | using System.Diagnostics; 6 | using Bogus; 7 | using DotNetEnv; 8 | 9 | namespace DotNetBulkCopy 10 | { 11 | class Program 12 | { 13 | static void Main(string[] args) 14 | { 15 | DotNetEnv.Env.Load(); 16 | 17 | Console.WriteLine("Generating sample data..."); 18 | var dt = new DataTable("Users"); 19 | dt.Columns.Add("Id", typeof(int)); 20 | dt.Columns.Add("FirstName", typeof(string)); 21 | dt.Columns.Add("LastName", typeof(string)); 22 | dt.Columns.Add("UserName", typeof(string)); 23 | dt.Columns.Add("Email", typeof(string)); 24 | 25 | var faker = new Faker("en"); 26 | int totalRows = 100000; 27 | foreach(var i in Enumerable.Range(1, totalRows)) 28 | { 29 | var dr = dt.NewRow(); 30 | dr["Id"] = i; 31 | dr["FirstName"] = faker.Name.FirstName(); 32 | dr["LastName"] = faker.Name.LastName(); 33 | dr["UserName"] = faker.Internet.UserName(); 34 | dr["Email"] = faker.Internet.Email(); 35 | dt.Rows.Add(dr); 36 | } 37 | 38 | Console.WriteLine("Bulk loading into Azure SQL..."); 39 | var sw = new Stopwatch(); 40 | using(var conn = new SqlConnection(Environment.GetEnvironmentVariable("CS_AzureSQL"))) 41 | { 42 | conn.Open(); 43 | 44 | var cmd = new SqlCommand("TRUNCATE TABLE dbo.BulkLoadedUsers", conn); 45 | cmd.ExecuteNonQuery(); 46 | 47 | using (var bc = new SqlBulkCopy(conn)) 48 | { 49 | bc.DestinationTableName = "dbo.BulkLoadedUsers"; 50 | bc.BatchSize = 10000; 51 | bc.NotifyAfter = 5000; 52 | bc.SqlRowsCopied += new SqlRowsCopiedEventHandler(BulkCopyNotificationHandler); 53 | try 54 | { 55 | sw.Start(); 56 | bc.WriteToServer(dt); 57 | sw.Stop(); 58 | double elapsedSecs = sw.ElapsedMilliseconds / 1000.0; 59 | double rowsPerSec = (double)totalRows / elapsedSecs; 60 | Console.WriteLine($"Total elapsed seconds: {elapsedSecs:#.##}"); 61 | Console.WriteLine($"Rows Per Sec: {rowsPerSec:#.##}"); 62 | } 63 | catch (Exception ex) 64 | { 65 | Console.WriteLine(ex.Message); 66 | } 67 | } 68 | } 69 | } 70 | 71 | static void BulkCopyNotificationHandler(object sender, SqlRowsCopiedEventArgs e) 72 | { 73 | Console.WriteLine($"{e.RowsCopied} Rows copied..."); 74 | } 75 | 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /04-DevelopingWithAzureSQL-Foundations/BulkCopy/DotNetBulkCopy/README.md: -------------------------------------------------------------------------------- 1 | # .NET Core Bulk Copy Sample 2 | 3 | Sample showing how to Bulk Load data into Azure SQL using BulkCopy API available in .NET Core 4 | 5 | Before running the sample create a `.env` file using the `.env.template` as a starting point. Azure SQL connection string can be obtained from the Azure Portal or via AZ CLI: 6 | 7 | ```bash 8 | az sql db show-connection-string -n --server -c ado.net 9 | ``` 10 | 11 | The sample will create 100000 on the fly, using the `Bogus` package and will bulk load them in Azure SQL. -------------------------------------------------------------------------------- /04-DevelopingWithAzureSQL-Foundations/BulkCopy/DotNetBulkCopy/sql/setup.sql: -------------------------------------------------------------------------------- 1 | drop table if exists dbo.BulkLoadedUsers; 2 | create table dbo.BulkLoadedUsers 3 | ( 4 | Id int not null primary key, 5 | FirstName nvarchar(100) not null, 6 | LastName nvarchar(100) not null, 7 | UserName nvarchar(100) not null, 8 | Email nvarchar(1024) not null 9 | ) 10 | go -------------------------------------------------------------------------------- /04-DevelopingWithAzureSQL-Foundations/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 4 - Developing with Azure SQL: Foundations 2 | 3 | Samples for to Chapter 4 of [Practical Azure SQL Database for Modern Developers](https://www.apress.com/gp/book/9781484263693) book. 4 | 5 | Make sure to use the World Wide Importers database. 6 | 7 | You can use the samples of Chapter 2 to restore that database to Azure SQL or Azure SQL Managed Instance. -------------------------------------------------------------------------------- /05-DevelopingWithAzureSQL-Advanced/01-views.sql: -------------------------------------------------------------------------------- 1 | -- Use WideWorldImporters database 2 | 3 | /* 4 | Create View 5 | */ 6 | CREATE OR ALTER VIEW [Sales].[OrderLinesRuninngTotal] 7 | AS 8 | SELECT 9 | [OrderID], 10 | [OrderLineID], 11 | [Description], 12 | [Quantity], 13 | SUM(Quantity) OVER ( 14 | PARTITION BY [OrderID] 15 | ORDER BY [OrderLineID] ROWS BETWEEN 16 | UNBOUNDED PRECEDING AND 17 | CURRENT ROW 18 | ) AS RunningTotal 19 | FROM 20 | [Sales].[OrderLines] 21 | GO 22 | 23 | /* 24 | Use View 25 | */ 26 | SELECT 27 | OrderID, 28 | [OrderLineID], 29 | [Description], 30 | [Quantity], 31 | [RunningTotal] 32 | FROM 33 | [Sales].[OrderLinesRuninngTotal]; 34 | GO 35 | 36 | /* 37 | The body of the query using the view and the view body itself are merged togheter. 38 | This query and the following one are identical, but the query using the view 39 | is much easier to read. Also the view can be reused in other queries 40 | */ 41 | SELECT 42 | OrderID, 43 | [OrderLineID], 44 | [Description], 45 | [Quantity], 46 | [RunningTotal] 47 | FROM 48 | [Sales].[OrderLinesRuninngTotal] 49 | WHERE 50 | [OrderID] IN (41, 42, 43); 51 | 52 | SELECT 53 | [OrderID], 54 | [OrderLineID], 55 | [Description], 56 | [Quantity], 57 | SUM(Quantity) OVER ( 58 | PARTITION BY [OrderID] 59 | ORDER BY [OrderLineID] ROWS BETWEEN 60 | UNBOUNDED PRECEDING AND 61 | CURRENT ROW 62 | ) AS RunningTotal 63 | FROM 64 | [Sales].[OrderLines] 65 | WHERE 66 | [OrderID] IN (41, 42, 43); -------------------------------------------------------------------------------- /05-DevelopingWithAzureSQL-Advanced/02-functions.sql: -------------------------------------------------------------------------------- 1 | -- Use WideWorldImporters database 2 | 3 | /* 4 | Create Inline Table-Valued Function 5 | */ 6 | CREATE OR ALTER FUNCTION dbo.GetOrderTotals(@FromOrderId AS INT, @ToOrderID AS INT) 7 | RETURNS TABLE 8 | AS 9 | RETURN 10 | WITH cte AS ( 11 | SELECT 12 | [OrderId], 13 | SUM([Quantity]) AS TotalQuantity, 14 | SUM([Quantity] * [UnitPrice]) AS TotalValue 15 | FROM 16 | [Sales].[OrderLines] 17 | WHERE 18 | [OrderId] BETWEEN @FromOrderId AND @ToOrderID 19 | GROUP BY 20 | [OrderId] 21 | ) 22 | SELECT 23 | o.[OrderId], 24 | o.[OrderDate], 25 | ol.[TotalQuantity], 26 | ol.[TotalValue] 27 | FROM 28 | cte ol 29 | INNER JOIN 30 | [Sales].[Orders] o ON [ol].[OrderID] = [o].[OrderID] 31 | GO 32 | 33 | /* 34 | Use created Function 35 | */ 36 | SELECT 37 | [OrderID], 38 | [OrderDate], 39 | [TotalQuantity], 40 | [TotalValue] 41 | FROM 42 | dbo.[GetOrderTotals](40, 42); 43 | go 44 | 45 | /* 46 | Multi-Statement Table-Valued Function 47 | */ 48 | CREATE OR ALTER FUNCTION dbo.AlternativeGetOrderTotals(@FromOrderId AS INT, @ToOrderID AS INT) 49 | RETURNS @result TABLE 50 | ( 51 | OrderId INT PRIMARY KEY NOT NULL, 52 | OrderDate DATE NOT NULL, 53 | TotalQuantity INT NOT NULL, 54 | TotalValue DECIMAL(13,2) NOT NULL 55 | ) 56 | AS 57 | BEGIN 58 | 59 | DECLARE @temp TABLE 60 | ( 61 | OrderId INT PRIMARY KEY NOT NULL, 62 | TotalQuantity INT NOT NULL, 63 | TotalValue DECIMAL(13,2) NOT NULL 64 | ); 65 | 66 | INSERT INTO 67 | @temp 68 | SELECT 69 | [OrderId], 70 | SUM([Quantity]) AS TotalQuantity, 71 | SUM([Quantity] * [UnitPrice]) AS TotalValue 72 | FROM 73 | [Sales].[OrderLines] 74 | WHERE 75 | [OrderId] BETWEEN @FromOrderId AND @ToOrderID 76 | GROUP BY 77 | [OrderId]; 78 | 79 | INSERT INTO 80 | @result 81 | SELECT 82 | o.[OrderId], 83 | o.[OrderDate], 84 | ol.[TotalQuantity], 85 | ol.[TotalValue] 86 | FROM 87 | @temp AS ol 88 | INNER JOIN 89 | [Sales].[Orders] o ON [ol].[OrderID] = [o].[OrderID] 90 | 91 | RETURN 92 | END 93 | GO 94 | 95 | /* 96 | Use created Function 97 | */ 98 | SELECT 99 | [OrderID], 100 | [OrderDate], 101 | [TotalQuantity], 102 | [TotalValue] 103 | FROM 104 | dbo.[AlternativeGetOrderTotals](40, 42); 105 | go 106 | 107 | /* 108 | Scalar Function 109 | */ 110 | CREATE OR ALTER FUNCTION dbo.GenerateInvoiceNumber(@CustomerID INT, @InvoiceID INT) 111 | RETURNS VARCHAR(50) 112 | AS 113 | BEGIN 114 | DECLARE @InvoiceNumber VARCHAR(50); 115 | SET @InvoiceNumber = 'INV' + CAST(@CustomerID * 1000000 + @InvoiceID AS VARCHAR(16)) 116 | RETURN @InvoiceNumber; 117 | END 118 | 119 | /* 120 | Use created Function 121 | */ 122 | SELECT TOP(10) 123 | dbo.GenerateInvoiceNumber(CustomerId, InvoiceID) as InvoiceNumber, 124 | * 125 | FROM 126 | Sales.Invoices 127 | go -------------------------------------------------------------------------------- /05-DevelopingWithAzureSQL-Advanced/03-stored-procedures.sql: -------------------------------------------------------------------------------- 1 | -- Use WideWorldImporters database 2 | 3 | /* 4 | Create Stored Procedure 5 | */ 6 | CREATE OR ALTER PROCEDURE dbo.GetOrderForCustomer 7 | @CustomerInfo NVARCHAR(MAX) 8 | AS 9 | IF (ISJSON(@CustomerInfo) != 1) BEGIN 10 | THROW 50000, '@CustomerInfo is not a valid JSON document', 16 11 | END 12 | 13 | SELECT [Value] INTO #T FROM OPENJSON(@CustomerInfo, '$.CustomerId') AS ci; 14 | 15 | SELECT 16 | [CustomerID], 17 | COUNT(*) AS OrderCount, 18 | MIN([OrderDate]) AS FirstOrder, 19 | MAX([OrderDate]) AS LastOrder 20 | FROM 21 | Sales.[Orders] 22 | WHERE 23 | [CustomerID] IN (SELECT [Value] FROM #T) 24 | GROUP BY 25 | [CustomerID]; 26 | GO 27 | 28 | /* 29 | Use created Stored Procedure 30 | */ 31 | EXEC dbo.GetOrderForCustomer N'{"CustomerId": [106, 193, 832]}'; 32 | -------------------------------------------------------------------------------- /05-DevelopingWithAzureSQL-Advanced/04-triggers.sql: -------------------------------------------------------------------------------- 1 | -- Use WideWorldImporters database 2 | 3 | /* 4 | Make sure Table has the default values 5 | */ 6 | UPDATE [Warehouse].[Colors] SET ColorName = 'Azure' WHERE ColorId = 1 7 | DELETE [Warehouse].[Colors] WHERE ColorId > 36 8 | GO 9 | 10 | /* 11 | Define a Trigger on [Warehouse].[Colors] Table 12 | */ 13 | CREATE OR ALTER TRIGGER [Warehouse].[ColorTrigger] ON [Warehouse].[Colors] 14 | FOR UPDATE, DELETE 15 | AS 16 | BEGIN 17 | IF EXISTS(SELECT * FROM [Deleted] WHERE [ColorName] IN ('Azure')) 18 | BEGIN 19 | IF NOT EXISTS(SELECT * FROM [Inserted] WHERE [ColorName] IN ('Azure')) 20 | BEGIN 21 | THROW 50000, 'Azure is here to stay.', 16; 22 | ROLLBACK TRAN; 23 | END 24 | END 25 | END 26 | GO 27 | 28 | 29 | /* 30 | Try to delete or update the Azure color 31 | */ 32 | -- That won't work 33 | UPDATE [Warehouse].[Colors] SET ColorName = 'NotAzure' WHERE ColorId = 1; 34 | 35 | --Neither this 36 | DELETE FROM [Warehouse].[Colors] WHERE ColorName LIKE 'A%'; 37 | 38 | --This is fine 39 | INSERT INTO [Warehouse].[Colors] 40 | ([ColorID], [ColorName], [LastEditedBy]) 41 | VALUES 42 | (999, 'SomeOtherColor', 1) 43 | ; 44 | 45 | DELETE FROM [Warehouse].[Colors] WHERE ColorID = 999 46 | ; 47 | 48 | -------------------------------------------------------------------------------- /05-DevelopingWithAzureSQL-Advanced/05-tvp.sql: -------------------------------------------------------------------------------- 1 | -- Use WideWorldImporters database 2 | 3 | /* 4 | Make sure sample objects doesn't exists already 5 | */ 6 | DROP TABLE IF EXISTS dbo.PostTags; 7 | DROP PROCEDURE IF EXISTS dbo.AddTagsToPost; 8 | DROP TYPE IF EXISTS dbo.PostTagsTableType ; 9 | GO 10 | 11 | /* 12 | Create sample table 13 | */ 14 | CREATE TABLE dbo.PostTags 15 | ( 16 | PostId INT NOT NULL, 17 | Tag NVARCHAR(100) NOT NULL, 18 | CONSTRAINT pk__Samples__PostTags 19 | PRIMARY KEY CLUSTERED (PostId, Tag) 20 | ); 21 | GO 22 | 23 | /* 24 | Create Table Variable 25 | */ 26 | CREATE TYPE dbo.PostTagsTableType AS TABLE 27 | ( 28 | Tag NVARCHAR(100) NOT NULL UNIQUE 29 | ); 30 | GO 31 | 32 | /* 33 | Create Stored Procedure that uses the created Table Variable 34 | */ 35 | CREATE PROCEDURE dbo.AddTagsToPost 36 | @PostId INT, 37 | @Tags dbo.PostTagsTableType READONLY 38 | AS 39 | INSERT INTO dbo.PostTags 40 | SELECT @PostId, Tag FROM @Tags 41 | GO 42 | 43 | /* 44 | Use TVP from T-SQL 45 | */ 46 | DECLARE @tvp AS dbo.PostTagsTableType; 47 | INSERT INTO @tvp (Tag) VALUES ('T-SQL'), ('Test'), ('TVP'); 48 | EXEC dbo.[AddTagsToPost] 123, @tvp 49 | go 50 | 51 | /* 52 | To use TVP from you app you can find 53 | samples in the TableValueParameters folder 54 | */ 55 | 56 | /* 57 | View table content 58 | */ 59 | SELECT * FROM dbo.PostTags 60 | GO 61 | -------------------------------------------------------------------------------- /05-DevelopingWithAzureSQL-Advanced/06-csv.sql: -------------------------------------------------------------------------------- 1 | -- Use WideWorldImporters database 2 | 3 | /* 4 | Make sure sample objects doesn't exists already 5 | */ 6 | DROP TABLE IF EXISTS dbo.PostTags; 7 | DROP PROCEDURE IF EXISTS dbo.AddTagsToPost; 8 | GO 9 | 10 | /* 11 | Create sample table 12 | */ 13 | CREATE TABLE dbo.PostTags 14 | ( 15 | PostId INT NOT NULL, 16 | Tag NVARCHAR(100) NOT NULL, 17 | CONSTRAINT pk__Samples__PostTags 18 | PRIMARY KEY CLUSTERED (PostId, Tag) 19 | ); 20 | GO 21 | 22 | /* 23 | Create Stored Procedure 24 | */ 25 | CREATE PROCEDURE dbo.AddTagsToPost 26 | @PostId INT, 27 | @Tags NVARCHAR(MAX) 28 | AS 29 | INSERT INTO dbo.PostTags 30 | SELECT @PostId, T.[value] FROM STRING_SPLIT(@Tags, '|') AS T 31 | GO 32 | 33 | /* 34 | Use created Stored Procedure 35 | */ 36 | EXEC dbo.AddTagsToPost 123, 'azure-sql|string_split|csv' 37 | GO 38 | 39 | /* 40 | View table content 41 | */ 42 | SELECT * FROM dbo.PostTags 43 | GO 44 | 45 | 46 | -------------------------------------------------------------------------------- /05-DevelopingWithAzureSQL-Advanced/07-json.sql: -------------------------------------------------------------------------------- 1 | -- Use WideWorldImporters database 2 | 3 | /* 4 | Make sure sample objects doesn't exists already 5 | */ 6 | DROP TABLE IF EXISTS dbo.PostTags; 7 | DROP PROCEDURE IF EXISTS dbo.AddTagsToPost; 8 | GO 9 | 10 | /* 11 | Create sample table 12 | */ 13 | CREATE TABLE dbo.PostTags 14 | ( 15 | PostId INT NOT NULL, 16 | Tag NVARCHAR(100) NOT NULL, 17 | CONSTRAINT pk__Samples__PostTags 18 | PRIMARY KEY CLUSTERED (PostId, Tag) 19 | ); 20 | GO 21 | 22 | /* 23 | Create Stored Procedure 24 | */ 25 | CREATE PROCEDURE dbo.AddTagsToPost 26 | @PostId INT, 27 | @Tags NVARCHAR(MAX) 28 | AS 29 | INSERT INTO dbo.PostTags 30 | SELECT @PostId, T.[value] FROM OPENJSON(@Tags, '$.tags') T 31 | GO 32 | 33 | /* 34 | Use created Stored Procedure 35 | */ 36 | EXEC dbo.AddTagsToPost 123, '{"tags": ["azure-sql", "string_split", "csv"], "categories": {}}' 37 | GO 38 | 39 | /* 40 | View table content 41 | */ 42 | SELECT * FROM dbo.PostTags 43 | GO 44 | 45 | 46 | -------------------------------------------------------------------------------- /05-DevelopingWithAzureSQL-Advanced/08-change-tracking.sql: -------------------------------------------------------------------------------- 1 | -- Use WideWorldImporters database 2 | 3 | /* 4 | Enable Change Tracking on Database 5 | */ 6 | ALTER DATABASE WideWorldImportersStandard 7 | SET CHANGE_TRACKING = ON 8 | (CHANGE_RETENTION = 2 DAYS, AUTO_CLEANUP = ON) 9 | GO 10 | 11 | /* 12 | Enable Change Tracking on Colors Table 13 | */ 14 | ALTER TABLE [Warehouse].[Colors] 15 | ENABLE CHANGE_TRACKING 16 | GO 17 | 18 | /* 19 | Get Current Change Tracking Version. 20 | Make sure to write this number down somewhere 21 | as it will be needed later 22 | */ 23 | SELECT CHANGE_TRACKING_CURRENT_VERSION() 24 | GO 25 | 26 | /* 27 | Update a color 28 | */ 29 | UPDATE [Warehouse].[Colors] 30 | SET ColorName = 'Test' 31 | WHERE ColorID = 10; 32 | GO 33 | 34 | /* 35 | Insert a couple of new colors 36 | */ 37 | INSERT INTO [Warehouse].[Colors] ([ColorID], [ColorName], [LastEditedBy]) 38 | VALUES 39 | (100, 'Blue Metal', 1), 40 | (101, 'Dark Brown Green', 1); 41 | go 42 | 43 | /* 44 | Delete a color 45 | */ 46 | DELETE FROM [Warehouse].[Colors] WHERE ColorID = 30; 47 | go 48 | 49 | /* 50 | Get all changes. In @fromVersion set the number returned by 51 | the previous call to CHANGE_TRACKING_CURRENT_VERSION() 52 | */ 53 | DECLARE @fromVersion INT = 3; 54 | SELECT 55 | SYS_CHANGE_OPERATION, ColorID 56 | FROM 57 | CHANGETABLE(CHANGES [Warehouse].[Colors], @fromVersion) C 58 | go 59 | 60 | /* 61 | Get Current Change Tracking Version 62 | */ 63 | SELECT CHANGE_TRACKING_CURRENT_VERSION(); 64 | GO 65 | 66 | /* 67 | Update a color 68 | */ 69 | UPDATE [Warehouse].[Colors] 70 | SET ColorName = 'Deep Blue' 71 | WHERE ColorID = 10; 72 | GO 73 | 74 | /* 75 | Get all changes. In @fromVersion set the number returned by 76 | the previous call to CHANGE_TRACKING_CURRENT_VERSION() 77 | */ 78 | DECLARE @fromVersion INT = 5; 79 | SELECT 80 | SYS_CHANGE_OPERATION, ColorID 81 | FROM 82 | CHANGETABLE(CHANGES [Warehouse].[Colors], @fromVersion) C 83 | go 84 | 85 | /* 86 | Disable Change Tracking on Table 87 | */ 88 | ALTER TABLE [Warehouse].[Colors] 89 | DISABLE CHANGE_TRACKING 90 | GO 91 | 92 | /* 93 | Disable Change Tracking on Database 94 | */ 95 | ALTER DATABASE WideWorldImportersStandard 96 | SET CHANGE_TRACKING = OFF 97 | GO 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /05-DevelopingWithAzureSQL-Advanced/09-rls.sql: -------------------------------------------------------------------------------- 1 | -- Use WideWorldImporters database 2 | 3 | /* 4 | Create a schema to be used in this example 5 | */ 6 | IF (SCHEMA_ID('rls') IS NULL) BEGIN 7 | EXEC('CREATE SCHEMA [rls]'); 8 | END 9 | GO 10 | 11 | /* 12 | Create the Inline Table-Valued Function to be used 13 | for enforcing the Security Policy 14 | */ 15 | DROP FUNCTION IF EXISTS rls.LogonSecurityPolicy; 16 | GO 17 | CREATE FUNCTION rls.LogonSecurityPolicy(@PersonID AS INT) 18 | RETURNS TABLE 19 | WITH SCHEMABINDING 20 | AS 21 | RETURN 22 | SELECT 23 | 1 As [Authorized] 24 | FROM 25 | [Application].[People] 26 | WHERE 27 | LogonName = SESSION_CONTEXT(N'Logon') 28 | AND 29 | PersonID = @PersonId 30 | GO 31 | 32 | /* 33 | Create the Security Policy 34 | */ 35 | CREATE SECURITY POLICY OrderSecurityPolicy 36 | ADD FILTER PREDICATE [rls].LogonSecurityPolicy(SalespersonPersonID) ON [Sales].[Orders], 37 | ADD BLOCK PREDICATE [rls].LogonSecurityPolicy(SalespersonPersonID) ON [Sales].[Orders] 38 | WITH (STATE = ON); 39 | 40 | /* 41 | Set a Session Context, simulating that Kayla (ID=2) logged in 42 | By setting @readOnly to False it will be possibile to simulate different Logon values 43 | without having to disconnect and reconnect 44 | */ 45 | EXEC sp_set_session_context @key=N'Logon', @value=N'kaylaw@wideworldimporters.com', @readOnly=0 46 | GO 47 | 48 | /* 49 | Kyala can only see her own orders (due to FILTER predicate) 50 | */ 51 | SELECT * FROM [Sales].[Orders] 52 | GO 53 | 54 | /* 55 | Kyala can insert/update/delete rows she has access to (due to FILTER predicate) 56 | */ 57 | UPDATE [Sales].[Orders] SET Comments = 'Happy customer!' WHERE [OrderID] = 1; 58 | 59 | /* 60 | Kyala CANNOT insert/update/delete rows she doesn't have access to (due to FILTER predicate) 61 | */ 62 | UPDATE [Sales].[Orders] SET Comments = 'Happy customer!' WHERE [OrderID] = 38; 63 | 64 | /* 65 | Kyala CANNOT change an order so that it will be moved out of her scope (due to BLOCK predicate) 66 | */ 67 | UPDATE [Sales].[Orders] SET [SalespersonPersonID] = 16 WHERE [OrderID] = 1 ; 68 | 69 | /* 70 | Simulate another user logged in 71 | */ 72 | EXEC sp_set_session_context @key=N'Logon', @value=N'archerl@wideworldimporters.com', @readOnly=0 73 | GO 74 | 75 | /* 76 | Archer can only see her own orders (due to FILTER predicate) 77 | */ 78 | SELECT * FROM [Sales].[Orders] 79 | GO 80 | 81 | /* 82 | Simulate guest login 83 | */ 84 | EXEC sp_set_session_context @key=N'Logon', @value=N'someone@email.com', @readOnly=0 85 | GO 86 | 87 | /* 88 | No orders returned, as external users are NOT authorized to see placed orders (due to FILTER predicate) 89 | */ 90 | SELECT * FROM [Sales].[Orders] 91 | GO 92 | 93 | /* 94 | Disable Security Policy 95 | */ 96 | ALTER SECURITY POLICY OrderSecurityPolicy 97 | WITH (STATE = OFF); 98 | GO 99 | 100 | /* 101 | Table is now accessible to everyone 102 | */ 103 | SELECT * FROM [Sales].[Orders] 104 | GO 105 | 106 | /* 107 | Remove Security Policy 108 | */ 109 | DROP SECURITY POLICY OrderSecurityPolicy 110 | GO 111 | 112 | /* 113 | More detailed sample available here: 114 | 115 | Creating API to securely access data using Azure SQL Row Level Security 116 | https://github.com/Azure-Samples/azure-sql-db-secure-data-access-api 117 | */ 118 | -------------------------------------------------------------------------------- /05-DevelopingWithAzureSQL-Advanced/10-dynamic-data-masking.sql: -------------------------------------------------------------------------------- 1 | -- Use WideWorldImporters database 2 | 3 | /* 4 | Table with sensitive data 5 | */ 6 | SELECT PersonId, FullName, EmailAddress FROM [Application].[People] 7 | GO 8 | 9 | /* 10 | Add Dynamic Data Masking to EmailAddress column 11 | */ 12 | ALTER TABLE [Application].[People] 13 | ALTER COLUMN EmailAddress ADD MASKED WITH (FUNCTION = 'email()') 14 | GO 15 | 16 | /* 17 | Check that user is local administrator (db_owner = 1) 18 | */ 19 | SELECT [user_name] = USER_NAME(), [db_owner] = IS_MEMBER('db_owner') 20 | GO 21 | 22 | /* 23 | Data not masked as by default a db_owner has access to masked data 24 | */ 25 | SELECT PersonId, FullName, EmailAddress FROM [Application].[People] 26 | GO 27 | 28 | /* 29 | Create sample low-priviledged user 30 | */ 31 | CREATE USER TestUser WITHOUT LOGIN; 32 | GRANT SELECT ON [Application].[People] TO TestUser; 33 | GO 34 | 35 | /* 36 | Impersonate the created user (this is possibile only if user is local admin) 37 | */ 38 | EXECUTE AS USER = 'TestUser' 39 | GO 40 | SELECT USER_NAME(), IS_MEMBER('db_owner') 41 | GO 42 | 43 | /* 44 | Data is now masked 45 | */ 46 | SELECT PersonId, FullName, EmailAddress FROM [Application].[People] 47 | GO 48 | 49 | /* 50 | User can still use original values 51 | to seach for data, for example 52 | */ 53 | SELECT PersonId, FullName, EmailAddress FROM [Application].[People] WHERE EmailAddress = 'helenm@fabrikam.com' 54 | GO 55 | 56 | /* 57 | Return to be the local admin 58 | */ 59 | REVERT 60 | GO 61 | 62 | /* 63 | Now allow TestUser to UNMASK data 64 | */ 65 | GRANT UNMASK TO TestUSer; 66 | GO 67 | 68 | /* 69 | Impersonate the created TestUser 70 | */ 71 | EXECUTE AS USER = 'TestUser' 72 | GO 73 | SELECT USER_NAME(), IS_MEMBER('db_owner') 74 | GO 75 | 76 | /* 77 | Data can now be unmasked 78 | */ 79 | SELECT PersonId, FullName, EmailAddress FROM [Application].[People] 80 | GO 81 | 82 | /* 83 | Revert back to local admin 84 | */ 85 | REVERT 86 | GO 87 | 88 | /* 89 | Drop created TestUser 90 | */ 91 | DROP USER TestUser 92 | GO 93 | -------------------------------------------------------------------------------- /05-DevelopingWithAzureSQL-Advanced/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 5 - Developing with Azure SQL: Advanced 2 | 3 | Samples for to Chapter 5 of [Practical Azure SQL Database for Modern Developers](https://www.apress.com/gp/book/9781484263693) book. 4 | 5 | Make sure to use the World Wide Importers database. 6 | 7 | You can use the samples of Chapter 2 to restore that database to Azure SQL or Azure SQL Managed Instance. 8 | -------------------------------------------------------------------------------- /05-DevelopingWithAzureSQL-Advanced/TableValuedParameters/DotNetTVP/.env.template: -------------------------------------------------------------------------------- 1 | CS_AzureSQL='Server=;Initial Catalog=;User ID=;Password=;Persist Security Info=False;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;' -------------------------------------------------------------------------------- /05-DevelopingWithAzureSQL-Advanced/TableValuedParameters/DotNetTVP/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/bin/Debug/netcoreapp3.1/DotNetTVP.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}", 16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console 17 | "console": "internalConsole", 18 | "stopAtEntry": false, 19 | "envFile": "${workspaceFolder}/.env" 20 | }, 21 | { 22 | "name": ".NET Core Attach", 23 | "type": "coreclr", 24 | "request": "attach", 25 | "processId": "${command:pickProcess}" 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /05-DevelopingWithAzureSQL-Advanced/TableValuedParameters/DotNetTVP/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/DotNetTVP.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/DotNetTVP.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "${workspaceFolder}/DotNetTVP.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /05-DevelopingWithAzureSQL-Advanced/TableValuedParameters/DotNetTVP/DotNetTVP.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /05-DevelopingWithAzureSQL-Advanced/TableValuedParameters/DotNetTVP/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using Microsoft.Data.SqlClient; 5 | 6 | namespace DotNetTVP 7 | { 8 | class Program 9 | { 10 | static void Main(string[] args) 11 | { 12 | var tags = new DataTable("PostTagsTableType"); 13 | tags.Columns.Add("Tag", typeof(string)); 14 | tags.Rows.Add("azure-sql"); 15 | tags.Rows.Add("tvp"); 16 | 17 | using(var conn = new SqlConnection(Environment.GetEnvironmentVariable("CS_AzureSQL"))) 18 | { 19 | var cmd = new SqlCommand("dbo.AddTagsToPost", conn); 20 | cmd.CommandType = CommandType.StoredProcedure; 21 | 22 | var p1 = new SqlParameter("@PostId", SqlDbType.Int); 23 | p1.Value = 1; 24 | cmd.Parameters.Add(p1); 25 | 26 | var p2 = new SqlParameter("@Tags", SqlDbType.Structured); 27 | p2.TypeName = "dbo.PostTagsTableType"; 28 | p2.Value = tags; 29 | cmd.Parameters.Add(p2); 30 | 31 | conn.Open(); 32 | cmd.ExecuteNonQuery(); 33 | conn.Close(); 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /06-PracticalUseOfTableAndIndexes/01-Create table.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Create a table 3 | Create a plain table without some advanced indexes or constraints (drop existing table if already exists). 4 | */ 5 | DROP TABLE IF EXISTS dbo.Customer; 6 | GO 7 | CREATE TABLE dbo.Customer ( 8 | CustomerID tinyint, 9 | CustomerName varchar(max), 10 | LocationID bigint 11 | ); -------------------------------------------------------------------------------- /06-PracticalUseOfTableAndIndexes/02-Change column collation.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Change the column collation 3 | */ 4 | ALTER TABLE Warehouse.StockItems 5 | ALTER COLUMN Brand NVARCHAR(50) COLLATE Serbian_Cyrillic_100_CI_AI 6 | 7 | -------------------------------------------------------------------------------- /06-PracticalUseOfTableAndIndexes/03-Add computed column.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Add a computed column as a named expression (drop existing if it already exists) 3 | */ 4 | ALTER TABLE Sales.[OrderLines] 5 | DROP COLUMN IF EXISTS Profit; 6 | GO 7 | 8 | ALTER TABLE Sales.OrderLines 9 | ADD Profit AS (Quantity*UnitPrice)*(1-TaxRate); 10 | GO 11 | 12 | /* 13 | Add a computed column as persisted automatically re-calculated column (drop existing if it already exists) 14 | */ 15 | ALTER TABLE Sales.[OrderLines] 16 | DROP COLUMN IF EXISTS Profit; 17 | GO 18 | ALTER TABLE Sales.OrderLines 19 | ADD Profit AS (Quantity*UnitPrice)*(1-TaxRate) PERSISTED; 20 | GO -------------------------------------------------------------------------------- /06-PracticalUseOfTableAndIndexes/04-Query spatial column.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Query a table with spatial column 3 | */ 4 | DECLARE @g geography = 'POINT(-95 45.5)'; 5 | 6 | SELECT TOP(5) 7 | [Location].ToString(), 8 | CityName 9 | FROM 10 | [Application].Cities 11 | ORDER BY 12 | [Location].STDistance(@g) ASC; 13 | GO 14 | 15 | /* 16 | Complete sample on Geospatial Data can also be found here: 17 | 18 | https://github.com/yorek/azure-sql-db-samples/tree/master/samples/05-spatial 19 | */ -------------------------------------------------------------------------------- /06-PracticalUseOfTableAndIndexes/05-Add foreign key constriants.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Adding foreign key constraints (dropping existing if they exist) 3 | */ 4 | ALTER TABLE Sales.Invoices 5 | DROP CONSTRAINT IF EXISTS FK_Cust_Inv 6 | ALTER TABLE Sales.Orders 7 | DROP CONSTRAINT IF EXISTS FK_Cust_Orders 8 | GO 9 | ALTER TABLE Sales.Invoices 10 | ADD CONSTRAINT FK_Cust_Inv FOREIGN KEY (CustomerID) 11 | REFERENCES Sales.Customers (CustomerID) ON DELETE CASCADE; 12 | GO 13 | ALTER TABLE Sales.Orders 14 | ADD CONSTRAINT FK_Cust_Orders FOREIGN KEY (CustomerID) 15 | REFERENCES Sales.Customers (CustomerID) ON DELETE NO ACTION; 16 | -------------------------------------------------------------------------------- /06-PracticalUseOfTableAndIndexes/06-Secure your table.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Setting security access rules on database objects 3 | */ 4 | DROP USER IF EXISTS [SalesMicroservice]; 5 | GO 6 | CREATE USER [SalesMicroservice] WITHOUT LOGIN; 7 | -- Alternative: User with password (works only in Azure SQL Database, or in the Managed Instance with contained database) 8 | -- CREATE USER [SalesMicroservice] WITH PASSWORD = 'AVERy_STR0-NGPAZZw0rd!' 9 | GO 10 | 11 | GRANT SELECT, INSERT 12 | ON OBJECT::Sales.Orders 13 | TO SalesMicroservice; 14 | GRANT SELECT 15 | ON OBJECT::Sales.Customers 16 | TO SalesMicroservice; 17 | GRANT SELECT 18 | ON OBJECT::Sales.Invoices 19 | TO SalesMicroservice; 20 | GRANT EXECUTE 21 | ON SCHEMA::Sales 22 | TO SalesMicroservice; 23 | GO 24 | 25 | /* 26 | Checking permissions 27 | */ 28 | EXECUTE AS USER = 'SalesMicroservice'; 29 | SELECT * FROM fn_my_permissions('Sales.Customers', 'OBJECT'); 30 | REVERT; -------------------------------------------------------------------------------- /06-PracticalUseOfTableAndIndexes/07-Query that can be optimized with index.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Query that can be optimized with index 3 | */ 4 | 5 | -- Create a schema to hold test tables 6 | IF (0 = (SELECT COUNT(*) FROM sys.schemas WHERE name = 'test')) 7 | EXEC sp_executesql N'CREATE SCHEMA [test]' 8 | GO 9 | 10 | DROP TABLE IF EXISTS test.Customers; 11 | DROP TABLE IF EXISTS test.Orders; 12 | 13 | -- Create some tables from existing ones, but without any index 14 | SELECT * INTO test.Customers FROM Sales.Customers 15 | SELECT * INTO test.Orders FROM Sales.Orders 16 | GO 17 | 18 | -- Return elapsed time and IO count (in the "Messages" tab) 19 | SET STATISTICS IO ON 20 | SET STATISTICS TIME ON 21 | GO 22 | 23 | -- Run a sample query 24 | SELECT 25 | c.CustomerName, 26 | c.CreditLimit, 27 | o.CustomerPurchaseOrderNumber, 28 | o.DeliveryInstructions 29 | FROM 30 | test.Customers AS c 31 | INNER JOIN test.Orders AS o 32 | ON c.CustomerID = o.CustomerID 33 | WHERE 34 | c.PostalCityID = 37886; 35 | GO 36 | 37 | /* 38 | You will see something like 39 | 40 | SQL Server parse and compile time: 41 | CPU time = 0 ms, elapsed time = 0 ms. 42 | SQL Server parse and compile time: 43 | CPU time = 125 ms, elapsed time = 141 ms. 44 | 45 | (72 rows affected) 46 | Table 'Workfile'. Scan count 0, logical reads 0, physical reads 0, page server reads 0, read-ahead reads 0, page server read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob page server reads 0, lob read-ahead reads 0, lob page server read-ahead reads 0. 47 | Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, page server reads 0, read-ahead reads 0, page server read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob page server reads 0, lob read-ahead reads 0, lob page server read-ahead reads 0. 48 | Table 'Orders'. Scan count 1, logical reads 818, physical reads 0, page server reads 0, read-ahead reads 0, page server read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob page server reads 0, lob read-ahead reads 0, lob page server read-ahead reads 0. 49 | Table 'Customers'. Scan count 1, logical reads 38, physical reads 0, page server reads 0, read-ahead reads 0, page server read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob page server reads 0, lob read-ahead reads 0, lob page server read-ahead reads 0. 50 | 51 | SQL Server Execution Times: 52 | CPU time = 16 ms, elapsed time = 21 ms. 53 | 54 | Completion time: 2020-09-19T13:10:48.5823372-07:00 55 | */ -------------------------------------------------------------------------------- /06-PracticalUseOfTableAndIndexes/08-Create index.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Creating indexes that optimize a query 3 | */ 4 | 5 | CREATE INDEX ix_Customer_PostalCityID 6 | ON test.Customers(PostalCityID); 7 | GO 8 | 9 | CREATE INDEX ix_Orders_CustomerID 10 | ON test.Orders(CustomerID); 11 | GO 12 | 13 | -- Re-Run the query now 14 | SET STATISTICS IO ON 15 | SET STATISTICS TIME ON 16 | GO 17 | 18 | SELECT 19 | c.[PostalCityID], 20 | c.CustomerName, 21 | c.CreditLimit, 22 | o.CustomerPurchaseOrderNumber, 23 | o.DeliveryInstructions 24 | FROM 25 | test.Customers AS c 26 | INNER JOIN test.Orders AS o 27 | ON c.CustomerID = o.CustomerID 28 | WHERE 29 | c.PostalCityID = 37886; 30 | GO 31 | 32 | /* 33 | You will see something like 34 | 35 | SQL Server parse and compile time: 36 | CPU time = 0 ms, elapsed time = 5 ms. 37 | 38 | (72 rows affected) 39 | Table 'Orders'. Scan count 1, logical reads 74, physical reads 0, page server reads 0, read-ahead reads 0, page server read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob page server reads 0, lob read-ahead reads 0, lob page server read-ahead reads 0. 40 | Table 'Customers'. Scan count 1, logical reads 3, physical reads 0, page server reads 0, read-ahead reads 0, page server read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob page server reads 0, lob read-ahead reads 0, lob page server read-ahead reads 0. 41 | 42 | SQL Server Execution Times: 43 | CPU time = 0 ms, elapsed time = 0 ms. 44 | 45 | Completion time: 2020-09-19T13:11:52.2126504-07:00 46 | 47 | From 818 logical reads to 74 for the Orders table. 48 | From 38 logical reads to 3 for the Customers table. 49 | CPU time from 16ms to less than 1ms. 50 | */ 51 | 52 | -- Clean up 53 | DROP TABLE test.[Customers]; 54 | DROP TABLE test.[Orders]; 55 | GO 56 | 57 | DROP SCHEMA [test] 58 | GO -------------------------------------------------------------------------------- /06-PracticalUseOfTableAndIndexes/09-Rebuild index.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Rebuilding an index that optimizes a query 3 | */ 4 | ALTER INDEX FK_Sales_Customers_DeliveryCityID 5 | ON Sales.Customers 6 | REBUILD WITH ( ONLINE = ON); 7 | GO 8 | 9 | /* 10 | Identify the indexes that need to be rebuilt or reorganized 11 | */ 12 | DECLARE @db_id int = DB_ID(); 13 | DECLARE @object_id int = OBJECT_ID('Sales.Customers'); 14 | 15 | SELECT 16 | index_id, 17 | partition_number, 18 | avg_fragmentation_in_percent 19 | FROM 20 | sys.dm_db_index_physical_stats(@db_id, @object_id, NULL, NULL , 'LIMITED'); 21 | 22 | /* 23 | Check out also the script provided here: 24 | 25 | - https://github.com/Microsoft/tigertoolbox/tree/master/MaintenanceSolution 26 | - https://github.com/microsoft/tigertoolbox/tree/master/Index-Information 27 | - https://github.com/microsoft/tigertoolbox/tree/master/Index-Creation 28 | 29 | Also make sure to check out this post: 30 | 31 | https://stackoverflow.com/questions/48681024/how-to-set-azure-sql-to-rebuild-indexes-automatically 32 | */ -------------------------------------------------------------------------------- /06-PracticalUseOfTableAndIndexes/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 3 - Practical use of Tables and Indexes 2 | 3 | Samples for to Chapter 6 of [Practical Azure SQL Database for Modern Developers](https://www.apress.com/gp/book/9781484263693) book. 4 | 5 | Make sure to use the World Wide Importers database. 6 | 7 | You can use the samples of Chapter 2 to restore that database to Azure SQL or Azure SQL Managed Instance. -------------------------------------------------------------------------------- /06-PracticalUseOfTableAndIndexes/todo.txt: -------------------------------------------------------------------------------- 1 | TODO: Create table simple sample 2 | 3 | TODO: Collations sample 4 | 5 | TODO: Computed columns 6 | 7 | TODO: Complex Type 8 | - XML 9 | - Spatial 10 | - CLR 11 | - JSON 12 | Just simple sample as more detail will be given in a next chapter 13 | 14 | TODO - Constraints 15 | PRIMARY KEY 16 | UNIQUE KEY 17 | FOREIGN KEY 18 | 19 | TODO - Security Sample 20 | 21 | TODO - Index usage sample -------------------------------------------------------------------------------- /07-ScalabilityConsistencyAndPerformances/1-PythonTransactions/.gitignore: -------------------------------------------------------------------------------- 1 | *.env -------------------------------------------------------------------------------- /07-ScalabilityConsistencyAndPerformances/1-PythonTransactions/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Python: Current File", 9 | "type": "python", 10 | "request": "launch", 11 | "program": "app.py", 12 | "console": "integratedTerminal" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /07-ScalabilityConsistencyAndPerformances/1-PythonTransactions/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.pythonPath": "/usr/bin/python3" 3 | } -------------------------------------------------------------------------------- /07-ScalabilityConsistencyAndPerformances/1-PythonTransactions/app.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pyodbc 3 | from decouple import config 4 | 5 | server = config('server') 6 | database = config('database') 7 | username = config('username') 8 | password = config('password') 9 | driver= '{ODBC Driver 17 for SQL Server}' 10 | cnxn = pyodbc.connect('DRIVER='+driver+';SERVER='+server+';PORT=1433;DATABASE='+database+';UID='+username+';PWD='+ password,autocommit=True) 11 | 12 | def prepare_database(): 13 | try: 14 | cursor = cnxn.cursor() 15 | cursor.execute("""IF (EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES 16 | WHERE TABLE_SCHEMA = 'dbo' 17 | AND TABLE_NAME = 'Orders')) 18 | BEGIN 19 | DROP TABLE [dbo].[Orders]; 20 | END 21 | 22 | IF (EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES 23 | WHERE TABLE_SCHEMA = 'dbo' 24 | AND TABLE_NAME = 'Inventory')) 25 | BEGIN 26 | DROP TABLE [dbo].[Inventory]; 27 | END 28 | 29 | CREATE TABLE [dbo].[Orders] (ID int PRIMARY KEY, ProductID int, OrderDate datetime); 30 | CREATE TABLE [dbo].[Inventory] (ProductID int PRIMARY KEY, QuantityInStock int 31 | CONSTRAINT CHK_QuantityInStock CHECK (QuantityInStock>-1)); 32 | 33 | -- Fill up with some sample values 34 | INSERT INTO dbo.Orders VALUES (1,1,getdate()); 35 | INSERT INTO dbo.Inventory VALUES (1,0); 36 | """) 37 | except pyodbc.DatabaseError as err: 38 | print("Couldn't prepare database tables") 39 | 40 | def execute_transaction(): 41 | try: 42 | cnxn.autocommit = False 43 | cursor = cnxn.cursor() 44 | cursor.execute("INSERT INTO Orders VALUES (2,1,getdate());") 45 | cursor.execute("UPDATE Inventory SET QuantityInStock=QuantityInStock-1 WHERE ProductID=1") 46 | except pyodbc.DatabaseError as err: 47 | cnxn.rollback() 48 | print("Transaction rolled back: " + str(err)) 49 | else: 50 | cnxn.commit() 51 | print("Transaction committed!") 52 | finally: 53 | cnxn.autocommit = True 54 | 55 | prepare_database() 56 | execute_transaction() -------------------------------------------------------------------------------- /07-ScalabilityConsistencyAndPerformances/2-TSQL-Transactions/transactions.sql: -------------------------------------------------------------------------------- 1 | USE AdventureWorks2012; 2 | GO 3 | 4 | IF EXISTS (SELECT name FROM sys.objects WHERE name = N'SaveTranExample') 5 | DROP PROCEDURE SaveTranExample; 6 | GO 7 | 8 | CREATE PROCEDURE SaveTranExample 9 | @InputCandidateID INT 10 | AS 11 | -- Detect whether the procedure was called 12 | -- from an active transaction and save 13 | -- that for later use. 14 | -- In the procedure, @TranCounter = 0 15 | -- means there was no active transaction 16 | -- and the procedure started one. 17 | -- @TranCounter > 0 means an active 18 | -- transaction was started before the 19 | -- procedure was called. 20 | DECLARE @TranCounter INT; 21 | SET @TranCounter = @@TRANCOUNT; 22 | 23 | IF @TranCounter > 0 24 | -- Procedure called when there is 25 | -- an active transaction. 26 | -- Create a savepoint to be able 27 | -- to roll back only the work done 28 | -- in the procedure if there is an 29 | -- error. 30 | SAVE TRANSACTION ProcedureSave; 31 | ELSE 32 | -- Procedure must start its own 33 | -- transaction. 34 | BEGIN TRANSACTION; 35 | -- Modify database. 36 | BEGIN TRY 37 | DELETE HumanResources.JobCandidate 38 | WHERE JobCandidateID = @InputCandidateID; 39 | -- Get here if no errors; must commit 40 | -- any transaction started in the 41 | -- procedure, but not commit a transaction 42 | -- started before the transaction was called. 43 | IF @TranCounter = 0 44 | -- @TranCounter = 0 means no transaction was 45 | -- started before the procedure was called. 46 | -- The procedure must commit the transaction 47 | -- it started. 48 | COMMIT TRANSACTION; 49 | END TRY 50 | BEGIN CATCH 51 | -- An error occurred; must determine 52 | -- which type of rollback will roll 53 | -- back only the work done in the 54 | -- procedure. 55 | IF @TranCounter = 0 56 | -- Transaction started in procedure. 57 | -- Roll back complete transaction. 58 | ROLLBACK TRANSACTION; 59 | ELSE 60 | -- Transaction started before procedure 61 | -- called, do not roll back modifications 62 | -- made before the procedure was called. 63 | IF XACT_STATE() <> -1 64 | -- If the transaction is still valid, just 65 | -- roll back to the savepoint set at the 66 | -- start of the stored procedure. 67 | ROLLBACK TRANSACTION ProcedureSave; 68 | -- If the transaction is uncommittable, a 69 | -- rollback to the savepoint is not allowed 70 | -- because the savepoint rollback writes to 71 | -- the log. Just return to the caller, which 72 | -- should roll back the outer transaction. 73 | -- After the appropriate rollback, echo error 74 | -- information to the caller. 75 | DECLARE @ErrorMessage NVARCHAR(4000); 76 | DECLARE @ErrorSeverity INT; 77 | DECLARE @ErrorState INT; 78 | SELECT @ErrorMessage = ERROR_MESSAGE(); 79 | SELECT @ErrorSeverity = ERROR_SEVERITY(); 80 | SELECT @ErrorState = ERROR_STATE(); 81 | RAISERROR (@ErrorMessage, -- Message text. 82 | @ErrorSeverity, -- Severity. 83 | @ErrorState -- State. 84 | ); 85 | END CATCH 86 | GO -------------------------------------------------------------------------------- /07-ScalabilityConsistencyAndPerformances/3-DotNET-Savepoints/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/bin/Debug/netcoreapp3.1/savepoints.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}", 16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console 17 | "console": "internalConsole", 18 | "stopAtEntry": false 19 | }, 20 | { 21 | "name": ".NET Core Attach", 22 | "type": "coreclr", 23 | "request": "attach", 24 | "processId": "${command:pickProcess}" 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /07-ScalabilityConsistencyAndPerformances/3-DotNET-Savepoints/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/savepoints.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/savepoints.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "${workspaceFolder}/savepoints.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /07-ScalabilityConsistencyAndPerformances/3-DotNET-Savepoints/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace savepoints 4 | { 5 | class Program 6 | { 7 | static void Main(string[] args) 8 | { 9 | 10 | 11 | 12 | Console.WriteLine("Hello World!"); 13 | } 14 | private static void ExecuteSqlTransaction(string connectionString) 15 | { 16 | using (SqlConnection connection = new SqlConnection(connectionString)) 17 | { 18 | connection.Open(); 19 | 20 | SqlCommand command = connection.CreateCommand(); 21 | SqlTransaction transaction; 22 | 23 | // Start a local transaction. 24 | transaction = connection.BeginTransaction("SampleTransaction"); 25 | 26 | // Must assign both transaction object and connection 27 | // to Command object for a pending local transaction 28 | command.Connection = connection; 29 | command.Transaction = transaction; 30 | 31 | try 32 | { 33 | command.CommandText = 34 | "Insert into Region (RegionID, RegionDescription) VALUES (100, 'Description')"; 35 | command.ExecuteNonQuery(); 36 | 37 | transaction.Save("FirstRegionInserted"); 38 | 39 | command.CommandText = 40 | "Insert into Region (RegionID, RegionDescription) VALUES (101, 'Description')"; 41 | command.ExecuteNonQuery(); 42 | 43 | // Rollback to a savepoint 44 | transaction.Rollback("FirstRegionInserted"); 45 | 46 | // Only first insert will be considered 47 | // Attempt to commit the transaction. 48 | transaction.Commit(); 49 | Console.WriteLine("Only first insert is written to database."); 50 | } 51 | catch (Exception ex) 52 | { 53 | Console.WriteLine("Commit Exception Type: {0}", ex.GetType()); 54 | Console.WriteLine(" Message: {0}", ex.Message); 55 | 56 | // Attempt to roll back the transaction. 57 | try 58 | { 59 | transaction.Rollback("SampleTransaction"); 60 | } 61 | catch (Exception ex2) 62 | { 63 | // This catch block will handle any errors that may have occurred 64 | // on the server that would cause the rollback to fail, such as 65 | // a closed connection. 66 | Console.WriteLine("Rollback Exception Type: {0}", ex2.GetType()); 67 | Console.WriteLine(" Message: {0}", ex2.Message); 68 | } 69 | } 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /07-ScalabilityConsistencyAndPerformances/3-DotNET-Savepoints/savepoints.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /07-ScalabilityConsistencyAndPerformances/4-DistributedTransactions/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.SqlClient; 3 | 4 | namespace disttran 5 | { 6 | class Program 7 | { 8 | static string connStrDb1="Server=tcp:.database.windows.net,1433;Initial Catalog=db1;Persist Security Info=False;User ID=;Password=;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"; 9 | static string connStrDb2="Server=tcp:.database.windows.net,1433;Initial Catalog=db2;Persist Security Info=False;User ID=;Password=;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"; 10 | static void Main(string[] args) 11 | { 12 | using (var scope = new TransactionScope()) 13 | { 14 | using (var conn1 = new SqlConnection(connStrDb1)) 15 | { 16 | conn1.Open(); 17 | SqlCommand cmd1 = conn1.CreateCommand(); 18 | cmd1.CommandText = string.Format("INSERT INTO tab1 VALUES(1,'aaa');"); 19 | cmd1.ExecuteNonQuery(); 20 | } 21 | 22 | using (var conn2 = new SqlConnection(connStrDb2)) 23 | { 24 | conn2.Open(); 25 | var cmd2 = conn2.CreateCommand(); 26 | cmd2.CommandText = string.Format("INSERT INTO tab1 VALUES(1,'aaa')"); 27 | cmd2.ExecuteNonQuery(); 28 | } 29 | 30 | scope.Complete(); 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /07-ScalabilityConsistencyAndPerformances/4-DistributedTransactions/disttran.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /07-ScalabilityConsistencyAndPerformances/4-DistributedTransactions/setup.sql: -------------------------------------------------------------------------------- 1 | 2 | CREATE DATABASE db1 3 | 4 | CREATE DATABASE db2 5 | 6 | 7 | CREATE TABLE tab1 (id int, val1 varchar(255)); -------------------------------------------------------------------------------- /07-ScalabilityConsistencyAndPerformances/5-NativeCompilation/nativecompilation.sql: -------------------------------------------------------------------------------- 1 | --USE WideWorldImporters-Full 2 | 3 | -- Create an In-memory OLTP optimized table 4 | CREATE TABLE InMemory.VehicleLocations 5 | ( 6 | VehicleLocationID bigint IDENTITY(1,1) PRIMARY KEY NONCLUSTERED, 7 | RegistrationNumber nvarchar(20) NOT NULL, 8 | TrackedWhen datetime2(2) NOT NULL, 9 | Longitude decimal(18,4) NOT NULL, 10 | Latitude decimal(18,4) NOT NULL 11 | ) 12 | WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_AND_DATA); 13 | GO 14 | 15 | -- Create a natively compiled stored procedure 16 | CREATE PROCEDURE InMemory.Insert50ThousandVehicleLocations 17 | WITH NATIVE_COMPILATION, SCHEMABINDING 18 | AS 19 | BEGIN ATOMIC WITH 20 | ( 21 | TRANSACTION ISOLATION LEVEL = SNAPSHOT, 22 | LANGUAGE = N'English' 23 | ) 24 | DECLARE @Counter int = 0; 25 | WHILE @Counter < 50000 26 | BEGIN 27 | INSERT InMemory.VehicleLocations 28 | (RegistrationNumber, TrackedWhen, Longitude, Latitude) 29 | VALUES 30 | (N'EA-232-JB', SYSDATETIME(), 125.4, 132.7); 31 | SET @Counter += 1; 32 | END; 33 | RETURN 0; 34 | END; 35 | GO 36 | 37 | -- Execute a natively compiled stored procedure 38 | EXECUTE InMemory.Insert50ThousandVehicleLocations -------------------------------------------------------------------------------- /07-ScalabilityConsistencyAndPerformances/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 7 - Scalability, Consistency And Performance 2 | 3 | Samples for to Chapter 7 of [Practical Azure SQL Database for Modern Developers](https://www.apress.com/gp/book/9781484263693) book. 4 | 5 | Make sure to use the World Wide Importers database. 6 | 7 | You can use the samples of Chapter 2 to restore that database to Azure SQL or Azure SQL Managed Instance. 8 | 9 | ## 1-Transactions 10 | 11 | Python sample showing how to execute explicit transactions from code leveraging pyodbc library 12 | 13 | ## 2-TSQL-Transactions 14 | 15 | How to create explicit transactions in T-SQL 16 | 17 | ## 3-Savepoints 18 | 19 | .NET application demonstrating the usage of Savepoints in transactions 20 | 21 | ## 4-DistributedTransactions 22 | 23 | .NET application executing a distributed transaction across multiple Azure SQL databases 24 | 25 | ## 5-NativeCompilation 26 | 27 | Transact-SQL script that creates In-memory OLTP table and natively compiled stored procedure -------------------------------------------------------------------------------- /08-MultiModelCapabilities/01-Get JSON data.cs: -------------------------------------------------------------------------------- 1 | public class LogAnalyticController { 2 | 3 | [HttpGet] 4 | public void CountBySeverity(){ 5 | var QUERY = @"SELECT severity, total = COUNT(*) 6 | FROM WebSite.Logs 7 | GROUP BY severity 8 | FOR JSON PATH"; 9 | connection.QueryInto(Response.Body, QUERY); 10 | } 11 | 12 | } -------------------------------------------------------------------------------- /08-MultiModelCapabilities/02-Storing JSON documents.sql: -------------------------------------------------------------------------------- 1 | /* 2 | JSON data is stored in a table as NVARCHAR column. 3 | */ 4 | DROP TABLE IF EXISTS dbo.Logs; 5 | CREATE TABLE dbo.Logs 6 | ( 7 | [_id] BIGINT NOT NULL IDENTITY 8 | ,[logDate] DATETIME2(7) NOT NULL 9 | ,[log] NVARCHAR(MAX) NOT NULL 10 | ); 11 | GO 12 | 13 | /* 14 | Optionally, set CHECK constraint that will verify that text in NVARCHAR colum is formatted as JSON. 15 | */ 16 | ALTER TABLE dbo.Logs 17 | ADD 18 | CONSTRAINT [Data should be formatted as JSON] CHECK (ISJSON([log]) = 1); 19 | GO 20 | 21 | /* 22 | Optionally, create an index on some value in JSON text. 23 | */ 24 | ALTER TABLE dbo.Logs 25 | ADD 26 | [$severity] AS CAST(JSON_VALUE([log], '$.severity') AS TINYINT); 27 | GO 28 | 29 | CREATE INDEX [ix_severity] ON dbo.Logs ([$severity]); 30 | GO 31 | 32 | /* 33 | Insert sample data 34 | */ 35 | INSERT INTO 36 | dbo.Logs ([logDate], [log]) 37 | VALUES 38 | (SYSDATETIME(), '{"severity": 10, "duration": 300, "ip": "192.168.0.1"}'), 39 | (SYSDATETIME(), '{"severity": 20, "duration": 50, "ip": "192.168.0.3"}'), 40 | (SYSDATETIME(), '{"severity": 30, "duration": 1200, "ip": "192.168.0.1"}'), 41 | (SYSDATETIME(), '{"severity": 40, "duration": 30, "ip": "192.168.0.2"}') 42 | GO 43 | 44 | SELECT * FROM dbo.Logs 45 | GO -------------------------------------------------------------------------------- /08-MultiModelCapabilities/03-Query JSON data.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Parse JSON text stored in log column using OPENJSON function to create report. 3 | */ 4 | WITH cte AS 5 | ( 6 | SELECT 7 | * 8 | FROM 9 | dbo.Logs l 10 | CROSS APPLY 11 | OPENJSON(l.log) WITH ( 12 | duration INT, 13 | ip VARCHAR(16), 14 | severity int 15 | ) 16 | ) 17 | SELECT 18 | c.ip, 19 | c.severity, 20 | AVG(c.duration) AS [avg_duration] 21 | FROM 22 | cte c 23 | GROUP BY 24 | severity, ip 25 | HAVING 26 | AVG(c.duration) > 100 27 | ORDER BY 28 | AVG(c.duration); -------------------------------------------------------------------------------- /08-MultiModelCapabilities/04-Import JSON data.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Using values from the JSON document in a query. 3 | */ 4 | CREATE OR ALTER PROCEDURE dbo.InsertDeviceLog 5 | (@msg NVARCHAR(MAX)) 6 | AS 7 | INSERT INTO dbo.Logs 8 | (logDate, log) 9 | SELECT 10 | j.logDate, 11 | j.log 12 | FROM 13 | OPENJSON(@msg) 14 | WITH 15 | ( 16 | logDate DATETIME2 '$.properties.logDate' 17 | ,log NVARCHAR(MAX) '$.info' AS JSON 18 | ) AS j; 19 | GO 20 | 21 | 22 | dbo.InsertDeviceLog '{ 23 | "properties": { 24 | "logDate": "2020-09-19 19:40:32.323" 25 | }, 26 | "info": { 27 | "severity": 10, 28 | "duration": 300, 29 | "ip": "192.168.0.100" 30 | } 31 | }' 32 | GO 33 | 34 | SELECT * FROM dbo.Logs WHERE JSON_VALUE(log, '$.ip') = '192.168.0.100' 35 | GO 36 | -------------------------------------------------------------------------------- /08-MultiModelCapabilities/05-Create graph table.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Creating Graph tables 3 | */ 4 | DROP TABLE IF EXISTS FlightLine; 5 | GO 6 | DROP TABLE IF EXISTS Airport; 7 | GO 8 | 9 | CREATE TABLE Airport ( 10 | AirportID int PRIMARY KEY, 11 | Name NVARCHAR(100), 12 | CityID int FOREIGN KEY REFERENCES Application.Cities(CityID) 13 | ) AS NODE 14 | GO 15 | 16 | CREATE TABLE Flightline ( 17 | Name NVARCHAR(10) 18 | ) AS EDGE; 19 | GO 20 | 21 | /* 22 | Define a constraint that Flightline edge can connect two Airports 23 | */ 24 | 25 | ALTER TABLE Flightline 26 | ADD CONSTRAINT [Connecting airports] 27 | CONNECTION (Airport TO Airport) 28 | ON DELETE CASCADE; 29 | GO 30 | 31 | /* 32 | Populate dummy data 33 | */ 34 | 35 | DELETE Airport 36 | DELETE FlightLine 37 | 38 | INSERT INTO 39 | Airport (AirportID, Name) 40 | VALUES (1, 'BEG'), (2, 'FRA'),(3, 'JFK'),(4,'SEA'),(5, 'OHR'), (6, 'LHR') 41 | 42 | INSERT INTO 43 | Flightline ($from_id, $to_id, Name) 44 | SELECT 45 | f.$NODE_ID, 46 | t.$NODE_ID, 47 | a.Name 48 | FROM (VALUES ('BEG', 'FRA', 'BF-4019'), 49 | ('BEG', 'LHR', 'BL-502'), 50 | ('BEG', 'JFK', 'BJ-1002'), 51 | ('FRA', 'SEA', 'FS-401'), 52 | ('JFK', 'SEA', 'JS-772'), 53 | ('JFK', 'OHR', 'JO-401'), 54 | ('OHR', 'SEA', 'OS-431'), 55 | ('LHR', 'OHR', 'LO-9301'), 56 | ('LHR', 'SEA', 'LS-702') 57 | ) as a (FromAirport, ToAirport, name) 58 | JOIN Airport f ON f.Name = a.FromAirport 59 | JOIN Airport t ON t.Name = a.ToAirport; 60 | 61 | -------------------------------------------------------------------------------- /08-MultiModelCapabilities/06-Import graph data.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Loading data into Graph table 3 | */ 4 | INSERT INTO 5 | Flightline ($from_id, $to_id, Name) 6 | SELECT 7 | f.$NODE_ID, 8 | t.$NODE_ID, 9 | a.Name 10 | FROM OPENROWSET( 11 | BULK 'data/flightlines.csv', 12 | DATA_SOURCE = 'MyAzureBlobStorage', 13 | FORMATFILE='data/flightlines.fmt', 14 | FORMATFILE_DATA_SOURCE = 'MyAzureBlobStorage') as a 15 | JOIN Airport f ON f.Name = a.FromAirport 16 | JOIN Airport t ON t.Name = a.ToAirport; 17 | -------------------------------------------------------------------------------- /08-MultiModelCapabilities/07-Query graph data.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Query graph data 3 | */ 4 | SELECT 5 | src.Name, fl.Name, dest.Name 6 | FROM 7 | Airport src, Flightline fl, Airport dest 8 | WHERE 9 | MATCH(src-(fl)->dest) 10 | AND 11 | src.Name='BEG'; 12 | 13 | WITH routes AS ( 14 | SELECT 15 | src.Name, 16 | STRING_AGG(dest.name, '->') 17 | WITHIN GROUP (GRAPH PATH) AS path, 18 | COUNT(dest.name) 19 | WITHIN GROUP (GRAPH PATH) AS stops, 20 | LAST_VALUE(fl.name) 21 | WITHIN GROUP (GRAPH PATH) AS lastFlight, 22 | LAST_VALUE(dest.name) 23 | WITHIN GROUP (GRAPH PATH) AS destination 24 | FROM 25 | Airport src, 26 | Flightline FOR PATH fl, 27 | Airport FOR PATH dest 28 | WHERE 29 | MATCH(SHORTEST_PATH(src(-(fl)->dest)+)) 30 | AND 31 | src.Name='BEG' 32 | ) 33 | SELECT 34 | path, stops, lastFlight, destination 35 | FROM 36 | routes 37 | WHERE 38 | destination = 'SEA'; -------------------------------------------------------------------------------- /08-MultiModelCapabilities/08-Graph and spatial data.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Tables with spatial columns 3 | */ 4 | DROP TABLE IF EXISTS FlightLine; 5 | GO 6 | DROP TABLE IF EXISTS Airport; 7 | GO 8 | 9 | CREATE TABLE Airport ( 10 | AirportID int PRIMARY KEY, 11 | Name NVARCHAR(100), 12 | Location GEOGRAPHY, 13 | CityID int FOREIGN KEY REFERENCES Application.Cities(CityID) 14 | ) AS NODE 15 | GO 16 | 17 | CREATE TABLE FlightLine ( 18 | FlightLineID INT IDENTITY PRIMARY KEY, 19 | Name NVARCHAR(10), 20 | Route GEOGRAPHY 21 | ) AS EDGE; 22 | 23 | DELETE Airport 24 | DELETE FlightLine 25 | 26 | INSERT INTO 27 | Airport (AirportID, Name) 28 | VALUES (1, 'BEG'), 29 | (2, 'FRA'), 30 | (3, 'JFK'), 31 | (4, 'SEA'), 32 | (5, 'OHR'), 33 | (6, 'LHR') 34 | 35 | INSERT INTO 36 | Flightline ($from_id, $to_id, Name, Route) 37 | SELECT 38 | f.$NODE_ID, 39 | t.$NODE_ID, 40 | a.Name, 41 | a.Route 42 | FROM (VALUES ('BEG', 'FRA', 'BF-4019', NULL), 43 | ('BEG', 'LHR', 'BL-502', NULL), 44 | ('BEG', 'JFK', 'BJ-1002', NULL), 45 | ('FRA', 'SEA', 'FS-401', NULL), 46 | ('JFK', 'SEA', 'JS-772', GEOGRAPHY::STGeomFromText('LINESTRING(-122.58797797518645 47.47412022983742,-76.62118110018645 35.19352895108936,-71.17196235018645 42.76472876523594)', 4326)), 47 | ('JFK', 'OHR', 'JO-401', NULL), 48 | ('OHR', 'SEA', 'OS-431', GEOGRAPHY::STGeomFromText('LINESTRING(-122.32430610018645 47.11646707768522,-92.88094672518645 39.86080982410391,-87.69539985018645 41.85480238479831)', 4326)), 49 | ('LHR', 'OHR', 'LO-9301', NULL), 50 | ('LHR', 'SEA', 'LS-702', NULL) 51 | ) as a (FromAirport, ToAirport, name, Route) 52 | JOIN Airport f ON f.Name = a.FromAirport 53 | JOIN Airport t ON t.Name = a.ToAirport; 54 | 55 | GO 56 | 57 | /* 58 | Spatial index 59 | -- Note: Table must have a primary key for the spatial index 60 | */ 61 | CREATE SPATIAL INDEX SI_Flightline_Routes 62 | ON Flightline(Route) 63 | USING GEOGRAPHY_GRID 64 | WITH ( 65 | GRIDS = ( MEDIUM, LOW, MEDIUM, HIGH ), 66 | CELLS_PER_OBJECT = 64, 67 | PAD_INDEX = ON 68 | ); 69 | -------------------------------------------------------------------------------- /08-MultiModelCapabilities/09-Query spatial data.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Spatial query: All flightlines crossing Nebraska 3 | */ 4 | DECLARE @nebraska GEOGRAPHY = ( 5 | SELECT TOP (1) Border 6 | FROM Application.StateProvinces 7 | WHERE StateProvinceName = 'Nebraska' 8 | ); 9 | 10 | SELECT 11 | * 12 | FROM 13 | FlightLine 14 | WHERE 15 | Route.STIntersects(@nebraska) = 1; 16 | GO 17 | 18 | /* 19 | Spatial query: Airports closest to the given location 20 | */ 21 | DECLARE @currentLocation GEOGRAPHY = 'POINT(-121.626 47.8315)'; 22 | 23 | SELECT TOP(5) 24 | * 25 | FROM 26 | Airport 27 | ORDER BY 28 | Location.STDistance(@currentLocation) ASC; 29 | -------------------------------------------------------------------------------- /08-MultiModelCapabilities/10-Spatial index.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Spatial index 3 | */ 4 | -- Note: Table must have a primary key for the spatial index 5 | 6 | CREATE SPATIAL INDEX SI_Flightline_Routes 7 | ON Flightline(Route) 8 | USING GEOGRAPHY_GRID 9 | WITH ( 10 | GRIDS = ( MEDIUM, LOW, MEDIUM, HIGH ), 11 | CELLS_PER_OBJECT = 64, 12 | PAD_INDEX = ON 13 | ); -------------------------------------------------------------------------------- /08-MultiModelCapabilities/11-Spatial SRID.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Spatial shapes are described using SRID 3 | */ 4 | 5 | DECLARE @g GEOGRAPHY; 6 | 7 | SET @g = GEOGRAPHY::STGeomFromText('LINESTRING(-122.360 47.656, -122.343 47.656)', 4326); 8 | 9 | SELECT @g.STSrid; 10 | -------------------------------------------------------------------------------- /08-MultiModelCapabilities/12-XML query.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Querying XML data 3 | */ 4 | 5 | DECLARE @i INT = 47; 6 | DECLARE @x xml; 7 | SET @x=' 8 | Robin 9 | Lana 10 | Merriam 11 | '; 12 | 13 | SELECT 14 | family_id = @x.value('(/Family/@id)[1]', 'int'), 15 | family_81_name = @x.value('(//row[@id=81]/name)[1]', 'varchar(20)'), 16 | family_name = @x.query('//row[@id=sql:variable("@i")]/name') 17 | SELECT 18 | family_member = xrow.value('name[1]', 'varchar(20)'), 19 | family_member_id = xrow.value('@id[1]', 'varchar(20)'), 20 | family_member_xml = xrow.query('.') 21 | FROM 22 | @x.nodes('/Family/row') AS Members(xrow) 23 | WHERE 24 | xrow.value('@id[1]', 'int') < 50 25 | AND 26 | xrow.exist('.[@id > 5]') = 1 27 | 28 | /* 29 | Insert new XML element into the existing document. 30 | */ 31 | SET @x.modify('insert Danica 32 | into (/Family)[1]') ; 33 | SELECT @x; -------------------------------------------------------------------------------- /08-MultiModelCapabilities/12-XQuery.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Querying XML data using XQuery 3 | */ 4 | 5 | DECLARE @x AS XML; 6 | SET @x = N' 7 | Robin 8 | Lana 9 | Merriam 10 | '; 11 | 12 | SELECT xrow.query( 13 | 'let $r := self::node() 14 | return {$r/name/text()}') 15 | FROM @x.nodes('/Family/row') AS Members(xrow); -------------------------------------------------------------------------------- /08-MultiModelCapabilities/13-XML index.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Adding XML index on XML column 3 | */ 4 | DROP TABLE IF EXISTS XmlDocs; 5 | GO 6 | CREATE TABLE XmlDocs ( 7 | id INT IDENTITY PRIMARY KEY, 8 | doc XML 9 | ); 10 | GO 11 | 12 | CREATE SELECTIVE XML INDEX sxi_docs 13 | ON XmlDocs(doc) 14 | FOR ( 15 | path_price = '/row/info/price' AS SQL INT, 16 | path_name = '/row/info/name' AS SQL NVARCHAR(100) 17 | ); -------------------------------------------------------------------------------- /08-MultiModelCapabilities/14-Key-value cache.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Creating memory-optimized key-value pairs table for caching purposes (drop existing if needed) 3 | */ 4 | DROP TABLE IF EXISTS Cache; 5 | GO 6 | CREATE TABLE Cache ( 7 | [key] BIGINT IDENTITY, 8 | value NVARCHAR(MAX), 9 | INDEX IX_Hash_Key HASH ([key]) WITH (BUCKET_COUNT = 100000) 10 | ) WITH ( 11 | MEMORY_OPTIMIZED = ON, 12 | DURABILITY = SCHEMA_ONLY 13 | ); -------------------------------------------------------------------------------- /08-MultiModelCapabilities/15-Query text.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Searching text values 3 | */ 4 | DECLARE @searchText NVARCHAR(20) = 'Gray'; 5 | 6 | SELECT 7 | si.StockItemID, 8 | si.StockItemName, 9 | si.Tags 10 | FROM 11 | Warehouse.StockItems AS si 12 | WHERE 13 | si.SearchDetails LIKE N'%' + @SearchText + N'%' 14 | OR si.Tags LIKE N'%' + @SearchText + N'%'; 15 | -------------------------------------------------------------------------------- /08-MultiModelCapabilities/16-Full-text index.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Creating full-text index 3 | */ 4 | IF (0 = (SELECT COUNT(*) FROM sys.fulltext_catalogs WHERE name = 'Main')) 5 | CREATE FULLTEXT CATALOG [Main] AS DEFAULT; 6 | GO 7 | 8 | CREATE FULLTEXT INDEX 9 | ON Warehouse.StockItems (SearchDetails, CustomFields, Tags) 10 | KEY INDEX PK_Warehouse_StockItems 11 | WITH CHANGE_TRACKING AUTO; 12 | GO 13 | 14 | -------------------------------------------------------------------------------- /08-MultiModelCapabilities/17-Full-text search.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Searching indexed items using full-text search 3 | */ 4 | DECLARE @SearchCondition NVARCHAR(200) = 'blue car'; 5 | 6 | SELECT 7 | StockItemID = ft.[KEY], 8 | ft.[RANK] 9 | FROM 10 | FREETEXTTABLE(Warehouse.StockItems, SearchDetails, @SearchCondition) AS ft 11 | GO 12 | 13 | DECLARE @SearchCondition NVARCHAR(200) = 'blue AND car'; 14 | 15 | SELECT 16 | si.StockItemID, 17 | si.StockItemName, 18 | ft.[RANK] 19 | FROM 20 | Warehouse.StockItems AS si 21 | INNER JOIN 22 | CONTAINSTABLE(Warehouse.StockItems, SearchDetails, @SearchCondition) AS ft 23 | ON si.StockItemID = ft.[KEY] 24 | ORDER BY 25 | ft.[RANK]; 26 | GO 27 | 28 | DECLARE @SearchCondition NVARCHAR(200) = 'FORMSOF(INFLECTIONAL,children) OR car'; 29 | 30 | SELECT 31 | si.StockItemID, 32 | si.StockItemName, 33 | si.SearchDetails 34 | FROM 35 | Warehouse.StockItems AS si 36 | WHERE 37 | CONTAINS(SearchDetails, @SearchCondition); 38 | GO -------------------------------------------------------------------------------- /08-MultiModelCapabilities/18-Query JSON text using full-text search.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Searching JSON text using full-text search 3 | */ 4 | 5 | SELECT 6 | si.StockItemID, 7 | si.StockItemName, 8 | si.Tags 9 | FROM 10 | Warehouse.StockItems AS si 11 | WHERE 12 | CONTAINS(CustomFields, 13 | 'NEAR((CountryOfManufacture,USA),1)'); 14 | GO 15 | 16 | SELECT 17 | * 18 | FROM 19 | Warehouse.StockItems 20 | WHERE 21 | CONTAINS(CustomFields, 'NEAR((CountryOfManufacture,USA),1)') 22 | AND 23 | JSON_VALUE(CustomFields,'$.CountryOfManufacture') = 'USA'; 24 | -------------------------------------------------------------------------------- /08-MultiModelCapabilities/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 8 - Multi-Model Capabilities 2 | 3 | Samples for to Chapter 8 of [Practical Azure SQL Database for Modern Developers](https://www.apress.com/gp/book/9781484263693) book. 4 | 5 | Make sure to use the World Wide Importers database. 6 | 7 | You can use the samples of Chapter 2 to restore that database to Azure SQL or Azure SQL Managed Instance. 8 | -------------------------------------------------------------------------------- /09-MoreThanTables/00-Setup script.sql: -------------------------------------------------------------------------------- 1 | IF ( 0 = (SELECT COUNT(*) FROM sys.schemas WHERE name = 'Sales')) 2 | BEGIN 3 | EXEC sp_executesql N'CREATE SCHEMA Sales'; 4 | END 5 | GO 6 | DROP TABLE IF EXISTS Sales.Products; 7 | GO 8 | CREATE TABLE Sales.Products( 9 | ProductID int, 10 | State varchar(20), 11 | Price float 12 | ); 13 | GO 14 | CREATE TABLE dbo.Sales( 15 | SalesID int, 16 | Price float, 17 | Description nvarchar(4000), 18 | State varchar(20), 19 | Quantity int, 20 | [Date] date, 21 | Reference varchar(20) 22 | ) 23 | GO 24 | 25 | CREATE TABLE Employee ( 26 | EmployeeID int PRIMARY KEY , 27 | Name varchar(50) NOT NULL, 28 | Address varchar(50) NOT NULL, 29 | BirthDay datetime NULL, 30 | Position varchar(50) NULL, 31 | Department varchar(50) NULL, 32 | AnnualSalary float NULL, 33 | SysStartTime DATETIME2 GENERATED ALWAYS AS ROW START NOT NULL, 34 | SysEndTime DATETIME2 GENERATED ALWAYS AS ROW END NOT NULL, 35 | PERIOD FOR SYSTEM_TIME (SysStartTime,SysEndTime) 36 | ) WITH ( SYSTEM_VERSIONING = ON ( HISTORY_TABLE = dbo.EmployeeHistory) ); 37 | GO 38 | 39 | INSERT INTO Sales.Products(ProductID, State, Price) 40 | VALUES (1,'Available', 12.7), (2,'Available', 25.2), 41 | (3,'Available', 18.5), (4,'Not available', NULL), 42 | (5,'In Stock', 10.5), (6,'In Stock', 37.4) 43 | GO 44 | 45 | INSERT INTO dbo.Employee(EmployeeID, Name , Address, Position, Department) 46 | VALUES (1, 'Davide', '1st street', 'Program Manager', 'Azure SQL'), 47 | (2, 'Anna', '2nd street', 'Data Scientist', NULL), 48 | (3, 'Silvano', '3rd street', 'Program Manager', 'Azure SQL'), 49 | (4, 'Sanjay', '4th street', NULL , 'Azure SQL'), 50 | (5, 'Jovan', '5th street', NULL, NULL) 51 | GO 52 | -------------------------------------------------------------------------------- /09-MoreThanTables/01-Analytic query.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Select data from table 3 | Selects Products and returns average Price grouped by State column. 4 | */ 5 | SELECT 6 | State, 7 | Price = AVG(Price) 8 | FROM 9 | Sales.Products 10 | GROUP BY 11 | State; -------------------------------------------------------------------------------- /09-MoreThanTables/02-Query columnstore structure.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Select data from table 3 | Selects Products that have some values of State column 4 | and returns average Price grouped by State column. 5 | */ 6 | SELECT 7 | State, Price = AVG(Price) 8 | FROM 9 | Sales.Products 10 | WHERE 11 | State IN ('Available', 'In Stock') 12 | GROUP BY 13 | State; -------------------------------------------------------------------------------- /09-MoreThanTables/03-Create table with clustered columnstore index.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Creating table with columnstore index 3 | Adding CLUSTERED COLUMNSTORE index on a table creates column-organized structure. 4 | */ 5 | DROP TABLE IF EXISTS dbo.Orders; 6 | GO 7 | 8 | CREATE TABLE dbo.Orders ( 9 | OrderID int NOT NULL, 10 | CustomerID int NOT NULL, 11 | OrderDate date NOT NULL, 12 | INDEX cci CLUSTERED COLUMNSTORE 13 | ) -------------------------------------------------------------------------------- /09-MoreThanTables/04-Add clustered columnstore index on table.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Converting a table to columnstore format 3 | Adding CLUSTERED COLUMNSTORE index on an existing table creates column-organized structure. 4 | */ 5 | DROP TABLE IF EXISTS dbo.Orders; 6 | GO 7 | 8 | CREATE TABLE dbo.Orders ( 9 | OrderID int NOT NULL, 10 | CustomerID int NOT NULL, 11 | OrderDate date NOT NULL 12 | ); 13 | GO 14 | 15 | CREATE CLUSTERED COLUMNSTORE INDEX cci ON dbo.Orders; -------------------------------------------------------------------------------- /09-MoreThanTables/05-Reorganize columnstore index.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Reorganizes CLUSTERED COLUMNSTORE index 3 | */ 4 | ALTER INDEX cci 5 | ON dbo.Orders 6 | REORGANIZE; -------------------------------------------------------------------------------- /09-MoreThanTables/06-Create table with nonclustered columnstore index.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Creating NONCLUSTERED COLUMNSTORE index 3 | Adding NONCLUSTERED COLUMNSTORE index on a table additional copy of cells in column-organized structure. 4 | */ 5 | DROP TABLE IF EXISTS dbo.Sales; 6 | GO 7 | 8 | CREATE TABLE dbo.Sales ( 9 | SalesID int, 10 | Price float, 11 | Description nvarchar(4000), 12 | State varchar(20), 13 | Quantity int, 14 | INDEX ncci NONCLUSTERED COLUMNSTORE (Price, Quantity, SalesID) 15 | ); -------------------------------------------------------------------------------- /09-MoreThanTables/07-Create memory-optimized table.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Creating memory-optimized table 3 | */ 4 | DROP TABLE IF EXISTS dbo.Cache; 5 | GO 6 | 7 | CREATE TABLE dbo.Cache 8 | ( 9 | [key] INT IDENTITY PRIMARY KEY NONCLUSTERED, 10 | [data] NVARCHAR(MAX) 11 | ) 12 | WITH (MEMORY_OPTIMIZED=ON) -------------------------------------------------------------------------------- /09-MoreThanTables/08-Create non-durable memory-optimized table.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Creating memory-optimized non-durable table 3 | */ 4 | DROP TABLE IF EXISTS dbo.Cache; 5 | GO 6 | 7 | CREATE TABLE dbo.Cache 8 | ( 9 | [key] INT IDENTITY PRIMARY KEY NONCLUSTERED, 10 | [data] NVARCHAR(MAX) 11 | ) 12 | WITH (MEMORY_OPTIMIZED=ON, DURABILITY=SCHEMA_ONLY) 13 | 14 | 15 | -------------------------------------------------------------------------------- /09-MoreThanTables/09-Create memory-optimized hash table.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Creating memory-optimized hash table 3 | */ 4 | DROP TABLE IF EXISTS dbo.Employees; 5 | GO 6 | 7 | CREATE TABLE dbo.Employees( 8 | EmpID int NOT NULL 9 | CONSTRAINT PK_Employees_EmpID PRIMARY KEY 10 | NONCLUSTERED HASH (EmpID) WITH (BUCKET_COUNT = 100000), 11 | EmpName varchar(50) NOT NULL, 12 | EmpAddress varchar(50) NOT NULL, 13 | EmpDepartmentID int NOT NULL, 14 | EmpBirthDay datetime NULL 15 | ) WITH (MEMORY_OPTIMIZED = ON) 16 | GO 17 | 18 | -- Sample code that populates the table 19 | INSERT INTO dbo.Employees(EmpID, EmpName, EmpAddress, EmpDepartmentID) 20 | VALUES (1, 'Davide', '1st street', 1), (2, 'Anna', '2nd street', 2), 21 | (3, 'Silvano', '3rd street', 1), (4, 'Sanjay', '4th street', 1), 22 | (5, 'Jovan', '5th street', 3) 23 | GO 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /09-MoreThanTables/10-Create memory-optimized columnstore table.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Creating memory-optimized columnstore table 3 | */ 4 | DROP TABLE IF EXISTS dbo.Accounts; 5 | GO 6 | 7 | CREATE TABLE dbo.Accounts ( 8 | AccountKey int NOT NULL PRIMARY KEY NONCLUSTERED, 9 | Description nvarchar (50), 10 | Type nvarchar(50), 11 | UnitSold int, 12 | INDEX cci CLUSTERED COLUMNSTORE 13 | ) WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_AND_DATA) 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /09-MoreThanTables/11-Memory-optimized snapshot hint.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Using snapshot isolation on memory-optimized table 3 | */ 4 | SET TRANSACTION ISOLATION LEVEL READ COMMITTED; 5 | GO 6 | 7 | BEGIN TRANSACTION; -- Explicit transaction. 8 | 9 | -- Employees is a memory-optimized table. 10 | SELECT * 11 | FROM dbo.Employees as e WITH (SNAPSHOT) -- Table hint. 12 | 13 | COMMIT TRANSACTION; -------------------------------------------------------------------------------- /09-MoreThanTables/12-Natively compiled procedure template.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Creating natively compiled procedure 3 | */ 4 | DROP PROCEDURE IF EXISTS dbo.myProcedure; 5 | GO 6 | 7 | CREATE PROCEDURE dbo.myProcedure(@p1 int NOT NULL, @p2 nvarchar(5)) 8 | WITH NATIVE_COMPILATION, SCHEMABINDING 9 | AS BEGIN ATOMIC WITH 10 | ( TRANSACTION ISOLATION LEVEL = SNAPSHOT, 11 | LANGUAGE = N'us_english') 12 | 13 | /* Procedure code goes here - example: */ 14 | SELECT a = 1; 15 | 16 | END -------------------------------------------------------------------------------- /09-MoreThanTables/13-Natively compiled JSON function.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Creating memory-optimized table-value function: 3 | */ 4 | DROP FUNCTION IF EXISTS dbo.PeopleData; 5 | GO 6 | 7 | CREATE FUNCTION dbo.PeopleData(@json nvarchar(max)) 8 | RETURNS TABLE 9 | WITH NATIVE_COMPILATION, SCHEMABINDING 10 | AS RETURN ( 11 | SELECT Title, HireDate, PrimarySalesTerritory, 12 | CommissionRate, OtherLanguages 13 | FROM OPENJSON(@json) 14 | WITH(Title nvarchar(50), 15 | HireDate datetime2, 16 | PrimarySalesTerritory nvarchar(50), 17 | CommissionRate float, 18 | OtherLanguages nvarchar(max) AS JSON) 19 | ) -------------------------------------------------------------------------------- /09-MoreThanTables/14-Temporal query on system versioned table.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Temporal query on system versioned table 3 | This query finds a version of a row in Employee table 4 | with the specified identifier and point in time in history 5 | */ 6 | DECLARE @asOf datetime2 = '2020-10-10'; 7 | DECLARE @EmployeeID int = 42; 8 | 9 | SELECT 10 | * 11 | FROM 12 | dbo.Employee FOR SYSTEM_TIME AS OF @asOf AS History 13 | WHERE 14 | EmployeeID = @EmployeeID 15 | -------------------------------------------------------------------------------- /09-MoreThanTables/15-Error correction in system versioned table.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Error correction on system versioned table 3 | This query finds a version of row in Employee table 4 | with the specified identifier and point in time in history 5 | and overwrites the current cells in the row 6 | */ 7 | 8 | DECLARE @asOf datetime2 = DATEADD(MINUTE, -1, GETUTCDATE()); 9 | DECLARE @EmployeeID int = 1; 10 | 11 | -- Step 1 - "accidentally" enter wrong values 12 | UPDATE 13 | Employee 14 | SET 15 | Position = NULL, 16 | Department = NULL, 17 | Address = '' 18 | WHERE 19 | EmployeeID = @EmployeeID; 20 | 21 | -- Step 2 - Verify that you see wrong values 22 | SELECT 23 | EmployeeID, Position, Department, Address 24 | FROM 25 | Employee E 26 | WHERE 27 | E.EmployeeID = @EmployeeID 28 | 29 | -- Step 3 - Make corection by overwriting data with older version 30 | UPDATE 31 | E 32 | SET 33 | Position = History.Position, 34 | Department = History.Department, 35 | Address = History.Address, 36 | AnnualSalary = History.AnnualSalary 37 | FROM 38 | Employee AS E 39 | JOIN 40 | Employee FOR SYSTEM_TIME AS OF @asOf AS History 41 | ON E.EmployeeID = History.EmployeeID 42 | WHERE 43 | E.EmployeeID = @EmployeeID 44 | 45 | -- Step 4 - See recovered data 46 | SELECT 47 | EmployeeID, Position, Department, Address 48 | FROM 49 | Employee E 50 | WHERE 51 | E.EmployeeID = @EmployeeID 52 | -------------------------------------------------------------------------------- /09-MoreThanTables/16-Optimize temporal table.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Optimizing temporal tables 3 | This query optimizes system-versioned history table by adding the following indexes: 4 | - Clustered columnstore index on history table 5 | - Nonclustered B-Tree index on time and identifier columns in history table. 6 | */ 7 | CREATE CLUSTERED COLUMNSTORE INDEX cci_EmployeeHistory 8 | ON dbo.EmployeeHistory; 9 | 10 | CREATE NONCLUSTERED INDEX IX_EmployeeHistory_ID_PERIOD_COLUMNS 11 | ON EmployeeHistory (SysEndTime, SysStartTime, EmployeeID); 12 | 13 | -------------------------------------------------------------------------------- /09-MoreThanTables/17-Set retention period on temporal table.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Database history retention 3 | This query enables automatic data retention rules on system versioned tables. 4 | */ 5 | ALTER DATABASE current 6 | SET TEMPORAL_HISTORY_RETENTION ON; 7 | 8 | /* 9 | Data retention 10 | This query enables automatic data retention on system versioned tables. 11 | */ 12 | ALTER TABLE dbo.Employee 13 | SET (SYSTEM_VERSIONING = ON (HISTORY_RETENTION_PERIOD = 9 MONTHS) ); 14 | 15 | 16 | -------------------------------------------------------------------------------- /09-MoreThanTables/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 09 - More Than Tables 2 | 3 | Samples for to Chapter 9 of [Practical Azure SQL Database for Modern Developers](https://www.apress.com/gp/book/9781484263693) book. 4 | 5 | Make sure to use the World Wide Importers database. 6 | 7 | You can use the samples of Chapter 2 to restore that database to Azure SQL or Azure SQL Managed Instance. 8 | -------------------------------------------------------------------------------- /10-MonitoringAndDebugging/2-AppInsightSpring/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "java.configuration.updateBuildConfiguration": "automatic" 3 | } -------------------------------------------------------------------------------- /10-MonitoringAndDebugging/2-AppInsightSpring/demo/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /10-MonitoringAndDebugging/2-AppInsightSpring/demo/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yorek/practical-azure-sql-db-for-modern-developers/309f04802c5c1fd38d08d78d86b72e359da84537/10-MonitoringAndDebugging/2-AppInsightSpring/demo/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /10-MonitoringAndDebugging/2-AppInsightSpring/demo/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /10-MonitoringAndDebugging/2-AppInsightSpring/demo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.3.5.RELEASE 9 | 10 | 11 | com.practicalsqldb 12 | demo 13 | 0.0.1-SNAPSHOT 14 | demo 15 | Demo project for Spring Boot 16 | 17 | 18 | 11 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-web 25 | 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-data-jdbc 30 | 31 | 32 | 33 | com.microsoft.sqlserver 34 | mssql-jdbc 35 | runtime 36 | 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter 41 | 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-starter-test 46 | test 47 | 48 | 49 | org.junit.vintage 50 | junit-vintage-engine 51 | 52 | 53 | 54 | 55 | 56 | com.microsoft.azure 57 | applicationinsights-spring-boot-starter 58 | 1.1.1 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | org.springframework.boot 67 | spring-boot-maven-plugin 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /10-MonitoringAndDebugging/2-AppInsightSpring/demo/src/main/java/com/practicalsqldb/demo/DemoApplication.java: -------------------------------------------------------------------------------- 1 | package com.practicalsqldb.demo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.data.jdbc.repository.config.*; 6 | 7 | @SpringBootApplication 8 | @EnableJdbcRepositories 9 | public class DemoApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(DemoApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /10-MonitoringAndDebugging/2-AppInsightSpring/demo/src/main/java/com/practicalsqldb/demo/controllers/CustomerController.java: -------------------------------------------------------------------------------- 1 | package com.practicalsqldb.demo.controllers; 2 | 3 | import com.microsoft.applicationinsights.TelemetryClient; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.web.server.*; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | import com.microsoft.applicationinsights.telemetry.Duration; 11 | import com.practicalsqldb.demo.repositories.*; 12 | import com.practicalsqldb.demo.models.*; 13 | 14 | import java.util.*; 15 | 16 | @RestController 17 | @RequestMapping("/") 18 | public class CustomerController { 19 | 20 | @Autowired 21 | TelemetryClient telemetryClient; 22 | 23 | @Autowired 24 | private final CustomerRepository customerRepository; 25 | 26 | public CustomerController(CustomerRepository customerRepository) { 27 | this.customerRepository = customerRepository; 28 | } 29 | 30 | @GetMapping("/customer") 31 | public Iterable getCustomers() { 32 | 33 | try { 34 | 35 | List customers; 36 | 37 | //track a custom event 38 | telemetryClient.trackEvent("URI /customer is triggered"); 39 | 40 | //trace a custom trace 41 | telemetryClient.trackTrace("Sending a custom trace...."); 42 | 43 | // measure DB query benchmark 44 | long startTime = System.currentTimeMillis(); 45 | customers = customerRepository.findAll(); 46 | long endTime = System.currentTimeMillis(); 47 | int duration = (int)((endTime-startTime)); 48 | 49 | //track a custom dependency 50 | telemetryClient.trackDependency("SQL", "Insert", new Duration(0, 0, 0, 0, duration), true); 51 | 52 | telemetryClient.trackMetric("DB query", duration); 53 | 54 | return customers; 55 | } catch (Exception e) { 56 | // send exception information 57 | telemetryClient.trackEvent("Error"); 58 | telemetryClient.trackTrace("Exception: " + e.getMessage()); 59 | throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR,e.getMessage()); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /10-MonitoringAndDebugging/2-AppInsightSpring/demo/src/main/java/com/practicalsqldb/demo/models/Customer.java: -------------------------------------------------------------------------------- 1 | package com.practicalsqldb.demo.models; 2 | 3 | import org.springframework.data.annotation.Id; 4 | import org.springframework.data.relational.core.mapping.Column; 5 | import org.springframework.data.relational.core.mapping.Table; 6 | 7 | @Table("Customer") 8 | public class Customer { 9 | 10 | @Id 11 | @Column("CustomerID") 12 | public Integer CustomerID; 13 | 14 | @Column("CustomerName") 15 | public String CustomerName; 16 | } 17 | -------------------------------------------------------------------------------- /10-MonitoringAndDebugging/2-AppInsightSpring/demo/src/main/java/com/practicalsqldb/demo/repositories/CustomerRepository.java: -------------------------------------------------------------------------------- 1 | package com.practicalsqldb.demo.repositories; 2 | 3 | import java.util.List; 4 | import org.springframework.data.repository.CrudRepository; 5 | import org.springframework.stereotype.Repository; 6 | import org.springframework.data.jdbc.repository.query.*; 7 | 8 | import com.practicalsqldb.demo.models.Customer; 9 | 10 | @Repository 11 | public interface CustomerRepository extends CrudRepository { 12 | @Query("SELECT TOP 10 CustomerID, CustomerName FROM Sales.Customers") 13 | List findAll(); 14 | } -------------------------------------------------------------------------------- /10-MonitoringAndDebugging/2-AppInsightSpring/demo/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | 2 | # Specify the instrumentation key of your Application Insights resource. 3 | azure.application-insights.instrumentation-key= 4 | # Specify the name of your springboot application. This can be any logical name you would like to give to your app. 5 | spring.application.name=demo 6 | 7 | logging.level.org.springframework.jdbc.core=DEBUG 8 | 9 | spring.datasource.url=jdbc:sqlserver://.database.windows.net:1433;database=WideWorldImporters-Full;encrypt=true;trustServerCertificate=false;hostNameInCertificate=*.database.windows.net;loginTimeout=30; 10 | spring.datasource.username= 11 | spring.datasource.password= 12 | 13 | spring.jpa.show-sql=true 14 | spring.jpa.hibernate.ddl-auto=none 15 | 16 | spring.datasource.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver 17 | spring.jpa.hibernate.dialect=org.hibernate.dialect.SQLServer2012Dialect -------------------------------------------------------------------------------- /10-MonitoringAndDebugging/2-AppInsightSpring/demo/src/test/java/com/practicalsqldb/demo/DemoApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.practicalsqldb.demo; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class DemoApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /10-MonitoringAndDebugging/3-AppInsightDotnet/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (web)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/bin/Debug/netcoreapp3.1/appinsight.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}", 16 | "stopAtEntry": false, 17 | // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser 18 | "serverReadyAction": { 19 | "action": "openExternally", 20 | "pattern": "\\bNow listening on:\\s+(https?://\\S+)" 21 | }, 22 | "env": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | }, 25 | "sourceFileMap": { 26 | "/Views": "${workspaceFolder}/Views" 27 | } 28 | }, 29 | { 30 | "name": ".NET Core Attach", 31 | "type": "coreclr", 32 | "request": "attach", 33 | "processId": "${command:pickProcess}" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /10-MonitoringAndDebugging/3-AppInsightDotnet/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/appinsight.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/appinsight.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "${workspaceFolder}/appinsight.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /10-MonitoringAndDebugging/3-AppInsightDotnet/Contexts/WWImportersContext.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.Extensions.Logging; 4 | 5 | namespace appinsight 6 | { 7 | public class WWImportersContext : DbContext 8 | { 9 | public static readonly ILoggerFactory MyLoggerFactory 10 | = LoggerFactory.Create(builder => { 11 | builder 12 | .AddFilter((category, level) => 13 | category == DbLoggerCategory.Database.Command.Name 14 | && level == LogLevel.Information) 15 | .AddConsole(); 16 | }); 17 | 18 | public DbSet Customers {get;set;} 19 | public WWImportersContext (DbContextOptions options) : base(options) 20 | {} 21 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 22 | { 23 | optionsBuilder 24 | .UseLoggerFactory(MyLoggerFactory); 25 | } 26 | protected override void OnModelCreating(ModelBuilder modelBuilder) 27 | { 28 | modelBuilder.Entity(o => o.ToTable("Customers","Sales")); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /10-MonitoringAndDebugging/3-AppInsightDotnet/Controllers/CustomerController.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace appinsight 7 | { 8 | [ApiController] 9 | [Route("[controller]")] 10 | public class CustomerController : ControllerBase 11 | { 12 | private readonly WWImportersContext _context; 13 | 14 | private readonly ILogger _logger; 15 | 16 | public CustomerController(ILogger logger,WWImportersContext context) 17 | { 18 | _logger = logger; 19 | _context = context; 20 | } 21 | 22 | [HttpGet] 23 | public IEnumerable Get() 24 | { 25 | return _context.Customers.Take(10).ToList(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /10-MonitoringAndDebugging/3-AppInsightDotnet/Models/Customer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace appinsight 4 | { 5 | public class Customer 6 | { 7 | public int CustomerID { get; set; } 8 | public string CustomerName { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /10-MonitoringAndDebugging/3-AppInsightDotnet/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.Hosting; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace appinsight 11 | { 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | CreateHostBuilder(args).Build().Run(); 17 | } 18 | 19 | public static IHostBuilder CreateHostBuilder(string[] args) => 20 | Host.CreateDefaultBuilder(args) 21 | .ConfigureWebHostDefaults(webBuilder => 22 | { 23 | webBuilder.UseStartup(); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /10-MonitoringAndDebugging/3-AppInsightDotnet/Repositories/CustomerRepository.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yorek/practical-azure-sql-db-for-modern-developers/309f04802c5c1fd38d08d78d86b72e359da84537/10-MonitoringAndDebugging/3-AppInsightDotnet/Repositories/CustomerRepository.cs -------------------------------------------------------------------------------- /10-MonitoringAndDebugging/3-AppInsightDotnet/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.Extensions.Configuration; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Microsoft.Extensions.Hosting; 10 | using Microsoft.Extensions.Logging; 11 | using Microsoft.ApplicationInsights.DependencyCollector; 12 | using Microsoft.EntityFrameworkCore; 13 | 14 | namespace appinsight 15 | { 16 | public class Startup 17 | { 18 | public Startup(IConfiguration configuration) 19 | { 20 | Configuration = configuration; 21 | } 22 | 23 | public IConfiguration Configuration { get; } 24 | 25 | // This method gets called by the runtime. Use this method to add services to the container. 26 | public void ConfigureServices(IServiceCollection services) 27 | { 28 | services.AddApplicationInsightsTelemetry(); 29 | services.ConfigureTelemetryModule((module, o) => { module. EnableSqlCommandTextInstrumentation = true; }); 30 | 31 | services.AddDbContext(options => 32 | options.UseSqlServer(Configuration.GetConnectionString("WWIDatabase"))); 33 | 34 | services.AddControllers(); 35 | } 36 | 37 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 38 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 39 | { 40 | if (env.IsDevelopment()) 41 | { 42 | app.UseDeveloperExceptionPage(); 43 | } 44 | 45 | app.UseHttpsRedirection(); 46 | 47 | app.UseRouting(); 48 | 49 | app.UseAuthorization(); 50 | 51 | app.UseEndpoints(endpoints => 52 | { 53 | endpoints.MapControllers(); 54 | }); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /10-MonitoringAndDebugging/3-AppInsightDotnet/appinsight.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /10-MonitoringAndDebugging/3-AppInsightDotnet/applicationInsights.config: -------------------------------------------------------------------------------- 1 | 2 | true 3 | -------------------------------------------------------------------------------- /10-MonitoringAndDebugging/3-AppInsightDotnet/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "ApplicationInsights": { 3 | "InstrumentationKey": "[InstrumentationKey]" 4 | }, 5 | "Logging": { 6 | "LogLevel": { 7 | "Default": "Information", 8 | "Microsoft": "Warning", 9 | "Microsoft.Hosting.Lifetime": "Information" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /10-MonitoringAndDebugging/3-AppInsightDotnet/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "WWIDatabase": "Server=tcp:.database.windows.net,1433;Initial Catalog=WideWorldImporters-Full;User ID=;Password=;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;" 4 | }, 5 | "ApplicationInsights": { 6 | "InstrumentationKey": "[InstrumentationKey]" 7 | }, 8 | "Logging": { 9 | "LogLevel": { 10 | "Default": "Information", 11 | "Microsoft": "Warning", 12 | "Microsoft.Hosting.Lifetime": "Information" 13 | } 14 | }, 15 | "AllowedHosts": "*" 16 | } 17 | -------------------------------------------------------------------------------- /10-MonitoringAndDebugging/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 10 - Monitoring and Debugging 2 | 3 | Samples for to Chapter 10 of [Practical Azure SQL Database for Modern Developers](https://www.apress.com/gp/book/9781484263693) book. 4 | 5 | Make sure to use the World Wide Importers database. 6 | 7 | You can use the samples of Chapter 2 to restore that database to Azure SQL or Azure SQL Managed Instance. 8 | 9 | ## 1-DMV 10 | 11 | Examples of commonly used DMVs to monitor and troubleshoot performance issues 12 | 13 | ## 2-AppInsightSpring 14 | 15 | Integration of Application Insights with Spring Data to monitor Azure SQL workloads 16 | 17 | ## 3-AppInsightDotnet 18 | 19 | Integration of ApplicationInsight with Entity Framework Core to monitor Azure SQL workloads -------------------------------------------------------------------------------- /11-DevOpsWithAzureSQL/.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Database 2 | on: 3 | push: 4 | branches: [ master ] 5 | pull_request: 6 | branches: [ master ] 7 | jobs: 8 | Deploy: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout Repo 12 | uses: actions/checkout@v2 13 | - name: Setup .NET Core 14 | uses: actions/setup-dotnet@v1.4.0 15 | with: 16 | dotnet-version: '3.1.100' 17 | - name: Restore dependencies 18 | working-directory: db-deploy 19 | run: dotnet restore 20 | - name: Deploy 21 | working-directory: db-deploy 22 | env: 23 | ConnectionString: ${{ secrets.AZURE_SQL_CONNECTION_STRING }} 24 | run: dotnet run 25 | Test: 26 | runs-on: ubuntu-latest 27 | needs: [Deploy] 28 | steps: 29 | - name: Checkout Repo 30 | uses: actions/checkout@v2 31 | - name: Setup .NET Core 32 | uses: actions/setup-dotnet@v1.4.0 33 | with: 34 | dotnet-version: '3.1.100' 35 | - name: Restore dependencies 36 | working-directory: db-test 37 | run: dotnet restore 38 | - name: Run Tests 39 | working-directory: db-test 40 | env: 41 | ConnectionString: ${{ secrets.AZURE_SQL_CONNECTION_STRING }} 42 | run: dotnet test 43 | 44 | 45 | -------------------------------------------------------------------------------- /11-DevOpsWithAzureSQL/ch11.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "db-test", "db-test\db-test.csproj", "{FBA399DC-A346-409E-8D14-39F012B2A1A5}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "db-deploy", "db-deploy\db-deploy.csproj", "{FD698C2A-937B-4733-B07F-2F297B2C8166}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Debug|x64 = Debug|x64 14 | Debug|x86 = Debug|x86 15 | Release|Any CPU = Release|Any CPU 16 | Release|x64 = Release|x64 17 | Release|x86 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {FBA399DC-A346-409E-8D14-39F012B2A1A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {FBA399DC-A346-409E-8D14-39F012B2A1A5}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {FBA399DC-A346-409E-8D14-39F012B2A1A5}.Debug|x64.ActiveCfg = Debug|Any CPU 26 | {FBA399DC-A346-409E-8D14-39F012B2A1A5}.Debug|x64.Build.0 = Debug|Any CPU 27 | {FBA399DC-A346-409E-8D14-39F012B2A1A5}.Debug|x86.ActiveCfg = Debug|Any CPU 28 | {FBA399DC-A346-409E-8D14-39F012B2A1A5}.Debug|x86.Build.0 = Debug|Any CPU 29 | {FBA399DC-A346-409E-8D14-39F012B2A1A5}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {FBA399DC-A346-409E-8D14-39F012B2A1A5}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {FBA399DC-A346-409E-8D14-39F012B2A1A5}.Release|x64.ActiveCfg = Release|Any CPU 32 | {FBA399DC-A346-409E-8D14-39F012B2A1A5}.Release|x64.Build.0 = Release|Any CPU 33 | {FBA399DC-A346-409E-8D14-39F012B2A1A5}.Release|x86.ActiveCfg = Release|Any CPU 34 | {FBA399DC-A346-409E-8D14-39F012B2A1A5}.Release|x86.Build.0 = Release|Any CPU 35 | {FD698C2A-937B-4733-B07F-2F297B2C8166}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {FD698C2A-937B-4733-B07F-2F297B2C8166}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {FD698C2A-937B-4733-B07F-2F297B2C8166}.Debug|x64.ActiveCfg = Debug|Any CPU 38 | {FD698C2A-937B-4733-B07F-2F297B2C8166}.Debug|x64.Build.0 = Debug|Any CPU 39 | {FD698C2A-937B-4733-B07F-2F297B2C8166}.Debug|x86.ActiveCfg = Debug|Any CPU 40 | {FD698C2A-937B-4733-B07F-2F297B2C8166}.Debug|x86.Build.0 = Debug|Any CPU 41 | {FD698C2A-937B-4733-B07F-2F297B2C8166}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {FD698C2A-937B-4733-B07F-2F297B2C8166}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {FD698C2A-937B-4733-B07F-2F297B2C8166}.Release|x64.ActiveCfg = Release|Any CPU 44 | {FD698C2A-937B-4733-B07F-2F297B2C8166}.Release|x64.Build.0 = Release|Any CPU 45 | {FD698C2A-937B-4733-B07F-2F297B2C8166}.Release|x86.ActiveCfg = Release|Any CPU 46 | {FD698C2A-937B-4733-B07F-2F297B2C8166}.Release|x86.Build.0 = Release|Any CPU 47 | EndGlobalSection 48 | EndGlobal 49 | -------------------------------------------------------------------------------- /11-DevOpsWithAzureSQL/db-deploy/.env.template: -------------------------------------------------------------------------------- 1 | ConnectionString="Server=tcp:.database.windows.net,1433;Initial Catalog=;Persist Security Info=False;User ID=;Password=;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;" -------------------------------------------------------------------------------- /11-DevOpsWithAzureSQL/db-deploy/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using DbUp; 4 | 5 | namespace AzureSQLDevelopers.Database.Deploy 6 | { 7 | class Program 8 | { 9 | static int Main(string[] args) 10 | { 11 | DotNetEnv.Env.Load(); 12 | 13 | var connectionString = Environment.GetEnvironmentVariable("ConnectionString"); 14 | 15 | var upgrader = DeployChanges.To 16 | .SqlDatabase(connectionString) 17 | .JournalToSqlTable("dbo", "$__schema_journal") 18 | .WithScriptsFromFileSystem("./sql") 19 | .LogToConsole() 20 | .Build(); 21 | 22 | var result = upgrader.PerformUpgrade(); 23 | 24 | if (!result.Successful) 25 | { 26 | Console.WriteLine(result.Error); 27 | return -1; 28 | } 29 | 30 | Console.WriteLine("Success!"); 31 | return 0; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /11-DevOpsWithAzureSQL/db-deploy/db-deploy.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | AzureSQLDevelopers.Database.Deploy 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | PreserveNewest 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /11-DevOpsWithAzureSQL/db-deploy/sql/01-change-tracking-setup.sql: -------------------------------------------------------------------------------- 1 | if user_id('DotNetWebApp') is null begin 2 | create user DotNetWebApp with password = 'a987REALLY#$%TRONGpa44w0rd!'; 3 | end 4 | go 5 | 6 | if schema_id('web') is null begin 7 | execute('create schema web') 8 | end 9 | go 10 | 11 | grant execute on schema::web to DotNetWebApp 12 | go 13 | 14 | if not exists(select * from sys.sequences where [name] = 'Ids') 15 | begin 16 | create sequence dbo.Ids 17 | as int 18 | start with 1; 19 | end 20 | go 21 | 22 | drop table if exists dbo.TrainingSession; 23 | create table dbo.TrainingSession 24 | ( 25 | [Id] int primary key not null default(next value for dbo.Ids), 26 | [RecordedOn] datetimeoffset not null, 27 | [Type] varchar(50) not null, 28 | [Steps] int not null, 29 | [Distance] int not null, --Meters 30 | [Duration] int not null, --Seconds 31 | [Calories] int not null, 32 | [PostProcessedOn] datetimeoffset null, 33 | [AdjustedSteps] int null, 34 | [AdjustedDistance] decimal(9,6) null 35 | ); 36 | go 37 | 38 | if not exists(select * from sys.change_tracking_databases where database_id = db_id()) 39 | begin 40 | alter database current 41 | set change_tracking = on 42 | (change_retention = 30 days, auto_cleanup = on) 43 | end 44 | go 45 | 46 | if not exists(select * from sys.change_tracking_tables where [object_id]=object_id('dbo.TrainingSession')) 47 | begin 48 | alter table dbo.TrainingSession 49 | enable change_tracking 50 | end 51 | go 52 | -------------------------------------------------------------------------------- /11-DevOpsWithAzureSQL/db-deploy/sql/02-stored-procedure.sql: -------------------------------------------------------------------------------- 1 | create or alter procedure web.get_trainingsessionsync 2 | @json nvarchar(max) 3 | as 4 | 5 | declare @fromVersion int = json_value(@json, '$.fromVersion') 6 | 7 | set xact_abort on 8 | set transaction isolation level snapshot; 9 | begin tran 10 | declare @reason int 11 | 12 | declare @curVer int = change_tracking_current_version(); 13 | declare @minVer int = change_tracking_min_valid_version(object_id('dbo.TrainingSession')); 14 | 15 | if (@fromVersion = 0) begin 16 | set @reason = 0 -- First Sync 17 | end else if (@fromVersion < @minVer) begin 18 | set @fromVersion = 0 19 | set @reason = 1 -- fromVersion too old. New full sync needed 20 | end 21 | 22 | if (@fromVersion = 0) 23 | begin 24 | select 25 | @curVer as 'Metadata.Sync.Version', 26 | 'Full' as 'Metadata.Sync.Type', 27 | @reason as 'Metadata.Sync.ReasonCode', 28 | [Data] = json_query((select Id, RecordedOn, [Type], Steps, Distance from dbo.TrainingSession for json auto)) 29 | for 30 | json path, without_array_wrapper 31 | end else begin 32 | select 33 | @curVer as 'Metadata.Sync.Version', 34 | 'Diff' as 'Metadata.Sync.Type', 35 | [Data] = json_query(( 36 | select 37 | ct.SYS_CHANGE_OPERATION as '$operation', 38 | ct.Id, 39 | ts.RecordedOn, 40 | ts.[Type], 41 | ts.Steps, 42 | ts.Distance, 43 | ts.PostProcessedOn, 44 | ts.AdjustedSteps, 45 | ts.AdjustedDistance 46 | from 47 | dbo.TrainingSession as ts 48 | right outer join 49 | changetable(changes dbo.TrainingSession, @fromVersion) as ct on ct.[Id] = ts.[id] 50 | for 51 | json path 52 | )) 53 | for 54 | json path, without_array_wrapper 55 | end 56 | 57 | commit tran -------------------------------------------------------------------------------- /11-DevOpsWithAzureSQL/db-deploy/sql/03-data.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Insert some "pre-existing" data 3 | */ 4 | insert into dbo.TrainingSession 5 | (RecordedOn, [Type], Steps, Distance, Duration, Calories) 6 | values 7 | ('20191028 17:27:23 -08:00', 'Run', 3784, 5123, 32*60+3, 526), 8 | ('20191027 17:54:48 -08:00', 'Run', 0, 4981, 32*60+37, 480) 9 | go 10 | 11 | /* 12 | View Data 13 | */ 14 | select * from dbo.TrainingSession 15 | go 16 | 17 | /* 18 | Make some changes 19 | */ 20 | insert into dbo.TrainingSession 21 | (RecordedOn, [Type], Steps, Distance, Duration, Calories) 22 | values 23 | ('20191026 18:24:32 -08:00', 'Run', 4866, 4562, 30*60+18, 475) 24 | go 25 | 26 | update 27 | dbo.TrainingSession 28 | set 29 | Steps = 3450 30 | where 31 | Id = 13 32 | 33 | /* 34 | Delete someting 35 | */ 36 | delete from dbo.TrainingSession where Id = 12 37 | -------------------------------------------------------------------------------- /11-DevOpsWithAzureSQL/db-deploy/sql/04-fix-stored-procedure.sql.fix: -------------------------------------------------------------------------------- 1 | create or alter procedure web.get_trainingsessionsync 2 | @json nvarchar(max) 3 | as 4 | 5 | -- If not version specified, assume 0 6 | declare @fromVersion int = coalesce(0, json_value(@json, '$.fromVersion')) 7 | 8 | set xact_abort on 9 | set transaction isolation level snapshot; 10 | begin tran 11 | declare @reason int 12 | 13 | declare @curVer int = change_tracking_current_version(); 14 | declare @minVer int = change_tracking_min_valid_version(object_id('dbo.TrainingSession')); 15 | 16 | if (@fromVersion = 0) begin 17 | set @reason = 0 -- First Sync 18 | end else if (@fromVersion < @minVer) begin 19 | set @fromVersion = 0 20 | set @reason = 1 -- fromVersion too old. New full sync needed 21 | end 22 | 23 | if (@fromVersion = 0) 24 | begin 25 | select 26 | @curVer as 'Metadata.Sync.Version', 27 | 'Full' as 'Metadata.Sync.Type', 28 | @reason as 'Metadata.Sync.ReasonCode', 29 | [Data] = json_query((select Id, RecordedOn, [Type], Steps, Distance from dbo.TrainingSession for json auto)) 30 | for 31 | json path, without_array_wrapper 32 | end else begin 33 | select 34 | @curVer as 'Metadata.Sync.Version', 35 | 'Diff' as 'Metadata.Sync.Type', 36 | [Data] = json_query(( 37 | select 38 | ct.SYS_CHANGE_OPERATION as '$operation', 39 | ct.Id, 40 | ts.RecordedOn, 41 | ts.[Type], 42 | ts.Steps, 43 | ts.Distance, 44 | ts.PostProcessedOn, 45 | ts.AdjustedSteps, 46 | ts.AdjustedDistance 47 | from 48 | dbo.TrainingSession as ts 49 | right outer join 50 | changetable(changes dbo.TrainingSession, @fromVersion) as ct on ct.[Id] = ts.[id] 51 | for 52 | json path 53 | )) 54 | for 55 | json path, without_array_wrapper 56 | end 57 | 58 | commit tran -------------------------------------------------------------------------------- /11-DevOpsWithAzureSQL/db-test/.env.template: -------------------------------------------------------------------------------- 1 | ConnectionString="Server=tcp:.database.windows.net,1433;Initial Catalog=;Persist Security Info=False;User ID=;Password=;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;" -------------------------------------------------------------------------------- /11-DevOpsWithAzureSQL/db-test/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/bin/Debug/netcoreapp3.1/db-test.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}", 16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console 17 | "console": "internalConsole", 18 | "stopAtEntry": false 19 | }, 20 | { 21 | "name": ".NET Core Attach", 22 | "type": "coreclr", 23 | "request": "attach", 24 | "processId": "${command:pickProcess}" 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /11-DevOpsWithAzureSQL/db-test/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/db-test.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/db-test.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "${workspaceFolder}/db-test.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /11-DevOpsWithAzureSQL/db-test/Tests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using DotNetEnv; 3 | using System.Data; 4 | using Microsoft.Data.SqlClient; 5 | using Dapper; 6 | using System; 7 | using Newtonsoft.Json; 8 | using Newtonsoft.Json.Linq; 9 | 10 | namespace AzureSQLDevelopers.Database 11 | { 12 | public class Tests 13 | { 14 | private string ConnectionString; 15 | 16 | [OneTimeSetUp] 17 | public void Setup() 18 | { 19 | DotNetEnv.Env.Load(Environment.CurrentDirectory + "/../../../" + Env.DEFAULT_ENVFILENAME); 20 | ConnectionString = Environment.GetEnvironmentVariable("ConnectionString"); 21 | } 22 | 23 | [Test] 24 | public void CheckEmptyJSON() 25 | { 26 | using(var conn = new SqlConnection(ConnectionString)) 27 | { 28 | var result = conn.ExecuteScalar("web.get_trainingsessionsync", new { @json = "{}" }, commandType: CommandType.StoredProcedure); 29 | var jsonResult = JObject.Parse(result); 30 | Assert.AreEqual("Full", (string)jsonResult.SelectToken("Metadata.Sync.Type")); 31 | } 32 | } 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /11-DevOpsWithAzureSQL/db-test/db-test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | AzureSQLDevelopers.Database.Tests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Davide Mauri 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 | -------------------------------------------------------------------------------- /practical-azure-sql-database-for-modern-developers-small.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yorek/practical-azure-sql-db-for-modern-developers/309f04802c5c1fd38d08d78d86b72e359da84537/practical-azure-sql-database-for-modern-developers-small.jpg --------------------------------------------------------------------------------