Implement Change Data Streams with Azure SQL Database, Change Tracking, and Azure SQL Bindings

Brian Spendolini

Building on my first Azure SQL Bindings blog post and taking inspiration from Drew Skwiers-Koballa’s post on SQL Bindings, we are going to create a change data stream with Azure SQL Database. Now, usually, the first part of these blogs is a prerequisite section that goes over what you need to install locally to get started. From talking to many of you in person and getting questions/emails on this process, this post is going to use GitHub codespaces; nothing to install locally or configure. I’ve created a project here that you can fork and create a codespace to get you up and running in minutes.

Starting the codespace

GitHub Account Needed!


You do need a free GitHub account to use codespaces. You can sign up for one here!

To start a codespace, just fork the project located here. Then, click the green Code button on the forked repository. Click the Codespaces tab followed by clicking the green Create codespace on main button.

Image Screenshot 2023 05 12 064043

This will create the codespace using the devcontainer.json file in the .devcontainer directory. Once the codespace is up and running, it will then run the install-dev-tools.sh file in the scripts directory to configure the codespace with all the necessary tools for this blog post and for developing with the Azure SQL Database in general.

Change data stream components

For this project, we are going to use the following services/features locally in the codespace and in Azure.

Create a table with change tracking

Once, the codespace has started up, go to the terminal and issue the following commands. We are going to create a directory for our database objects and a SQL Database Project:

mkdir database
cd database
dotnet new sqlproj -n "devDB" -tp "SqlAzureV12"

Image Screenshot 2023 05 12 065645

sqlproj in a nutshell


Here’s a breakdown of the command:

  • dotnet new sqlproj: This is a command from the .NET CLI used to create a new SQL Server Database Project. It generates a project file with the .sqlproj extension, which can be used to manage database schemas, scripts, and related artifacts.
  • -n “devDB”: This is an optional parameter specifying the name of the project or the database. In this case, it’s set to “devDB,” indicating that the project or database will be named “devDB.”
  • -tp “SqlAzureV12”: This is another optional parameter that specifies the target platform for the project. In this case, it’s set to “SqlAzureV12,” indicating that the project is targeting SQL Server Azure version 12.

Now, click on the SQL Database Projects extension in codespaces
Image Screenshot 2023 05 12 065855
and click the Open existing green button. The project file is located at /workspaces/codespace-for-DB-Devs/database/devDB/ and named devDB.sqlproj.
Image Screenshot 2023 05 12 070223
Use the next command in the terminal to create a SQL Server 2022 database right in your codespace. Yes, you heard that right…we can create a full SQL Server 2022 database in a codespace!
sqlcmd create mssql -u devDB –accept-eula
Image Screenshot 2023 05 12 085551

go-sqlcmd


Learn all about the new sqlcmd here!
In a few short minutes, the database will be created within docker and ready to be used. Before we move on from the terminal, run the following command to get the username and password of the database:
sqlcmd config connection-strings
This command will provide the connect strings for various languages: ADO.NET, JDBC, GO, ODBC, and SQLCMD.

Create a table in SQL Database Projects

Back in the SQL Database Projects extension, right click the project and select Add Table.
Image Screenshot 2023 05 12 090245
In the Add Table dialog window, name the table customer and press enter.
Image Screenshot 2023 05 12 090521
It creates a skeleton customer table script for you.
Image Screenshot 2023 05 12 090534
Replace that code with the following:
CREATE TABLE [dbo].[customer] (
    [customer_id]        INT            IDENTITY (1, 1) NOT NULL PRIMARY KEY CLUSTERED ([customer_id] ASC),
    [customer_name]      NVARCHAR (200) NOT NULL,
    [customer_email]     NVARCHAR (200) NOT NULL,
    [customer_address]   NVARCHAR (500) NOT NULL
);

Image Screenshot 2023 05 12 090711

and SAVE the file.

Publish to the local database

SQL Database Projects provides the ability to publish the database code added to the project to a database. Using this ability, the code can be deployed into the local SQL Server 2022 image that was created in this codespace with sqlcmd. Start by right clicking the project and select Publish.

Image Screenshot 2023 05 12 090955
After selecting Publish, the Publish dialog box will appear. For the first step, select “Publish to an existing Azure SQL logical server
Image Screenshot 2023 05 12 091221
Next, select “Don’t use profile
Image Screenshot 2023 05 12 091356
The next series of steps will create a connection profile to the locally running database. In the dialog, select “Create Connection Profile
Image Screenshot 2023 05 12 091407
Now, Use “localhost” as the server name, then press Enter.
Image Screenshot 2023 05 12 092322
Use “devDB” as the database name, then press Enter.
Image Screenshot 2023 05 12 092422
In the Authentication Type dialog box, select “SQL Login“.
Image Screenshot 2023 05 12 092546
Here is where it is best to refer back to the command “sqlcmd config connection-strings” to find the username and password. Run it again at the terminal to get the values if needed.
In the User name (SQL Login) dialog box, enter the user from the connect strings. It should be vscode, then press Enter.
Image Screenshot 2023 05 12 093030
and provide the password from the connect strings in the Password (SQL Login) dialog box, then press Enter.
Image Screenshot 2023 05 12 093207
Select “Yes” so that the password is saved (encrypted) on the connection profile
Image Screenshot 2023 05 12 093256
Provide a profile name in the last dialog box but this step is optional. Press Enter to finish the connection profile process.
Image Screenshot 2023 05 12 093516
After pressing Enter and the connection profile is verified, a warning box will appear on the lower right of the screen. This warning is indicating that due to new security features within the database, you need to enable the self-signed certificate. Click the Enable Trust Server Certificate green button to continue.
Image Screenshot 2023 05 12 093551
You may be asked to choose a database. If so, choose devDB
Image Screenshot 2023 05 12 094216
For the Action step, there are two options. Choose the Generate Script option to create publish scripts for the project. This is very useful for adding to CICD flows. Choose Publish to immedietly deploy the objects to the database chosen in the connection profile. For this project, choose Publish.
Image Screenshot 2023 05 12 094243

Viewing the published table

Upon choosing Publish, the project will build and then deploy to the database. Once finished
Image Screenshot 2023 05 12 094545
click the SQL Server extension
Image Screenshot 2023 05 12 094604
to view the connection profile we just created
Image Screenshot 2023 05 12 094756
Expand the Table folder to see the customer table and it’s columns
Image Screenshot 2023 05 12 094814

Enable change tracking

In the SQL Server extension, right click on the connection profile and select New Query.
Image Screenshot 2023 05 12 095051
This will open up a new query sheet where SQL commands can be run. The table needs change tracking enabled for the SQL bindings trigger to work correctly.
The first command to be run will enable change tracking in the database.
ALTER DATABASE [devDB]
SET CHANGE_TRACKING = ON;
GO
then enable change tracking on the customer table:
ALTER TABLE [dbo].[customer] ENABLE CHANGE_TRACKING;
GO
In this section, you created a SQL Database project, created a database right in your codespace with a single command, and published objects from the SQL Database project directly into the locally running database. The next section will create an Azure Function in which to house the SQL Bindings Trigger to watch for data changes.

Create an Azure Function

Back in the terminal at the bottom of the page,

Back to the terminal


You may have to click the terminal tab on the window bar on the bottom of the page. When you deployed the SQL Database Project, it may have moved from the terminal window to the Output window.

Image Screenshot 2023 05 12 100117

issue the following command to change the directory back to the top level of this project:
cd /workspaces/codespace-for-DB-Devs
Image Screenshot 2023 05 12 100517
then, issue the following command to start the function creation process:
func init
Upon issuing that command, you will be presented with a choice of frameworks for this function to use.
Image Screenshot 2023 05 12 100727
The framework/runtime for this project is dotnet so enter 1, then press enter.
Image Screenshot 2023 05 12 100604
The next option is to choose a language.
Image Screenshot 2023 05 12 100959
C# is used for this project so again, enter 1, then press enter.
Image Screenshot 2023 05 12 101054
When this process is finished, click the File Explorer extension to see the new files that were created for you.
Image Screenshot 2023 05 12 101239

Adding necessary libraries to the project

Next, we need to add some package references to the project (for SQL Bindings and Azure Event Hub). The following commands will add these references to the codespace-for-DB-Devs.csproj file.
Run the following commands in the terminal:
dotnet add package Microsoft.Azure.WebJobs.Extensions.Sql --prerelease

Image Screenshot 2023 05 12 101626

dotnet add package Azure.Messaging.EventHubs
dotnet add package Microsoft.Azure.WebJobs.Extensions.EventHubs

Image Screenshot 2023 05 12 101700

Create the customer class object

We are going to create a customer class object file. To create a new file in codespaces, right click below the files in the file explorer extension and select New File.

Image Screenshot 2023 05 12 102750

Name this file Customer.cs and press enter.

Image Screenshot 2023 05 12 102912

If the new file has not opened up for you in codespaces, select this file by right clicking on it. Copy and paste the following code into the Customer.cs file to create the customer class object.

namespace Company.Function;
public class Customer
{
    public int customer_id { get; set; }
    public string customer_name { get; set; }
    public string customer_email { get; set; }
    public string customer_address { get; set; }
}
Image Screenshot 2023 05 12 130007
Image Screenshot 2023 05 12 103252
and SAVE the file.

Create the SQL trigger function

The next step is to create an Azure Function. Start by pressing F1 or Shift-Ctrl-P to bring up the command palette.
Image Screenshot 2023 05 12 105414
Enter “create function” into the text field and then select Azure Functions: Create Function.
Image Screenshot 2023 05 12 105520
A dialog box will appear in the center of the screen asking to “Initialize project for use with VS Code?”. Click Yes in the dialog box.
Image Screenshot 2023 05 12 105712
In the following dialog box, Select “HTTP Trigger” as the function template.
Image Screenshot 2023 05 12 110427
Now, on step 2, name the Function changeDataStreams in the next dialog box, and then press Enter.
Image Screenshot 2023 05 12 110556
Step 3 is for the function namespace. Accept the default namespace of Company.Function, and then press Enter.
Image Screenshot 2023 05 12 110711
In step four, select “Anonymous” for AccessRights.
Image Screenshot 2023 05 12 110726
Looking at the file explorer, there will be a new file called changeDataStream.cs which should also automatically open up in codespaces for you.
Image Screenshot 2023 05 12 110941

Adding the SQL Bindings code

If the file is not already open, open the file by right clicking on it. Replace the code in the file with the following:
using System.Collections.Generic;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
using Microsoft.Azure.WebJobs.Extensions.Sql;
using System.Threading.Tasks;
using System.Text.Json;
namespace Company.Function;
public static class streamCustomers
{
    [FunctionName("changeDataStream")]
    public static async Task RunAsync(
        [SqlTrigger("[dbo].[customer]", "SqlConnectionString")]
            IReadOnlyList<SqlChange<Customer>> changes,
        ILogger logger)
   {
      foreach (SqlChange<Customer> change in changes)
      {
          var customer = JsonSerializer.Serialize(change.Item);
          var message = $"{change.Operation} {customer}";
          logger.LogInformation(message);
        }
    }
}
and SAVE the file. This code uses the SQL trigger binding to watch the customer table for changes. When it sees a change, it will fire and so something for each change. Here, in this code, we are just logging the data change to the terminal.
Now that the function code is done, we need to provide it a value for SqlConnectionString. This variable can be places in the local.settings.json file and contain the connect string for our locally running database.
Image Screenshot 2023 05 12 112130
Open the local.settings.json file and add the following line just below the “Values”: { section:
"SqlConnectionString": "Server=localhost,1433;Database=devDB;User ID=vscode;Password=XXXXX;Encrypt=True;TrustServerCertificate=True;Connection Timeout=30;",

Image Screenshot 2023 05 12 112130 8211 Copy

and be sure to replace the XXXXX with the password of the database that was created before. If you forgot the password, run the following in the terminal to find it again:

sqlcmd config connection-strings

And remember to SAVE the file when done.

Test the trigger

Time to test the trigger and see if it catches changes to the customer table in the database. At the terminal run the following command to start the Azure Function:

func host start

Image Screenshot 2023 05 12 112618

and once the function is started, open a new query sheet for the devDB database

Image Screenshot 2023 05 12 095051

and issue the following SQL insert statement:

insert into dbo.customer values(N'Bill', N'bill@computer.commm', N'Anytown, Anycity 12345');
You should see the following in the terminal window indicating the trigger binding did see the change:
[2023-05-11T19:18:03.124Z] Executing 'changeDataStream' (Reason='New change detected on table '[dbo].[customer]' at 2023-05-11T19:18:03.0902305Z.', Id=541ff09e-54ac-48e8-8d17-bbcc9a451432)
[2023-05-11T19:18:03.176Z] Insert{"customer_id":1,"customer_name":"Bill","customer_email":"bill@computer.commm","customer_address":"Anytown, Anycity 12345"}
[2023-05-11T19:18:03.195Z] Executed 'changeDataStream' (Succeeded, Id=541ff09e-54ac-48e8-8d17-bbcc9a451432, Duration=93ms)

Image Screenshot 2023 05 12 112957

Passing change data to the Azure Event Hub

This next section uses an Azure Event Hub for passing the change data events from the database to the hub for processing/routing. You can use this quickstart to create an Azure Event Hub in the Azure Portal and only need to create one in the Basic Tier; the lowest cost tier.
In the terminal window, press Ctrl-C to stop the function and open the changeDataStream.cs file if not already open in codespaces.
Replace the code in that file with the following new code:
using System.Collections.Generic;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
using Microsoft.Azure.WebJobs.Extensions.Sql;
using System.Threading.Tasks;
using Azure.Messaging.EventHubs;
using System.Text.Json;
namespace Company.Function;
public static class streamCustomers
{
    [FunctionName("changeDataStream")]
    public static async Task Run(
        [SqlTrigger("[dbo].[customer]", "SqlConnectionString")]
            IReadOnlyList<SqlChange<Customer>> changes,
        [EventHub("HUB_NAME", Connection = "EventHubConnectionAppSetting")]
            IAsyncCollector<EventData> outputEvents,
        ILogger logger)
    {
        foreach (SqlChange<Customer> change in changes)
        {
            var customer = JsonSerializer.Serialize(change.Item);
            var message = $"{change.Operation} {customer}";
            logger.LogInformation(message);
            await outputEvents.AddAsync(new EventData(message));
        }
    }
}
Replace HUB_NAME with the name of the hub that was created when the Azure Event Hub was set up.
Image Screenshot 2023 05 12 114000
The difference between this new code and the previous version is that once we get into the section foreach (SqlChange<Customer> change in changes), not only are we still logging the result to the terminal, but we are also sending this change event over to an Azure Event Hub using Event Hub bindings for Azure Functions.
Next, open the local.settings.json file again and add the following entries under the SqlConnectionString entry:
"EventHubConnectionAppSetting": "XXXXX",

Image Screenshot 2023 05 12 113721
Where XXXXX is the Event Hub Connection String that can be found in the Azure Portal, Azure Powershell or using Azure CLI.
Image Screenshot 2023 05 12 114054
Not sure on where to find the connection string? You can read how to find it here.
Once the value have been replaced in the local.settings.json file, SAVE the file and restart the function:
func host start

Image Screenshot 2023 05 12 112618
Using the query sheet again, issue another SQL insert statement:
insert into dbo.customer values(N'Ellie', N'ellie@computer.commm', N'Anytown, Anycity 12345');
In the terminal, there should be another JSON message indicating that the trigger binding has fired
[2023-05-12T21:47:03.447Z] Executing 'changeDataStream' (Reason='New change detected on table '[dbo].[customer]' at 2023-05-12T21:47:03.4465111Z.', Id=5c7077d5-cec9-424a-bc51-17f08b8677bb)
[2023-05-12T21:47:03.448Z] Insert {"customer_id":2,"customer_name":"Ellie","customer_email":"ellie@computer.commm","customer_address":"Anytown, Anycity 12345"}
[2023-05-12T21:47:03.561Z] Executed 'changeDataStream' (Succeeded, Id=5c7077d5-cec9-424a-bc51-17f08b8677bb, Duration=114ms)
and if you look at the Azure Event Hub in the Azure Portal, you can see a message has been queued.
Image Screenshot 2023 05 12 114944
Congratulations! You have created a change data stream using SQL Server 2022/Azure SQL Database, Azure Functions, Azure Event Hub, and SQL Bindings! It is pretty amazing the small amount of code you need to work with services within Azure. Both the interaction with the database and the event hub we single lines of code; so easy and simple to understand and implement.

Passwordless Authentication

In this project, we placed the connect string of the database into the local.settings.json file with a password. While this is fine for local development, it will not be appropriate for production deployments in Azure. To solve this challenge, we can replace the user/password in the file with a managed identity. This will allow the connect string to look like the following:
Server=productiondatabase.database.windows.net; Authentication=Active Directory Managed Identity; Database=prodDB
This is a much more secure option and removes the ability for someone to find a password and user in a connect string. There is a tutorial on how to set this up here. You can also migrate away from SAS tokens with the Event Hub connection string to managed identity as well. The tutorial on how to used functions with managed identity is here.

Outbox pattern with stored procedures

A popular pattern for change data capture is the outbox pattern. Here, you create a table that contains committed transactions that separate from the base table where the transactions originate. This helps with transaction integrity and assists with rollbacks. To implement this pattern here, I would create a set of stored procedures for the CRUD operations on a table (Sometimes called Table APIs). In these stored procedures, not only do you write to the main table, but you also write to this outbox table. You can even include the operation that was performed and a message ID in this outbox for better routing and sorting on the hub as well as keeping the transactions in order.
Keep an eye out for either a follow up to this post with these two topics discussed in depth or a full scale workshop/lab encompassing everything here.

0 comments

Discussion is closed.

Feedback usabilla icon