<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Martin Eriksson</title>
    <description>The latest articles on Forem by Martin Eriksson (@kranoz91).</description>
    <link>https://forem.com/kranoz91</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1209310%2F9b8f1e13-defd-48ac-a040-24f45930b243.jpg</url>
      <title>Forem: Martin Eriksson</title>
      <link>https://forem.com/kranoz91</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/kranoz91"/>
    <language>en</language>
    <item>
      <title>Push settings to App Configuration in Azure Pipeline</title>
      <dc:creator>Martin Eriksson</dc:creator>
      <pubDate>Mon, 04 Dec 2023 10:23:53 +0000</pubDate>
      <link>https://forem.com/kranoz91/push-settings-to-app-configuration-in-azure-pipeline-l12</link>
      <guid>https://forem.com/kranoz91/push-settings-to-app-configuration-in-azure-pipeline-l12</guid>
      <description>&lt;p&gt;Here is a small automation tip that could be beneficial when working with &lt;a href="https://learn.microsoft.com/en-us/azure/azure-app-configuration/overview"&gt;Azure App Configuration&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/kranoz91/push-to-app-config"&gt;Code Sample&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Details
&lt;/h2&gt;

&lt;p&gt;Those of you who are familiar with Bicep know that defining application settings is pretty straightforward. You define it directly in the App Service resource or a child resource under the App Service resource.&lt;/p&gt;

&lt;p&gt;With App Configuration, it's not as straightforward. You can't define or push application settings to an Azure App Configuration resource via Bicep.&lt;/p&gt;

&lt;p&gt;You can work around this by pushing the application settings to an Azure App Configuration resource in your Azure Pipeline instead by using the task &lt;a href="https://sapinder.medium.com/pushing-configurations-feature-flags-and-key-vault-secret-references-from-git-repository-to-azure-624ba21a39a7"&gt;"AzureAppConfigurationPush@6"&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The "AzureAppConfigurationPush@6" task uses a predefined JSON file where you set your application settings. These values are pushed to the App Configuration resource. However, in most cases, you want to utilize output from the Bicep deployment to populate your application settings.&lt;/p&gt;

&lt;p&gt;To do this, you can use &lt;a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/transforms-variable-substitution?view=azure-devops&amp;amp;tabs=Classic#json-variable-substitution"&gt;JSON variable substitution&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But before you can substitute variables in the JSON file, you need to retrieve the output from the Bicep deployment.&lt;/p&gt;

&lt;p&gt;Let's say that you have an output in your Bicep code defined as the following:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;output variableA string = 'someValue'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then, you can expose that output in your Azure Pipeline by defining the "deploymentOutputs" input in the "AzureResourceGroupDeployment@2" task. Let's set it to "deploymentOutputs" for the sake of this example.&lt;/p&gt;

&lt;p&gt;If you provision your infrastructure in a separate "job" from the one where you push your application settings, you need to set the "Bicep deployment outputs" as outputs of the job as well.&lt;/p&gt;

&lt;p&gt;Here is an example that sets the value of "variableA" from "deploymentOutputs" to a job output variable called "variableA":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- script: echo "##vso[task.setvariable variable=variableA;isOutput=true;]"$(deploymentOutputs.variableA.value)""
  name: OutputVariables
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next step is to pass the output variable from "JobA" to "JobB" where you are pushing the application settings to Azure App Configuration. Let's say that the JSON file that you want to perform "JSON variable substitution" on looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "root": {
        "variableA": ""
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means that you need to define a variable in "JobB" called "root.variableA" and set the value of that variable based on the output variable from "JobA" like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- job: JobB
  dependsOn: JobA
  variables:
    root.variableA: $[ dependencies.JobA.outputs['JobA.OutputVariables.variableA'] ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this, if you run the "FileTransform@2" task followed by the "AzureAppConfigurationPush@6" task, you should be able to see a key-value pair in your Azure App Configuration resource with key: "root:variableA" and value "someValue" after running the pipeline.&lt;/p&gt;

</description>
      <category>appconfiguration</category>
      <category>azure</category>
      <category>pipeline</category>
      <category>automation</category>
    </item>
    <item>
      <title>Write your own SQL Server CDC Application</title>
      <dc:creator>Martin Eriksson</dc:creator>
      <pubDate>Mon, 20 Nov 2023 10:59:57 +0000</pubDate>
      <link>https://forem.com/kranoz91/write-your-own-sql-server-cdc-application-47e4</link>
      <guid>https://forem.com/kranoz91/write-your-own-sql-server-cdc-application-47e4</guid>
      <description>&lt;p&gt;Recently, I was working for a client where one of their needs was to extract data in near real-time from SQL Server and expose it to an event driven architecture in Azure. Not the new, fancy SQL Server versions, no… That would have been too easy. No, this could be as old as SQL Server 2008, which, by coincidence, is also the version of SQL Server where the native SQL Server CDC capability was first released.&lt;/p&gt;

&lt;p&gt;As you can imagine, it was hard to find any SaaS offerings on the market that supported data extraction from SQL Server 2008 using the native SQL Server CDC capabilities.&lt;/p&gt;

&lt;p&gt;As a result, we made the decision to look into how one might go about building this type of application ourselves.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"How hard can it be, right!?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note!&lt;/strong&gt; This code is just to showcase the pure essence of getting changes from a SQL Server using CDC. In a production environment, you would need a much more sofisticated solution with state management, error handling, logging/monitoring and so on.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Before you get any data from this code snippet, you need to setup your SQL Server by enabling CDC on your &lt;a href="https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sys-sp-cdc-enable-db-transact-sql?view=sql-server-ver16"&gt;database&lt;/a&gt; and &lt;a href="https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sys-sp-cdc-enable-table-transact-sql?view=sql-server-ver16"&gt;table(s)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To test this code snippet, you need to do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a console application&lt;/li&gt;
&lt;li&gt;Install necessary dependencies: &lt;a href="https://www.nuget.org/packages/Dapper/"&gt;Dapper&lt;/a&gt; and - &lt;a href="https://www.nuget.org/packages/Microsoft.Data.SqlClient"&gt;Microsoft.Data.SqlClient&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Copy-paste the code to your Program.cs&lt;/li&gt;
&lt;li&gt;Update &lt;code&gt;CONNECTION STRING&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Dapper&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Data.SqlClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.IdentityModel.Tokens&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Collections&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]?&lt;/span&gt; &lt;span class="n"&gt;_currentMaxLSN&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SqlConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;CONNECTION STRING&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tables&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QueryAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"EXEC sys.sp_cdc_help_change_data_capture"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OpenAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_currentMaxLSN&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;CloneByteArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QuerySingleAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;]&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SELECT MIN(start_lsn) FROM cdc.lsn_time_mapping"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;newMaxLSN&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QuerySingleAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;]&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SELECT MAX(start_lsn) FROM cdc.lsn_time_mapping WHERE tran_id &amp;lt;&amp;gt; 0x00"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// if newMaxLSN is greater than _currentMaxLSN&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(((&lt;/span&gt;&lt;span class="n"&gt;IStructuralComparable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;newMaxLSN&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;CompareTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Comparer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="n"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_currentMaxLSN&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QuerySingleAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;]&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SELECT sys.fn_cdc_increment_lsn(@lsn_value)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;lsn_value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"SELECT * FROM cdc.fn_cdc_get_all_changes_&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Capture_Instance&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;(@from_lsn, @to_lsn, 'all update old')"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;fromLSN&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="c1"&gt;// if start_lsn of CDC table is greater than fromLSN, then fromLSN needs to be changed to the start_lsn of the table.&lt;/span&gt;
            &lt;span class="c1"&gt;// The reason for this is that you will get an exception if you try to get changes from a capture instance where from_lsn is less than the start_lsn of the table.&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(((&lt;/span&gt;&lt;span class="n"&gt;IStructuralComparable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Start_LSN&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;CompareTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fromLSN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Comparer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="n"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;fromLSN&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Start_LSN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;changes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;QueryAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;from_lsn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fromLSN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to_lsn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newMaxLSN&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;JsonSerializerOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;WriteIndented&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;_currentMaxLSN&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newMaxLSN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;++;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nf"&gt;ClearBottomLine&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nf"&gt;WriteOnBottomLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"No changes found{0}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Enumerable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToArray&lt;/span&gt;&lt;span class="p"&gt;())));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CloseAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;ClearBottomLine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;WriteOnBottomLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WindowWidth&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;WriteOnBottomLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CursorLeft&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CursorTop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CursorTop&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WindowTop&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WindowHeight&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// Restore previous position&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetCursorPosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="n"&gt;Table&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Capture_Instance&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;Start_LSN&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Defining constraints
&lt;/h2&gt;

&lt;p&gt;First of all, we had to define what our aim was and what we were willing to put on hold or treat as "bonus features".&lt;/p&gt;

&lt;p&gt;One of the constraints was that we were only going to handle incremental loads and on-demand replays based on the available data in the CDC tables in the SQL DB. In other words, no support for full loads or extracting data prior to what had been persisted in the CDC tables.&lt;/p&gt;

&lt;p&gt;We also wanted to avoid having any table specific logic such as DDL parsing, data transformation, etc.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Keep it simple!&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Setting up SQL Server
&lt;/h2&gt;

&lt;p&gt;To work with the CDC capabilities in SQL Server, you first need to enable CDC on the &lt;a href="https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sys-sp-cdc-enable-db-transact-sql?view=sql-server-ver16"&gt;database&lt;/a&gt; and the &lt;a href="https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sys-sp-cdc-enable-table-transact-sql?view=sql-server-ver16"&gt;tables&lt;/a&gt; that you want to handle.&lt;/p&gt;

&lt;p&gt;Once you have completed these steps, there will be &lt;a href="https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sys-sp-cdc-help-jobs-transact-sql?view=sql-server-ver16"&gt;jobs&lt;/a&gt; running in the background to capture and cleanup data in the CDC tables. These jobs can be &lt;a href="https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sys-sp-cdc-change-job-transact-sql?view=sql-server-ver16"&gt;modified&lt;/a&gt; to fit your needs where you can control things such as polling interval for the capture job and retention for the cleanup job which controls how long the data should be persisted in the CDC tables before its removed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Well then, how do I get changes in near real-time!?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Identifying Changes
&lt;/h2&gt;

&lt;p&gt;First of all, you need to understand what the LSN (Log Sequence Number) is and what role it plays in the CDC feature.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"In SQL Server, the Log Sequence Number (LSN) is a unique identifier used to track and order transactions and changes within the transaction log." -&lt;a href="https://www.linkedin.com/pulse/log-sequence-number-lsn-gopikrishna-pinnamaneni/"&gt;https://www.linkedin.com/pulse/log-sequence-number-lsn-gopikrishna-pinnamaneni/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You use the LSN to identify if there are any changes in the CDC tables. This is done by persisting the maximum LSN as you extract changes from the CDC tables, then you poll for the maximum LSN and when the value is different from what you have persisted from the previous execution, you know that there are changes which you can retrieve from the CDC tables.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Wait a minute… It's stated &lt;a href="https://learn.microsoft.com/en-us/sql/relational-databases/system-tables/cdc-lsn-time-mapping-transact-sql?view=sql-server-ver16"&gt;here&lt;/a&gt; that "Entries may also be logged for which there are no change tables entries." Doesn't that mean there might be new maximum LSN values which are not related to changes in CDC tables?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Yes, that could happen if you were to use the built-in system function called &lt;a href="https://learn.microsoft.com/en-us/sql/relational-databases/system-functions/sys-fn-cdc-get-max-lsn-transact-sql?view=sql-server-ver16"&gt;sys.fn_cdc_get_max_lsn()&lt;/a&gt;. You could potentially get new maximum LSN values which are not related to any changes in the CDC tables.&lt;/p&gt;

&lt;p&gt;One way around this is to query directly against the &lt;a href="https://learn.microsoft.com/en-us/sql/relational-databases/system-tables/cdc-lsn-time-mapping-transact-sql?view=sql-server-ver16"&gt;cdc.lsn_time_mapping&lt;/a&gt; table instead with a filter. You are only interested in the maximum LSN where the column "tran_id" is not equal to 0x00. This is the approach that &lt;a href="https://debezium.io/documentation/reference/stable/index.html"&gt;Debezium&lt;/a&gt; use in order to optimize identifying changes in CDC tables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start_lsn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;cdc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lsn_time_mapping&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;tran_id&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;x00&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the value is greater than what you had previously, that means you have &lt;strong&gt;2 LSN values&lt;/strong&gt; that make up a &lt;strong&gt;range of LSN values&lt;/strong&gt;. It is within this range that you will find the new changes in your CDC tables.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;But how do I use this range to get the changes in my CDC tables!?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Getting new changes
&lt;/h2&gt;

&lt;p&gt;When you have 2 LSN values that make up an LSN range, you can use those LSN values to get new changes from your CDC tables.&lt;/p&gt;

&lt;p&gt;You have &lt;strong&gt;two options&lt;/strong&gt;, either get &lt;a href="https://learn.microsoft.com/en-us/sql/relational-databases/system-functions/cdc-fn-cdc-get-all-changes-capture-instance-transact-sql?view=sql-server-ver16"&gt;ALL&lt;/a&gt; the changes within that range or get &lt;a href="https://learn.microsoft.com/en-us/sql/relational-databases/system-functions/cdc-fn-cdc-get-net-changes-capture-instance-transact-sql?view=sql-server-ver16"&gt;NET&lt;/a&gt; changes within that range.&lt;/p&gt;

&lt;p&gt;The difference between the two is basically that if one unique row has been altered multiple times within that range of LSN values, then getting ALL the changes would get all modifications on that single row, while getting NET changes would only get the latest one.&lt;/p&gt;

&lt;p&gt;To use the function that will get you net changes on a CDC table, you need to configure the CDC table to &lt;a href="https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sys-sp-cdc-enable-table-transact-sql?view=sql-server-ver16#----supports_net_changes"&gt;support net changes&lt;/a&gt;. This also requires either defining the "index_name" when enabling CDC on your table or having a primary key on your table which would then be selected as the index automatically.&lt;/p&gt;

&lt;p&gt;While only getting net changes can be nice to have, do pay attention to the fact that this means that an extra index needs to be maintained which could have a negative effect on CDC performance.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;I don't care! Just tell me how to get the changes!&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Alright, if you choose to support net changes then two functions which are tailor made for your CDC table (or rather it's connected to the capture instance that you defined when enabling CDC for your table) are created. Otherwise, you only get a function to get all changes.&lt;/p&gt;

&lt;p&gt;The function to get all changes looks like this: &lt;a href="https://learn.microsoft.com/en-us/sql/relational-databases/system-functions/cdc-fn-cdc-get-all-changes-capture-instance-transact-sql?view=sql-server-ver16"&gt;cdc.fn_cdc_get_all_changes_capture_instance&lt;/a&gt; where  is an optional parameter when enabling CDC for your table. If no value is provided, the capture instance will default to &lt;code&gt;schema_table&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For net changes, it's like this: &lt;a href="https://learn.microsoft.com/en-us/sql/relational-databases/system-functions/cdc-fn-cdc-get-net-changes-capture-instance-transact-sql?view=sql-server-ver16"&gt;cdc.fn_cdc_get_net_changes_capture_instance&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When you call any of these functions, they will require a "from_lsn" and "to_lsn" value. This is where your LSN range comes in.&lt;/p&gt;

&lt;p&gt;You also have a third parameter, called "row_filter_option". This parameter works a bit differently depending on whether you use it for getting all or net changes.&lt;/p&gt;

&lt;p&gt;For all changes, you can set the third parameter to "all". This will only get you the outcome after an update operation and not the state it was in before the update. If you set it to "all update old", you will get both the state of the row before and after the update operation in the output.&lt;/p&gt;

&lt;p&gt;Since performance was key in this project, we didn't really look at getting net changes since enabling that might have a negative effect on performance.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;If you only need to extract new changes in near real-time from SQL Server and you don't need to take snapshots of historical data, transform, route and do all kinds of crazy stuff in one single application, then it might be suitable for you to just build a CDC application on your own to get more control and flexibility if something needs to be tailored to your specific needs.&lt;/p&gt;

&lt;p&gt;But, if you do need a richer feature set. I would suggest looking at what &lt;a href="https://debezium.io/documentation/reference/stable/index.html"&gt;Debezium&lt;/a&gt; has to offer with their open source solution.&lt;/p&gt;

</description>
      <category>sqlserver</category>
      <category>cdc</category>
      <category>dotnet</category>
    </item>
  </channel>
</rss>
