|
| 1 | +using System; |
| 2 | +using System.Configuration; // use App.config file |
| 3 | +using System.Collections.Specialized; |
| 4 | +using System.Collections.Generic; |
| 5 | +using System.Linq; |
| 6 | +using System.Text; |
| 7 | +using System.Threading.Tasks; |
| 8 | +using System.Data.SqlClient; // use SQL client library |
| 9 | +using CG = System.Collections.Generic; |
| 10 | +using TD = System.Threading; |
| 11 | +using QC = System.Data.SqlClient; |
| 12 | + |
| 13 | +namespace ResilientDBApp |
| 14 | +{ |
| 15 | + public class Program |
| 16 | + { |
| 17 | + static void Main(string[] args) |
| 18 | + { |
| 19 | + // Query executions and waits |
| 20 | + int executions = 100; // how many executions |
| 21 | + int sleeptime = 2; // wait time between executions (seconds) |
| 22 | + |
| 23 | + // Connection parameters and resiliency options |
| 24 | + string InstanceConnectionString = "ConnMI1"; // connection string to SQL MI |
| 25 | + bool ResilientQuery = true; // execute resilient, or non-resilient query to transient errors (retry logic true/false) |
| 26 | + |
| 27 | + // Query text to execute |
| 28 | + string SQLtext = "INSERT INTO timetable (datestamp) VALUES (CURRENT_TIMESTAMP);"; |
| 29 | + bool ReadBack = false; // read query response from SQL MI - if using SELECT statements |
| 30 | + |
| 31 | + try |
| 32 | + { |
| 33 | + for (int i = 1; i <= executions; i++) |
| 34 | + { |
| 35 | + Console.Write("Execution # {0} ", i); |
| 36 | + if (!ResilientQuery) |
| 37 | + { |
| 38 | + Console.WriteLine ("(NON-RESILIENT)"); |
| 39 | + InsertNonResilient(SQLtext, InstanceConnectionString, ReadBack); // execute NON-resilient DB query - NO retry logic |
| 40 | + } else |
| 41 | + { |
| 42 | + Console.WriteLine("(RESILIENT)"); |
| 43 | + InsertResilient(SQLtext, InstanceConnectionString, ReadBack); // execute resilient DB query - HAS RETRY LOGIC |
| 44 | + } |
| 45 | + TD.Thread.Sleep(1000 * sleeptime); // wait between the queries |
| 46 | + } |
| 47 | + } |
| 48 | + catch (Exception e) |
| 49 | + { |
| 50 | + Console.WriteLine("Error: " + e.Message); // throw error message if connection unsuccessul |
| 51 | + } |
| 52 | + Console.WriteLine("\nProgram terminated. Press any key to close the window..."); |
| 53 | + Console.ReadLine(); |
| 54 | + } |
| 55 | + |
| 56 | + // Logic to execure NON-RESILIENT database query (no retry logic) |
| 57 | + static public void InsertNonResilient(string SQLtext, string InstanceConnectionString, bool ReadBack) |
| 58 | + { |
| 59 | + AccessDatabase(SQLtext, InstanceConnectionString, ReadBack); |
| 60 | + Console.WriteLine(); |
| 61 | + } |
| 62 | + |
| 63 | + // Logic to execure RESILIENT database query |
| 64 | + static public int InsertResilient(string SQLtext, string InstanceConnectionString, bool ReadBack) |
| 65 | + { |
| 66 | + bool succeeded = false; |
| 67 | + int totalNumberOfTimesToTry = 4; // how many times to retry |
| 68 | + int retryIntervalSeconds = 10; // after how many seconds |
| 69 | + |
| 70 | + for (int tries = 1; |
| 71 | + tries <= totalNumberOfTimesToTry; |
| 72 | + tries++) |
| 73 | + { |
| 74 | + try |
| 75 | + { |
| 76 | + if (tries > 1) |
| 77 | + { |
| 78 | + Console.WriteLine |
| 79 | + ("Transient error encountered. Will begin attempt number {0} of {1} max...", |
| 80 | + tries, totalNumberOfTimesToTry |
| 81 | + ); |
| 82 | + TD.Thread.Sleep(1000 * retryIntervalSeconds); |
| 83 | + retryIntervalSeconds = Convert.ToInt32 |
| 84 | + (retryIntervalSeconds * 1.5); |
| 85 | + } |
| 86 | + |
| 87 | + AccessDatabase(SQLtext, InstanceConnectionString, ReadBack); |
| 88 | + succeeded = true; |
| 89 | + break; |
| 90 | + } |
| 91 | + |
| 92 | + catch (QC.SqlException sqlExc) |
| 93 | + { |
| 94 | + if (TransientErrorNumbers.Contains |
| 95 | + (sqlExc.Number) == true) |
| 96 | + { |
| 97 | + Console.WriteLine("{0}: transient error.", sqlExc.Number); |
| 98 | + continue; |
| 99 | + } |
| 100 | + else |
| 101 | + { |
| 102 | + Console.WriteLine(sqlExc); |
| 103 | + succeeded = false; |
| 104 | + break; |
| 105 | + } |
| 106 | + } |
| 107 | + |
| 108 | + catch (Exception Exc) |
| 109 | + { |
| 110 | + Console.WriteLine(Exc); |
| 111 | + succeeded = false; |
| 112 | + break; |
| 113 | + } |
| 114 | + } |
| 115 | + |
| 116 | + if (succeeded == true) |
| 117 | + { |
| 118 | + Console.WriteLine("SUCCESS.\n"); |
| 119 | + return 0; |
| 120 | + } |
| 121 | + else |
| 122 | + { |
| 123 | + Console.WriteLine("ERROR: Unable to access the database.\n"); |
| 124 | + return 1; |
| 125 | + } |
| 126 | + } |
| 127 | + |
| 128 | + // Connects to the database, executes query |
| 129 | + static public void AccessDatabase(string sqltext, string instance, bool read) |
| 130 | + { |
| 131 | + //throw new TestSqlException(4060); //(7654321); // Uncomment for testing. |
| 132 | + |
| 133 | + using (var sqlConnection = new QC.SqlConnection |
| 134 | + (GetSqlConnectionString(instance))) |
| 135 | + { |
| 136 | + using (var dbCommand = sqlConnection.CreateCommand()) |
| 137 | + { |
| 138 | + sqlConnection.Open(); |
| 139 | + dbCommand.CommandText = sqltext; |
| 140 | + Console.WriteLine("Executing query: " + dbCommand.CommandText); |
| 141 | + |
| 142 | + // are we reading from SQL Server, or only comitting |
| 143 | + if (read == true) // execute SQL query and read reaspons |
| 144 | + { |
| 145 | + var dataReader = dbCommand.ExecuteReader(); |
| 146 | + while (dataReader.Read()) |
| 147 | + { |
| 148 | + Console.WriteLine(dataReader.GetString(0)); |
| 149 | + } |
| 150 | + } else // exectue SQL query and report rows affected |
| 151 | + { |
| 152 | + int rowsAffected = dbCommand.ExecuteNonQuery(); //execute query and get row count |
| 153 | + Console.WriteLine(rowsAffected + " row(s) updated"); |
| 154 | + } |
| 155 | + } |
| 156 | + } |
| 157 | + } |
| 158 | + |
| 159 | + // Build connection string to the server |
| 160 | + // ADD your SQL MI server details into App.config, |
| 161 | + // OR manuall edit the four string values - DataSource, InitialCatalog, UserID, Password with your SQL MI connection details |
| 162 | + static private string GetSqlConnectionString(string instance) |
| 163 | + { |
| 164 | + // Prepare the connection string to Azure SQL Database. |
| 165 | + var sqlConnectionSB = new QC.SqlConnectionStringBuilder(); |
| 166 | + |
| 167 | + // Change these values to your values. |
| 168 | + sqlConnectionSB.DataSource = ConfigurationManager.AppSettings.Get(instance + ".server"); // Server |
| 169 | + sqlConnectionSB.InitialCatalog = ConfigurationManager.AppSettings.Get(instance + ".database"); // Database |
| 170 | + sqlConnectionSB.UserID = ConfigurationManager.AppSettings.Get(instance + ".username"); |
| 171 | + sqlConnectionSB.Password = ConfigurationManager.AppSettings.Get(instance + ".pass"); |
| 172 | + |
| 173 | + // Adjust these values if you like. (ADO.NET 4.5.1 or later.) |
| 174 | + sqlConnectionSB.ConnectRetryCount = 3; |
| 175 | + sqlConnectionSB.ConnectRetryInterval = 10; // Seconds. |
| 176 | + |
| 177 | + // Leave these values as they are. |
| 178 | + sqlConnectionSB.IntegratedSecurity = false; |
| 179 | + sqlConnectionSB.Encrypt = true; |
| 180 | + sqlConnectionSB.ConnectTimeout = 30; |
| 181 | + |
| 182 | + // Test the connection string configuration online |
| 183 | + // Console.WriteLine(sqlConnectionSB); |
| 184 | + |
| 185 | + return sqlConnectionSB.ToString(); |
| 186 | + } |
| 187 | + |
| 188 | + // Error numbers for transient errors |
| 189 | + static public CG.List<int> TransientErrorNumbers = |
| 190 | + new CG.List<int> { 4060, 40197, 40501, 40613, 49918, 49919, 49920, 11001 }; |
| 191 | + } |
| 192 | + |
| 193 | +} |
0 commit comments