From 3447fc1d7b036cccecbc2fb5861d3f5ce75443ed Mon Sep 17 00:00:00 2001 From: Stuart Padley Date: Thu, 12 Oct 2023 04:50:55 -0700 Subject: [PATCH 1/5] Add Entra (AAD) authentication support --- Samples/Dapper/ElasticDapper.csproj | 9 +- Samples/Dapper/Program.cs | 108 ++++++------ Samples/Dapper/Sharding.cs | 2 +- Samples/Dapper/SqlDatabaseUtils.cs | 18 +- Samples/EFCodeFirst/ElasticScaleContext.cs | 2 +- .../ElasticScaleDbConfiguration.cs | 2 +- .../EntityFrameworkCodeFirst.csproj | 7 +- Samples/EFCodeFirst/Program.cs | 97 +++++------ Samples/EFCodeFirst/Sharding.cs | 2 +- Samples/EFCodeFirst/SqlDatabaseUtils.cs | 18 +- Samples/EFMultiTenant/ElasticScaleContext.cs | 2 +- .../ElasticScaleDbConfiguration.cs | 2 +- .../EntityFrameworkMultiTenant.csproj | 7 +- Samples/EFMultiTenant/Program.cs | 91 +++++----- Samples/EFMultiTenant/Sharding.cs | 2 +- Samples/EFMultiTenant/SqlDatabaseUtils.cs | 18 +- .../ElasticScaleStarterKit/Configuration.cs | 20 ++- .../DataDependentRoutingSample.cs | 84 +++++----- .../ElasticScaleStarterKit.csproj | 9 +- .../SqlDatabaseUtils.cs | 158 ++++++------------ Samples/ShardSqlCmd/Program.cs | 2 +- Samples/ShardSqlCmd/ShardSqlCmd.csproj | 4 +- ...ure.SqlDatabase.ElasticScale.Client.csproj | 11 +- ...abase.ElasticScale.ClientTestCommon.csproj | 4 +- ...tabase.ElasticScale.Query.UnitTests.csproj | 7 +- .../MultiShardTestUtils.cs | 6 +- .../Globals.cs | 10 +- ...sticScale.ShardManagement.UnitTests.csproj | 7 +- .../ScenarioTests.cs | 10 +- .../ShardMapTests.cs | 4 +- 30 files changed, 336 insertions(+), 387 deletions(-) diff --git a/Samples/Dapper/ElasticDapper.csproj b/Samples/Dapper/ElasticDapper.csproj index 8305c65..6eed8f5 100644 --- a/Samples/Dapper/ElasticDapper.csproj +++ b/Samples/Dapper/ElasticDapper.csproj @@ -5,14 +5,13 @@ - - - - - + + + + \ No newline at end of file diff --git a/Samples/Dapper/Program.cs b/Samples/Dapper/Program.cs index dafc787..f60417c 100644 --- a/Samples/Dapper/Program.cs +++ b/Samples/Dapper/Program.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using Dapper; using DapperExtensions; using Microsoft.Azure.SqlDatabase.ElasticScale.ShardManagement; @@ -67,89 +67,74 @@ public static void Main() Console.Write("Enter a name for a new Blog: "); var name = Console.ReadLine(); - SqlDatabaseUtils.SqlRetryPolicy.ExecuteAction(() => + using (SqlConnection sqlconn = shardingLayer.ShardMap.OpenConnectionForKey( + key: s_tenantId1, + connectionString: connStrBldr.ConnectionString, + options: ConnectionOptions.Validate)) { - using (SqlConnection sqlconn = shardingLayer.ShardMap.OpenConnectionForKey( - key: s_tenantId1, - connectionString: connStrBldr.ConnectionString, - options: ConnectionOptions.Validate)) - { - var blog = new Blog { Name = name }; - sqlconn.Insert(blog); - } - }); + var blog = new Blog { Name = name }; + sqlconn.Insert(blog); + } - SqlDatabaseUtils.SqlRetryPolicy.ExecuteAction(() => + using (SqlConnection sqlconn = shardingLayer.ShardMap.OpenConnectionForKey( + key: s_tenantId1, + connectionString: connStrBldr.ConnectionString, + options: ConnectionOptions.Validate)) { - using (SqlConnection sqlconn = shardingLayer.ShardMap.OpenConnectionForKey( - key: s_tenantId1, - connectionString: connStrBldr.ConnectionString, - options: ConnectionOptions.Validate)) + // Display all Blogs for tenant 1 + IEnumerable result = sqlconn.Query(@" + SELECT * + FROM Blog + ORDER BY Name"); + + Console.WriteLine("All blogs for tenant id {0}:", s_tenantId1); + foreach (var item in result) { - // Display all Blogs for tenant 1 - IEnumerable result = sqlconn.Query(@" - SELECT * - FROM Blog - ORDER BY Name"); - - Console.WriteLine("All blogs for tenant id {0}:", s_tenantId1); - foreach (var item in result) - { - Console.WriteLine(item.Name); - } + Console.WriteLine(item.Name); } - }); + } // Do work for tenant 2 :-) // Here I am going to illustrate how to integrate // with DapperExtensions which saves us the T-SQL // - SqlDatabaseUtils.SqlRetryPolicy.ExecuteAction(() => + using (SqlConnection sqlconn = shardingLayer.ShardMap.OpenConnectionForKey( + key: s_tenantId2, + connectionString: connStrBldr.ConnectionString, + options: ConnectionOptions.Validate)) { - using (SqlConnection sqlconn = shardingLayer.ShardMap.OpenConnectionForKey( - key: s_tenantId2, - connectionString: connStrBldr.ConnectionString, - options: ConnectionOptions.Validate)) + // Display all Blogs for tenant 2 + IEnumerable result = sqlconn.GetList(); + Console.WriteLine("All blogs for tenant id {0}:", s_tenantId2); + foreach (var item in result) { - // Display all Blogs for tenant 2 - IEnumerable result = sqlconn.GetList(); - Console.WriteLine("All blogs for tenant id {0}:", s_tenantId2); - foreach (var item in result) - { - Console.WriteLine(item.Name); - } + Console.WriteLine(item.Name); } - }); + } // Create and save a new Blog Console.Write("Enter a name for a new Blog: "); var name2 = Console.ReadLine(); - SqlDatabaseUtils.SqlRetryPolicy.ExecuteAction(() => + using (SqlConnection sqlconn = shardingLayer.ShardMap.OpenConnectionForKey( + key: s_tenantId2, + connectionString: connStrBldr.ConnectionString, + options: ConnectionOptions.Validate)) { - using (SqlConnection sqlconn = shardingLayer.ShardMap.OpenConnectionForKey( - key: s_tenantId2, - connectionString: connStrBldr.ConnectionString, - options: ConnectionOptions.Validate)) - { - var blog = new Blog { Name = name2 }; - sqlconn.Insert(blog); - } - }); + var blog = new Blog { Name = name2 }; + sqlconn.Insert(blog); + } - SqlDatabaseUtils.SqlRetryPolicy.ExecuteAction(() => + using (SqlConnection sqlconn = shardingLayer.ShardMap.OpenConnectionForKey(s_tenantId2, connStrBldr.ConnectionString, ConnectionOptions.Validate)) { - using (SqlConnection sqlconn = shardingLayer.ShardMap.OpenConnectionForKey(s_tenantId2, connStrBldr.ConnectionString, ConnectionOptions.Validate)) + // Display all Blogs for tenant 2 + IEnumerable result = sqlconn.GetList(); + Console.WriteLine("All blogs for tenant id {0}:", s_tenantId2); + foreach (var item in result) { - // Display all Blogs for tenant 2 - IEnumerable result = sqlconn.GetList(); - Console.WriteLine("All blogs for tenant id {0}:", s_tenantId2); - foreach (var item in result) - { - Console.WriteLine(item.Name); - } + Console.WriteLine(item.Name); } - }); + } Console.WriteLine("Press any key to exit..."); Console.ReadKey(); @@ -168,6 +153,7 @@ private static void CreateSchema(string shardName) using (SqlConnection conn = new SqlConnection(connStrBldr.ToString())) { + conn.RetryLogicProvider = SqlDatabaseUtils.SqlRetryProvider; conn.Open(); conn.Execute(@" IF (OBJECT_ID('[dbo].[Blog]', 'U') IS NULL) diff --git a/Samples/Dapper/Sharding.cs b/Samples/Dapper/Sharding.cs index a7625cf..f92b404 100644 --- a/Samples/Dapper/Sharding.cs +++ b/Samples/Dapper/Sharding.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using Microsoft.Azure.SqlDatabase.ElasticScale.ShardManagement; namespace ElasticDapper diff --git a/Samples/Dapper/SqlDatabaseUtils.cs b/Samples/Dapper/SqlDatabaseUtils.cs index 66de772..e235ead 100644 --- a/Samples/Dapper/SqlDatabaseUtils.cs +++ b/Samples/Dapper/SqlDatabaseUtils.cs @@ -2,7 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; -using Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling; +using Microsoft.Data.SqlClient; namespace ElasticDapper { @@ -11,12 +11,22 @@ namespace ElasticDapper /// internal static class SqlDatabaseUtils { + /// + /// Create a retry logic provider + /// + public static SqlRetryLogicBaseProvider SqlRetryProvider = SqlConfigurableRetryFactory.CreateExponentialRetryProvider(SqlRetryPolicy); + /// /// Gets the retry policy to use for connections to SQL Server. /// - public static RetryPolicy SqlRetryPolicy + private static SqlRetryLogicOption SqlRetryPolicy => new() { - get { return new RetryPolicy(10, TimeSpan.FromSeconds(5)); } - } + // Tries 5 times before throwing an exception + NumberOfTries = 5, + // Preferred gap time to delay before retry + DeltaTime = TimeSpan.FromSeconds(1), + // Maximum gap time for each delay time before retry + MaxTimeInterval = TimeSpan.FromSeconds(20) + }; } } diff --git a/Samples/EFCodeFirst/ElasticScaleContext.cs b/Samples/EFCodeFirst/ElasticScaleContext.cs index d42ab1e..9ce1039 100644 --- a/Samples/EFCodeFirst/ElasticScaleContext.cs +++ b/Samples/EFCodeFirst/ElasticScaleContext.cs @@ -3,7 +3,7 @@ using System.Data.Common; using System.Data.Entity; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using Microsoft.Azure.SqlDatabase.ElasticScale.ShardManagement; namespace EFCodeFirstElasticScale diff --git a/Samples/EFCodeFirst/ElasticScaleDbConfiguration.cs b/Samples/EFCodeFirst/ElasticScaleDbConfiguration.cs index 3c6b3b8..364f009 100644 --- a/Samples/EFCodeFirst/ElasticScaleDbConfiguration.cs +++ b/Samples/EFCodeFirst/ElasticScaleDbConfiguration.cs @@ -17,7 +17,7 @@ public ElasticScaleDbConfiguration() // the SqlAzureExecutionStrategy which would lead to wrong retry behavior // since it would not use the OpenConnectionForKey call. // For more details, see http://msdn.microsoft.com/en-us/data/dn456835.aspx. - this.SetExecutionStrategy("System.Data.SqlClient", () => new DefaultExecutionStrategy()); + this.SetExecutionStrategy("Microsoft.Data.SqlClient", () => new DefaultExecutionStrategy()); // There are legitimate cases, typically for migrations during development // using Add-Migration and Update-Datase, where a connection to a diff --git a/Samples/EFCodeFirst/EntityFrameworkCodeFirst.csproj b/Samples/EFCodeFirst/EntityFrameworkCodeFirst.csproj index bd76408..203c518 100644 --- a/Samples/EFCodeFirst/EntityFrameworkCodeFirst.csproj +++ b/Samples/EFCodeFirst/EntityFrameworkCodeFirst.csproj @@ -5,10 +5,6 @@ - - - - @@ -17,4 +13,7 @@ + + + \ No newline at end of file diff --git a/Samples/EFCodeFirst/Program.cs b/Samples/EFCodeFirst/Program.cs index cec4e9e..3dc2721 100644 --- a/Samples/EFCodeFirst/Program.cs +++ b/Samples/EFCodeFirst/Program.cs @@ -2,7 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using System.Linq; //////////////////////////////////////////////////////////////////////////////////////// @@ -58,81 +58,66 @@ public static void Main() Console.Write("Enter a name for a new Blog: "); var name = Console.ReadLine(); - SqlDatabaseUtils.SqlRetryPolicy.ExecuteAction(() => + using (var db = new ElasticScaleContext(sharding.ShardMap, s_tenantId1, connStrBldr.ConnectionString)) { - using (var db = new ElasticScaleContext(sharding.ShardMap, s_tenantId1, connStrBldr.ConnectionString)) - { - var blog = new Blog { Name = name }; - db.Blogs.Add(blog); - db.SaveChanges(); - } - }); + var blog = new Blog { Name = name }; + db.Blogs.Add(blog); + db.SaveChanges(); + } - SqlDatabaseUtils.SqlRetryPolicy.ExecuteAction(() => + using (var db = new ElasticScaleContext(sharding.ShardMap, s_tenantId1, connStrBldr.ConnectionString)) { - using (var db = new ElasticScaleContext(sharding.ShardMap, s_tenantId1, connStrBldr.ConnectionString)) + // Display all Blogs for tenant 1 + var query = from b in db.Blogs + orderby b.Name + select b; + + Console.WriteLine("All blogs for tenant id {0}:", s_tenantId1); + foreach (var item in query) { - // Display all Blogs for tenant 1 - var query = from b in db.Blogs - orderby b.Name - select b; - - Console.WriteLine("All blogs for tenant id {0}:", s_tenantId1); - foreach (var item in query) - { - Console.WriteLine(item.Name); - } + Console.WriteLine(item.Name); } - }); + } // Do work for tenant 2 :-) - SqlDatabaseUtils.SqlRetryPolicy.ExecuteAction(() => + using (var db = new ElasticScaleContext(sharding.ShardMap, s_tenantId2, connStrBldr.ConnectionString)) { - using (var db = new ElasticScaleContext(sharding.ShardMap, s_tenantId2, connStrBldr.ConnectionString)) + // Display all Blogs from the database + var query = from b in db.Blogs + orderby b.Name + select b; + + Console.WriteLine("All blogs for tenant id {0}:", s_tenantId2); + foreach (var item in query) { - // Display all Blogs from the database - var query = from b in db.Blogs - orderby b.Name - select b; - - Console.WriteLine("All blogs for tenant id {0}:", s_tenantId2); - foreach (var item in query) - { - Console.WriteLine(item.Name); - } + Console.WriteLine(item.Name); } - }); + } // Create and save a new Blog Console.Write("Enter a name for a new Blog: "); var name2 = Console.ReadLine(); - SqlDatabaseUtils.SqlRetryPolicy.ExecuteAction(() => + using (var db = new ElasticScaleContext(sharding.ShardMap, s_tenantId2, connStrBldr.ConnectionString)) { - using (var db = new ElasticScaleContext(sharding.ShardMap, s_tenantId2, connStrBldr.ConnectionString)) - { - var blog = new Blog { Name = name2 }; - db.Blogs.Add(blog); - db.SaveChanges(); - } - }); + var blog = new Blog { Name = name2 }; + db.Blogs.Add(blog); + db.SaveChanges(); + } - SqlDatabaseUtils.SqlRetryPolicy.ExecuteAction(() => + using (var db = new ElasticScaleContext(sharding.ShardMap, s_tenantId2, connStrBldr.ConnectionString)) { - using (var db = new ElasticScaleContext(sharding.ShardMap, s_tenantId2, connStrBldr.ConnectionString)) + // Display all Blogs from the database + var query = from b in db.Blogs + orderby b.Name + select b; + + Console.WriteLine("All blogs for tenant id {0}:", s_tenantId2); + foreach (var item in query) { - // Display all Blogs from the database - var query = from b in db.Blogs - orderby b.Name - select b; - - Console.WriteLine("All blogs for tenant id {0}:", s_tenantId2); - foreach (var item in query) - { - Console.WriteLine(item.Name); - } + Console.WriteLine(item.Name); } - }); + } Console.WriteLine("Press any key to exit..."); Console.ReadKey(); diff --git a/Samples/EFCodeFirst/Sharding.cs b/Samples/EFCodeFirst/Sharding.cs index 8ad1457..e9ead47 100644 --- a/Samples/EFCodeFirst/Sharding.cs +++ b/Samples/EFCodeFirst/Sharding.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using System.Linq; using Microsoft.Azure.SqlDatabase.ElasticScale.ShardManagement; diff --git a/Samples/EFCodeFirst/SqlDatabaseUtils.cs b/Samples/EFCodeFirst/SqlDatabaseUtils.cs index 2e63840..96830f4 100644 --- a/Samples/EFCodeFirst/SqlDatabaseUtils.cs +++ b/Samples/EFCodeFirst/SqlDatabaseUtils.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using Microsoft.Data.SqlClient; using Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling; namespace EFCodeFirstElasticScale @@ -11,12 +12,23 @@ namespace EFCodeFirstElasticScale /// internal static class SqlDatabaseUtils { + /// + /// Create a retry logic provider + /// + public static SqlRetryLogicBaseProvider SqlRetryProvider = SqlConfigurableRetryFactory.CreateExponentialRetryProvider(SqlRetryPolicy); + /// /// Gets the retry policy to use for connections to SQL Server. /// - public static RetryPolicy SqlRetryPolicy + private static SqlRetryLogicOption SqlRetryPolicy => new() { - get { return new RetryPolicy(10, TimeSpan.FromSeconds(5)); } - } + // Tries 5 times before throwing an exception + NumberOfTries = 5, + // Preferred gap time to delay before retry + DeltaTime = TimeSpan.FromSeconds(1), + // Maximum gap time for each delay time before retry + MaxTimeInterval = TimeSpan.FromSeconds(20) + }; + } } diff --git a/Samples/EFMultiTenant/ElasticScaleContext.cs b/Samples/EFMultiTenant/ElasticScaleContext.cs index b9d8ef6..99ac2c8 100644 --- a/Samples/EFMultiTenant/ElasticScaleContext.cs +++ b/Samples/EFMultiTenant/ElasticScaleContext.cs @@ -3,7 +3,7 @@ using System; using System.Data.Entity; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using Microsoft.Azure.SqlDatabase.ElasticScale.ShardManagement; namespace EFMultiTenantElasticScale diff --git a/Samples/EFMultiTenant/ElasticScaleDbConfiguration.cs b/Samples/EFMultiTenant/ElasticScaleDbConfiguration.cs index 6945bac..aea6369 100644 --- a/Samples/EFMultiTenant/ElasticScaleDbConfiguration.cs +++ b/Samples/EFMultiTenant/ElasticScaleDbConfiguration.cs @@ -17,7 +17,7 @@ public ElasticScaleDbConfiguration() // the SqlAzureExecutionStrategy which would lead to wrong retry behavior // since it would not use the OpenConnectionForKey call. // For more details, see http://msdn.microsoft.com/en-us/data/dn456835.aspx. - this.SetExecutionStrategy("System.Data.SqlClient", () => new DefaultExecutionStrategy()); + this.SetExecutionStrategy("Microsoft.Data.SqlClient", () => new DefaultExecutionStrategy()); // There are legitimate cases, typically for migrations during development // using Add-Migration and Update-Datase, where a connection to a diff --git a/Samples/EFMultiTenant/EntityFrameworkMultiTenant.csproj b/Samples/EFMultiTenant/EntityFrameworkMultiTenant.csproj index 7a49864..51a43c3 100644 --- a/Samples/EFMultiTenant/EntityFrameworkMultiTenant.csproj +++ b/Samples/EFMultiTenant/EntityFrameworkMultiTenant.csproj @@ -5,10 +5,6 @@ - - - - @@ -20,4 +16,7 @@ + + + diff --git a/Samples/EFMultiTenant/Program.cs b/Samples/EFMultiTenant/Program.cs index 143f546..7832e74 100644 --- a/Samples/EFMultiTenant/Program.cs +++ b/Samples/EFMultiTenant/Program.cs @@ -3,7 +3,7 @@ using System; using System.Data.Entity.Infrastructure; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using System.Linq; //////////////////////////////////////////////////////////////////////////////////////// @@ -69,69 +69,62 @@ public static void Main() Console.Write("\nEnter a name for a new Blog for TenantId {0}: ", tenantId); var name = Console.ReadLine(); - SqlDatabaseUtils.SqlRetryPolicy.ExecuteAction(() => + using (var db = new ElasticScaleContext(sharding.ShardMap, tenantId, connStrBldr.ConnectionString)) { - using (var db = new ElasticScaleContext(sharding.ShardMap, tenantId, connStrBldr.ConnectionString)) + var blog = new Blog { Name = name, TenantId = tenantId }; // must specify TenantId unless using default constraints to auto-populate + db.Blogs.Add(blog); + db.SaveChanges(); + + // If Row-Level Security is enabled, tenants will only display their own blogs + // Otherwise, tenants will see blogs for all tenants on the shard db + var query = from b in db.Blogs + orderby b.Name + select b; + + Console.WriteLine("All blogs for TenantId {0}:", tenantId); + foreach (var item in query) { - var blog = new Blog { Name = name, TenantId = tenantId }; // must specify TenantId unless using default constraints to auto-populate - db.Blogs.Add(blog); - db.SaveChanges(); - - // If Row-Level Security is enabled, tenants will only display their own blogs - // Otherwise, tenants will see blogs for all tenants on the shard db - var query = from b in db.Blogs - orderby b.Name - select b; - - Console.WriteLine("All blogs for TenantId {0}:", tenantId); - foreach (var item in query) - { - Console.WriteLine(item.Name); - } + Console.WriteLine(item.Name); } - }); + } } // Example query via ADO.NET SqlClient // If Row-Level Security is enabled, only Tenant 4's blogs will be listed - SqlDatabaseUtils.SqlRetryPolicy.ExecuteAction(() => + // Note: We are using a wrapper function OpenDDRConnection that automatically set SESSION_CONTEXT with the specified TenantId. + // This is a best practice to ensure that SESSION_CONTEXT is always set before executing a query. + using (SqlConnection conn = ElasticScaleContext.OpenDDRConnection(sharding.ShardMap, s_tenantId4, connStrBldr.ConnectionString)) { - // Note: We are using a wrapper function OpenDDRConnection that automatically set SESSION_CONTEXT with the specified TenantId. - // This is a best practice to ensure that SESSION_CONTEXT is always set before executing a query. - using (SqlConnection conn = ElasticScaleContext.OpenDDRConnection(sharding.ShardMap, s_tenantId4, connStrBldr.ConnectionString)) - { - SqlCommand cmd = conn.CreateCommand(); - cmd.CommandText = @"SELECT * FROM Blogs"; + SqlCommand cmd = conn.CreateCommand(); + cmd.RetryLogicProvider = SqlDatabaseUtils.SqlRetryProvider; + cmd.CommandText = @"SELECT * FROM Blogs"; - Console.WriteLine("\n--\n\nAll blogs for TenantId {0} (using ADO.NET SqlClient):", s_tenantId4); - SqlDataReader reader = cmd.ExecuteReader(); - while (reader.Read()) - { - Console.WriteLine("{0}", reader["Name"]); - } + Console.WriteLine("\n--\n\nAll blogs for TenantId {0} (using ADO.NET SqlClient):", s_tenantId4); + SqlDataReader reader = cmd.ExecuteReader(); + while (reader.Read()) + { + Console.WriteLine("{0}", reader["Name"]); } - }); - + } + // Because of the RLS block predicate, attempting to insert a row for the wrong tenant will throw an error. Console.WriteLine("\n--\n\nTrying to create a new Blog for TenantId {0} while connected as TenantId {1}: ", s_tenantId2, s_tenantId3); - SqlDatabaseUtils.SqlRetryPolicy.ExecuteAction(() => + + using (var db = new ElasticScaleContext(sharding.ShardMap, s_tenantId3, connStrBldr.ConnectionString)) { - using (var db = new ElasticScaleContext(sharding.ShardMap, s_tenantId3, connStrBldr.ConnectionString)) + // Verify that block predicate prevents Tenant 3 from inserting rows for Tenant 2 + try { - // Verify that block predicate prevents Tenant 3 from inserting rows for Tenant 2 - try - { - var bad_blog = new Blog { Name = "BAD BLOG", TenantId = s_tenantId2 }; - db.Blogs.Add(bad_blog); - db.SaveChanges(); - Console.WriteLine("No error thrown - make sure your security policy has a block predicate on this table in each shard database."); - } - catch (DbUpdateException) - { - Console.WriteLine("Can't insert blog for incorrect tenant."); - } + var bad_blog = new Blog { Name = "BAD BLOG", TenantId = s_tenantId2 }; + db.Blogs.Add(bad_blog); + db.SaveChanges(); + Console.WriteLine("No error thrown - make sure your security policy has a block predicate on this table in each shard database."); } - }); + catch (DbUpdateException) + { + Console.WriteLine("Can't insert blog for incorrect tenant."); + } + } Console.WriteLine("\n--\n\nPress any key to exit..."); Console.ReadKey(); diff --git a/Samples/EFMultiTenant/Sharding.cs b/Samples/EFMultiTenant/Sharding.cs index 2a601dd..7668456 100644 --- a/Samples/EFMultiTenant/Sharding.cs +++ b/Samples/EFMultiTenant/Sharding.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using System.Linq; using Microsoft.Azure.SqlDatabase.ElasticScale.ShardManagement; diff --git a/Samples/EFMultiTenant/SqlDatabaseUtils.cs b/Samples/EFMultiTenant/SqlDatabaseUtils.cs index 7a10b1b..03d777a 100644 --- a/Samples/EFMultiTenant/SqlDatabaseUtils.cs +++ b/Samples/EFMultiTenant/SqlDatabaseUtils.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using Microsoft.Data.SqlClient; using Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling; namespace EFMultiTenantElasticScale @@ -11,12 +12,23 @@ namespace EFMultiTenantElasticScale /// internal static class SqlDatabaseUtils { + /// + /// Create a retry logic provider + /// + public static SqlRetryLogicBaseProvider SqlRetryProvider = SqlConfigurableRetryFactory.CreateExponentialRetryProvider(SqlRetryPolicy); + /// /// Gets the retry policy to use for connections to SQL Server. /// - public static RetryPolicy SqlRetryPolicy + private static SqlRetryLogicOption SqlRetryPolicy => new() { - get { return new RetryPolicy(10, TimeSpan.FromSeconds(5)); } - } + // Tries 5 times before throwing an exception + NumberOfTries = 5, + // Preferred gap time to delay before retry + DeltaTime = TimeSpan.FromSeconds(1), + // Maximum gap time for each delay time before retry + MaxTimeInterval = TimeSpan.FromSeconds(20) + }; + } } diff --git a/Samples/ElasticScaleStarterKit/Configuration.cs b/Samples/ElasticScaleStarterKit/Configuration.cs index 3d1fd33..c6f7578 100644 --- a/Samples/ElasticScaleStarterKit/Configuration.cs +++ b/Samples/ElasticScaleStarterKit/Configuration.cs @@ -2,7 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Configuration; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; namespace ElasticScaleStarterKit { @@ -81,13 +81,15 @@ public static string GetCredentialsConnectionString() string integratedSecurityString = ConfigurationManager.AppSettings["IntegratedSecurity"]; bool integratedSecurity = integratedSecurityString != null && bool.Parse(integratedSecurityString); + var entraSecurityString = ConfigurationManager.AppSettings["EntraSecurity"]; + var entraSecurity = entraSecurityString != null && bool.Parse(entraSecurityString); + SqlConnectionStringBuilder connStr = new SqlConnectionStringBuilder - { - // DDR and MSQ require credentials to be set - UserID = userId, - Password = password, + { IntegratedSecurity = integratedSecurity, + Authentication = entraSecurity ? SqlAuthenticationMethod.ActiveDirectoryInteractive : SqlAuthenticationMethod.NotSpecified, + // DataSource and InitialCatalog cannot be set for DDR and MSQ APIs, because these APIs will // determine the DataSource and InitialCatalog for you. // @@ -100,6 +102,14 @@ public static string GetCredentialsConnectionString() ApplicationName = "ESC_SKv1.0", ConnectTimeout = 30 }; + + if (!integratedSecurity && !entraSecurity) + { + // DDR and MSQ require credentials to be set + connStr.UserID = userId; + connStr.Password = password; + } + return connStr.ToString(); } } diff --git a/Samples/ElasticScaleStarterKit/DataDependentRoutingSample.cs b/Samples/ElasticScaleStarterKit/DataDependentRoutingSample.cs index 780b0f7..05bb1dc 100644 --- a/Samples/ElasticScaleStarterKit/DataDependentRoutingSample.cs +++ b/Samples/ElasticScaleStarterKit/DataDependentRoutingSample.cs @@ -2,7 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using System.Linq; using Microsoft.Azure.SqlDatabase.ElasticScale.ShardManagement; @@ -64,30 +64,30 @@ private static void AddCustomer( // Open and execute the command with retry for transient faults. Note that if the command fails, the connection is closed, so // the entire block is wrapped in a retry. This means that only one command should be executed per block, since if we had multiple // commands then the first command may be executed multiple times if later commands fail. - SqlDatabaseUtils.SqlRetryPolicy.ExecuteAction(() => + + // Looks up the key in the shard map and opens a connection to the shard + using (SqlConnection conn = shardMap.OpenConnectionForKey(customerId, credentialsConnectionString)) { - // Looks up the key in the shard map and opens a connection to the shard - using (SqlConnection conn = shardMap.OpenConnectionForKey(customerId, credentialsConnectionString)) - { - // Create a simple command that will insert or update the customer information - SqlCommand cmd = conn.CreateCommand(); - cmd.CommandText = @" - IF EXISTS (SELECT 1 FROM Customers WHERE CustomerId = @customerId) - UPDATE Customers - SET Name = @name, RegionId = @regionId - WHERE CustomerId = @customerId - ELSE - INSERT INTO Customers (CustomerId, Name, RegionId) - VALUES (@customerId, @name, @regionId)"; - cmd.Parameters.AddWithValue("@customerId", customerId); - cmd.Parameters.AddWithValue("@name", name); - cmd.Parameters.AddWithValue("@regionId", regionId); - cmd.CommandTimeout = 60; - - // Execute the command - cmd.ExecuteNonQuery(); - } - }); + // Create a simple command that will insert or update the customer information + SqlCommand cmd = conn.CreateCommand(); + cmd.RetryLogicProvider = SqlDatabaseUtils.SqlRetryProvider; + + cmd.CommandText = @" + IF EXISTS (SELECT 1 FROM Customers WHERE CustomerId = @customerId) + UPDATE Customers + SET Name = @name, RegionId = @regionId + WHERE CustomerId = @customerId + ELSE + INSERT INTO Customers (CustomerId, Name, RegionId) + VALUES (@customerId, @name, @regionId)"; + cmd.Parameters.AddWithValue("@customerId", customerId); + cmd.Parameters.AddWithValue("@name", name); + cmd.Parameters.AddWithValue("@regionId", regionId); + cmd.CommandTimeout = 60; + + // Execute the command + cmd.ExecuteNonQuery(); + } } /// @@ -99,26 +99,24 @@ private static void AddOrder( int customerId, int productId) { - SqlDatabaseUtils.SqlRetryPolicy.ExecuteAction(() => + // Looks up the key in the shard map and opens a connection to the shard + using (SqlConnection conn = shardMap.OpenConnectionForKey(customerId, credentialsConnectionString)) { - // Looks up the key in the shard map and opens a connection to the shard - using (SqlConnection conn = shardMap.OpenConnectionForKey(customerId, credentialsConnectionString)) - { - // Create a simple command that will insert a new order - SqlCommand cmd = conn.CreateCommand(); - - // Create a simple command - cmd.CommandText = @"INSERT INTO dbo.Orders (CustomerId, OrderDate, ProductId) - VALUES (@customerId, @orderDate, @productId)"; - cmd.Parameters.AddWithValue("@customerId", customerId); - cmd.Parameters.AddWithValue("@orderDate", DateTime.Now.Date); - cmd.Parameters.AddWithValue("@productId", productId); - cmd.CommandTimeout = 60; - - // Execute the command - cmd.ExecuteNonQuery(); - } - }); + // Create a simple command that will insert a new order + SqlCommand cmd = conn.CreateCommand(); + cmd.RetryLogicProvider = SqlDatabaseUtils.SqlRetryProvider; + + // Create a simple command + cmd.CommandText = @"INSERT INTO dbo.Orders (CustomerId, OrderDate, ProductId) + VALUES (@customerId, @orderDate, @productId)"; + cmd.Parameters.AddWithValue("@customerId", customerId); + cmd.Parameters.AddWithValue("@orderDate", DateTime.Now.Date); + cmd.Parameters.AddWithValue("@productId", productId); + cmd.CommandTimeout = 60; + + // Execute the command + cmd.ExecuteNonQuery(); + } ConsoleUtils.WriteInfo("Inserted order for customer ID: {0}", customerId); } diff --git a/Samples/ElasticScaleStarterKit/ElasticScaleStarterKit.csproj b/Samples/ElasticScaleStarterKit/ElasticScaleStarterKit.csproj index a523329..9abd73f 100644 --- a/Samples/ElasticScaleStarterKit/ElasticScaleStarterKit.csproj +++ b/Samples/ElasticScaleStarterKit/ElasticScaleStarterKit.csproj @@ -5,10 +5,8 @@ - - - - + + @@ -20,4 +18,7 @@ + + + \ No newline at end of file diff --git a/Samples/ElasticScaleStarterKit/SqlDatabaseUtils.cs b/Samples/ElasticScaleStarterKit/SqlDatabaseUtils.cs index 6049668..ef7313f 100644 --- a/Samples/ElasticScaleStarterKit/SqlDatabaseUtils.cs +++ b/Samples/ElasticScaleStarterKit/SqlDatabaseUtils.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using System.IO; using System.Text; using System.Threading; @@ -34,11 +34,10 @@ public static bool TryConnectToSqlDatabase() try { - using (ReliableSqlConnection conn = new ReliableSqlConnection( - connectionString, - SqlRetryPolicy, - SqlRetryPolicy)) + using (SqlConnection conn = new SqlConnection( + connectionString)) { + conn.RetryLogicProvider = SqlRetryProvider; conn.Open(); } @@ -55,18 +54,20 @@ public static bool TryConnectToSqlDatabase() public static bool DatabaseExists(string server, string db) { - using (ReliableSqlConnection conn = new ReliableSqlConnection( - Configuration.GetConnectionString(server, MasterDatabaseName), - SqlRetryPolicy, - SqlRetryPolicy)) + using (SqlConnection conn = new SqlConnection( + Configuration.GetConnectionString(server, MasterDatabaseName))) { + conn.RetryLogicProvider = SqlRetryProvider; conn.Open(); SqlCommand cmd = conn.CreateCommand(); + cmd.RetryLogicProvider = SqlRetryProvider; + cmd.CommandText = "select count(*) from sys.databases where name = @dbname"; cmd.Parameters.AddWithValue("@dbname", db); cmd.CommandTimeout = 60; - int count = conn.ExecuteCommand(cmd); + + int count = (int)cmd.ExecuteScalar(); bool exists = count > 0; return exists; @@ -75,18 +76,20 @@ public static bool DatabaseExists(string server, string db) public static bool DatabaseIsOnline(string server, string db) { - using (ReliableSqlConnection conn = new ReliableSqlConnection( - Configuration.GetConnectionString(server, MasterDatabaseName), - SqlRetryPolicy, - SqlRetryPolicy)) + using (SqlConnection conn = new SqlConnection( + Configuration.GetConnectionString(server, MasterDatabaseName))) { + conn.RetryLogicProvider = SqlRetryProvider; conn.Open(); SqlCommand cmd = conn.CreateCommand(); + cmd.RetryLogicProvider = SqlRetryProvider; + cmd.CommandText = "select count(*) from sys.databases where name = @dbname and state = 0"; // online cmd.Parameters.AddWithValue("@dbname", db); cmd.CommandTimeout = 60; - int count = conn.ExecuteCommand(cmd); + + int count = (int)cmd.ExecuteScalar(); bool exists = count > 0; return exists; @@ -96,35 +99,33 @@ public static bool DatabaseIsOnline(string server, string db) public static void CreateDatabase(string server, string db) { ConsoleUtils.WriteInfo("Creating database {0}", db); - using (ReliableSqlConnection conn = new ReliableSqlConnection( - Configuration.GetConnectionString(server, MasterDatabaseName), - SqlRetryPolicy, - SqlRetryPolicy)) + using (SqlConnection conn = new SqlConnection( + Configuration.GetConnectionString(server, MasterDatabaseName))) { + conn.RetryLogicProvider = SqlRetryProvider; conn.Open(); SqlCommand cmd = conn.CreateCommand(); // Determine if we are connecting to Azure SQL DB cmd.CommandText = "SELECT SERVERPROPERTY('EngineEdition')"; cmd.CommandTimeout = 60; - int engineEdition = conn.ExecuteCommand(cmd); + cmd.RetryLogicProvider = SqlRetryProvider; + + int engineEdition = (int)cmd.ExecuteScalar(); if (engineEdition == 5) { // Azure SQL DB - SqlRetryPolicy.ExecuteAction(() => - { - if (!DatabaseExists(server, db)) - { - // Begin creation (which is async for Standard/Premium editions) - cmd.CommandText = string.Format( - "CREATE DATABASE {0} (EDITION = '{1}')", - BracketEscapeName(db), - Configuration.DatabaseEdition); - cmd.CommandTimeout = 60; - cmd.ExecuteNonQuery(); - } - }); + if (!DatabaseExists(server, db)) + { + // Begin creation (which is async for Standard/Premium editions) + cmd.CommandText = string.Format( + "CREATE DATABASE {0} (EDITION = '{1}')", + BracketEscapeName(db), + Configuration.DatabaseEdition); + cmd.CommandTimeout = 180; + cmd.ExecuteNonQuery(); + } // Wait for the operation to complete while (!DatabaseIsOnline(server, db)) @@ -139,7 +140,7 @@ public static void CreateDatabase(string server, string db) { // Other edition of SQL DB cmd.CommandText = string.Format("CREATE DATABASE {0}", BracketEscapeName(db)); - conn.ExecuteCommand(cmd); + cmd.ExecuteNonQuery(); } } } @@ -147,18 +148,17 @@ public static void CreateDatabase(string server, string db) public static void DropDatabase(string server, string db) { ConsoleUtils.WriteInfo("Dropping database {0}", db); - using (ReliableSqlConnection conn = new ReliableSqlConnection( - Configuration.GetConnectionString(server, MasterDatabaseName), - SqlRetryPolicy, - SqlRetryPolicy)) + using (SqlConnection conn = new SqlConnection( + Configuration.GetConnectionString(server, MasterDatabaseName))) { + conn.RetryLogicProvider = SqlRetryProvider; conn.Open(); SqlCommand cmd = conn.CreateCommand(); // Determine if we are connecting to Azure SQL DB cmd.CommandText = "SELECT SERVERPROPERTY('EngineEdition')"; cmd.CommandTimeout = 60; - int engineEdition = conn.ExecuteCommand(cmd); + int engineEdition = (int)cmd.ExecuteScalar(); // Drop the database if (engineEdition == 5) @@ -182,13 +182,13 @@ public static void DropDatabase(string server, string db) public static void ExecuteSqlScript(string server, string db, string schemaFile) { ConsoleUtils.WriteInfo("Executing script {0}", schemaFile); - using (ReliableSqlConnection conn = new ReliableSqlConnection( - Configuration.GetConnectionString(server, db), - SqlRetryPolicy, - SqlRetryPolicy)) + using (SqlConnection conn = new SqlConnection( + Configuration.GetConnectionString(server, db))) { + conn.RetryLogicProvider = SqlRetryProvider; conn.Open(); SqlCommand cmd = conn.CreateCommand(); + cmd.RetryLogicProvider = SqlRetryProvider; // Read the commands from the sql script file IEnumerable commands = ReadSqlScript(schemaFile); @@ -197,7 +197,7 @@ public static void ExecuteSqlScript(string server, string db, string schemaFile) { cmd.CommandText = command; cmd.CommandTimeout = 60; - conn.ExecuteCommand(cmd); + cmd.ExecuteNonQuery(); } } } @@ -234,68 +234,20 @@ private static string BracketEscapeName(string sqlName) return '[' + sqlName.Replace("]", "]]") + ']'; } - /// - /// Gets the retry policy to use for connections to SQL Server. - /// - public static RetryPolicy SqlRetryPolicy - { - get - { - return new RetryPolicy(10, TimeSpan.FromSeconds(5)); - } - } + // Create a retry logic provider + public static SqlRetryLogicBaseProvider SqlRetryProvider = SqlConfigurableRetryFactory.CreateExponentialRetryProvider(SqlRetryPolicy); /// - /// Extended sql transient error detection strategy that performs additional transient error - /// checks besides the ones done by the enterprise library. + /// Gets the retry policy to use for connections to SQL Server. /// - private class ExtendedSqlDatabaseTransientErrorDetectionStrategy : ITransientErrorDetectionStrategy + private static SqlRetryLogicOption SqlRetryPolicy => new() { - /// - /// Enterprise transient error detection strategy. - /// - private SqlDatabaseTransientErrorDetectionStrategy _sqltransientErrorDetectionStrategy = new SqlDatabaseTransientErrorDetectionStrategy(); - - /// - /// Checks with enterprise library's default handler to see if the error is transient, additionally checks - /// for such errors using the code in the in function. - /// - /// Exception being checked. - /// true if exception is considered transient, false otherwise. - public bool IsTransient(Exception ex) - { - return _sqltransientErrorDetectionStrategy.IsTransient(ex) || IsTransientException(ex); - } - - /// - /// Detects transient errors not currently considered as transient by the enterprise library's strategy. - /// - /// Input exception. - /// true if exception is considered transient, false otherwise. - private static bool IsTransientException(Exception ex) - { - SqlException se = ex as SqlException; - - if (se != null && se.InnerException != null) - { - Win32Exception we = se.InnerException as Win32Exception; - - if (we != null) - { - switch (we.NativeErrorCode) - { - case 0x102: - // Transient wait expired error resulting in timeout - return true; - case 0x121: - // Transient semaphore wait expired error resulting in timeout - return true; - } - } - } - - return false; - } - } + // Tries 5 times before throwing an exception + NumberOfTries = 5, + // Preferred gap time to delay before retry + DeltaTime = TimeSpan.FromSeconds(1), + // Maximum gap time for each delay time before retry + MaxTimeInterval = TimeSpan.FromSeconds(20) + }; } } diff --git a/Samples/ShardSqlCmd/Program.cs b/Samples/ShardSqlCmd/Program.cs index 5ce8a42..53b7ad2 100644 --- a/Samples/ShardSqlCmd/Program.cs +++ b/Samples/ShardSqlCmd/Program.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Data; using System.Data.Common; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using System.Diagnostics; using System.Linq; using System.Text; diff --git a/Samples/ShardSqlCmd/ShardSqlCmd.csproj b/Samples/ShardSqlCmd/ShardSqlCmd.csproj index 432852a..5f83b3b 100644 --- a/Samples/ShardSqlCmd/ShardSqlCmd.csproj +++ b/Samples/ShardSqlCmd/ShardSqlCmd.csproj @@ -6,9 +6,9 @@ - + - + diff --git a/Src/ElasticScale.Client/Microsoft.Azure.SqlDatabase.ElasticScale.Client.csproj b/Src/ElasticScale.Client/Microsoft.Azure.SqlDatabase.ElasticScale.Client.csproj index ac4c846..26ce352 100644 --- a/Src/ElasticScale.Client/Microsoft.Azure.SqlDatabase.ElasticScale.Client.csproj +++ b/Src/ElasticScale.Client/Microsoft.Azure.SqlDatabase.ElasticScale.Client.csproj @@ -9,7 +9,7 @@ Microsoft net6.0 Microsoft;Elastic;Scale;Azure;SQL;DB;Database;Shard;Sharding;Management;Query;azureofficial - Updated to net6.0 and migrated from System.Data.SqlClient to Microsoft.Data.SqlClient. + Updated to net6.0 and migrated from Microsoft.Data.SqlClient to Microsoft.Data.SqlClient. Icon.png https://github.com/Azure/elastic-db-tools MIT @@ -24,7 +24,7 @@ - + @@ -51,11 +51,4 @@ PerformanceCounters.Designer.cs - - - - 2.1.2 - - - diff --git a/Test/ElasticScale.ClientTestCommon/Microsoft.Azure.SqlDatabase.ElasticScale.ClientTestCommon.csproj b/Test/ElasticScale.ClientTestCommon/Microsoft.Azure.SqlDatabase.ElasticScale.ClientTestCommon.csproj index cfb54d1..b1b401c 100644 --- a/Test/ElasticScale.ClientTestCommon/Microsoft.Azure.SqlDatabase.ElasticScale.ClientTestCommon.csproj +++ b/Test/ElasticScale.ClientTestCommon/Microsoft.Azure.SqlDatabase.ElasticScale.ClientTestCommon.csproj @@ -6,7 +6,7 @@ - - + + \ No newline at end of file diff --git a/Test/ElasticScale.Query.UnitTests/Microsoft.Azure.SqlDatabase.ElasticScale.Query.UnitTests.csproj b/Test/ElasticScale.Query.UnitTests/Microsoft.Azure.SqlDatabase.ElasticScale.Query.UnitTests.csproj index 97b9f29..6ae32d3 100644 --- a/Test/ElasticScale.Query.UnitTests/Microsoft.Azure.SqlDatabase.ElasticScale.Query.UnitTests.csproj +++ b/Test/ElasticScale.Query.UnitTests/Microsoft.Azure.SqlDatabase.ElasticScale.Query.UnitTests.csproj @@ -7,10 +7,9 @@ - - - - + + + diff --git a/Test/ElasticScale.Query.UnitTests/MultiShardTestUtils.cs b/Test/ElasticScale.Query.UnitTests/MultiShardTestUtils.cs index e012536..e16cb1b 100644 --- a/Test/ElasticScale.Query.UnitTests/MultiShardTestUtils.cs +++ b/Test/ElasticScale.Query.UnitTests/MultiShardTestUtils.cs @@ -47,12 +47,12 @@ internal static class MultiShardTestUtils /// /// Connection string for local shard user. /// - internal static string ShardConnectionString = @"Integrated Security=SSPI;"; + internal static string ShardConnectionString = @"Integrated Security=SSPI;TrustServerCertificate=True;"; /// /// Connection string for global shard map manager operations. /// - internal static string ShardMapManagerConnectionString = @"Data Source=localhost;Initial Catalog=ShardMapManager;Integrated Security=SSPI;"; + internal static string ShardMapManagerConnectionString = @"Data Source=localhost;Initial Catalog=ShardMapManager;Integrated Security=SSPI;TrustServerCertificate=True;"; /// /// Name of the database where the ShardMapManager persists its data. @@ -214,6 +214,8 @@ private static string GetTestConnectionString(string database) builder.DataSource = s_serverLocation; builder.IntegratedSecurity = true; builder.InitialCatalog = database; + builder.TrustServerCertificate = true; + return builder.ConnectionString; } diff --git a/Test/ElasticScale.ShardManagement.UnitTests/Globals.cs b/Test/ElasticScale.ShardManagement.UnitTests/Globals.cs index c706ca2..21035ab 100644 --- a/Test/ElasticScale.ShardManagement.UnitTests/Globals.cs +++ b/Test/ElasticScale.ShardManagement.UnitTests/Globals.cs @@ -22,22 +22,22 @@ internal static class Globals /// /// Connection string for global shard map manager for Integrated Auth /// - private const string ShardMapManagerConnString = ShardMapManagerConnStringBase + "Integrated Security=SSPI;"; + private const string ShardMapManagerConnString = ShardMapManagerConnStringBase + "Integrated Security=SSPI;TrustServerCertificate=True;"; /// /// Connection string for global shard map manager for Sql Auth /// - private const string ShardMapManagerConnStringForSqlAuth = ShardMapManagerConnStringBase + "Integrated Security=False;"; + private const string ShardMapManagerConnStringForSqlAuth = ShardMapManagerConnStringBase + "Integrated Security=False;TrustServerCertificate=True;"; /// /// Connect string for local shard user. /// - private const string ShardUserConnString = @"Integrated Security=SSPI;"; + private const string ShardUserConnString = @"Integrated Security=SSPI;TrustServerCertificate=True;"; /// /// Connect string for local shard user. /// - private const string ShardUserConnStringForSqlAuth = @"User={0};Password={1}"; + private const string ShardUserConnStringForSqlAuth = @"User={0};Password={1};TrustServerCertificate=True;"; /// /// shardMapManager datasource name for unit tests. @@ -57,7 +57,7 @@ internal static class Globals /// /// Connection string for connecting to test server. /// - internal const string ShardMapManagerTestConnectionString = @"Data Source=" + Globals.ShardMapManagerTestsDatasourceName + ";Integrated Security=SSPI;"; + internal const string ShardMapManagerTestConnectionString = @"Data Source=" + Globals.ShardMapManagerTestsDatasourceName + ";Integrated Security=SSPI;TrustServerCertificate=True;"; /// /// Query to create database. diff --git a/Test/ElasticScale.ShardManagement.UnitTests/Microsoft.Azure.SqlDatabase.ElasticScale.ShardManagement.UnitTests.csproj b/Test/ElasticScale.ShardManagement.UnitTests/Microsoft.Azure.SqlDatabase.ElasticScale.ShardManagement.UnitTests.csproj index d755f90..dcde649 100644 --- a/Test/ElasticScale.ShardManagement.UnitTests/Microsoft.Azure.SqlDatabase.ElasticScale.ShardManagement.UnitTests.csproj +++ b/Test/ElasticScale.ShardManagement.UnitTests/Microsoft.Azure.SqlDatabase.ElasticScale.ShardManagement.UnitTests.csproj @@ -7,10 +7,9 @@ - - - - + + + diff --git a/Test/ElasticScale.ShardManagement.UnitTests/ScenarioTests.cs b/Test/ElasticScale.ShardManagement.UnitTests/ScenarioTests.cs index 26a1067..d24ebd1 100644 --- a/Test/ElasticScale.ShardManagement.UnitTests/ScenarioTests.cs +++ b/Test/ElasticScale.ShardManagement.UnitTests/ScenarioTests.cs @@ -308,14 +308,14 @@ public void BasicScenarioDefaultShardMaps() { // Also verify we can connect to the shard with Sql Auth, and Sql Auth using a secure credential using (shardForConnection.OpenConnectionAsync( - string.Empty, + "TrustServerCertificate=True;", Globals.ShardUserCredentialForSqlAuth(sqlAuthLogin.UniquifiedUserName), ConnectionOptions.None).Result) { } using (shardForConnection.OpenConnectionAsync( - string.Empty, + "TrustServerCertificate=True;", Globals.ShardUserCredentialForSqlAuth(sqlAuthLogin.UniquifiedUserName)).Result) { } @@ -1142,7 +1142,7 @@ public void BasicScenarioRangeShardMaps() // Cover the OpenConnectionForKey overloads using (SqlConnection conn = newMultiTenantShardMap.OpenConnectionForKey( 20, - string.Empty, + "TrustServerCertificate=True;", Globals.ShardUserCredentialForSqlAuth(sqlAuthLogin.UniquifiedUserName))) { } @@ -1237,7 +1237,7 @@ public void BasicScenarioRangeShardMaps() // Cover the OpenConnectionForKeyAsync overloads using (SqlConnection conn = multiTenantShardMap.OpenConnectionForKeyAsync( 20, - string.Empty, + "TrustServerCertificate=True;", Globals.ShardUserCredentialForSqlAuth(sqlAuthLogin.UniquifiedUserName), ConnectionOptions.None).Result) { @@ -1245,7 +1245,7 @@ public void BasicScenarioRangeShardMaps() using (SqlConnection conn = multiTenantShardMap.OpenConnectionForKeyAsync( 20, - string.Empty, + "TrustServerCertificate=True;", Globals.ShardUserCredentialForSqlAuth(sqlAuthLogin.UniquifiedUserName)).Result) { } diff --git a/Test/ElasticScale.ShardManagement.UnitTests/ShardMapTests.cs b/Test/ElasticScale.ShardManagement.UnitTests/ShardMapTests.cs index a1ee56a..a2bf280 100644 --- a/Test/ElasticScale.ShardManagement.UnitTests/ShardMapTests.cs +++ b/Test/ElasticScale.ShardManagement.UnitTests/ShardMapTests.cs @@ -221,12 +221,12 @@ public void CreateShardDefault() } // Validate that we can connect to the shard using a secure Sql Auth Credential - using (sNew.OpenConnection(string.Empty, Globals.ShardUserCredentialForSqlAuth(sqlAuthLogin.UniquifiedUserName))) + using (sNew.OpenConnection("TrustServerCertificate=True;", Globals.ShardUserCredentialForSqlAuth(sqlAuthLogin.UniquifiedUserName))) { } using (sNew.OpenConnection( - string.Empty, + "TrustServerCertificate=True;", Globals.ShardUserCredentialForSqlAuth(sqlAuthLogin.UniquifiedUserName), ConnectionOptions.Validate)) { From 71f4c6737afcc90d5a54df1de20955ccd0d9333d Mon Sep 17 00:00:00 2001 From: Stuart Padley Date: Thu, 19 Oct 2023 05:56:35 -0700 Subject: [PATCH 2/5] Added support for all 10 SqlAuthenticationMethod methods --- Samples/Dapper/ElasticDapper.csproj | 2 +- Samples/Dapper/LICENSE | 2 +- Samples/Dapper/README.md | 4 ++ Samples/EFCodeFirst/LICENSE | 2 +- Samples/EFCodeFirst/README.md | 4 ++ Samples/EFCodeFirst/SqlDatabaseUtils.cs | 1 - Samples/EFMultiTenant/LICENSE | 2 +- Samples/EFMultiTenant/README.md | 4 ++ Samples/EFMultiTenant/SqlDatabaseUtils.cs | 1 - Samples/ElasticScaleStarterKit/App.config | 62 ++++++++++++++++++ .../ElasticScaleStarterKit/Configuration.cs | 63 +++++++++++++++---- Samples/ElasticScaleStarterKit/LICENSE | 2 +- Samples/ElasticScaleStarterKit/Program.cs | 5 +- Samples/ElasticScaleStarterKit/README.txt | 7 ++- .../SqlDatabaseUtils.cs | 22 ++++++- Samples/ShardSqlCmd/LICENSE | 2 +- .../Implementation/RetryPolicy.cs | 2 +- .../ShardManagement/ShardMap/ShardMapUtils.cs | 1 + .../SqlStore/SqlShardMapManagerCredentials.cs | 2 + 19 files changed, 163 insertions(+), 27 deletions(-) create mode 100644 Samples/Dapper/README.md create mode 100644 Samples/EFCodeFirst/README.md create mode 100644 Samples/EFMultiTenant/README.md create mode 100644 Samples/ElasticScaleStarterKit/App.config diff --git a/Samples/Dapper/ElasticDapper.csproj b/Samples/Dapper/ElasticDapper.csproj index 6eed8f5..14562bf 100644 --- a/Samples/Dapper/ElasticDapper.csproj +++ b/Samples/Dapper/ElasticDapper.csproj @@ -5,7 +5,7 @@ - + diff --git a/Samples/Dapper/LICENSE b/Samples/Dapper/LICENSE index b8b569d..bfa6449 100644 --- a/Samples/Dapper/LICENSE +++ b/Samples/Dapper/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 Microsoft +Copyright (c) 2023 Microsoft Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Samples/Dapper/README.md b/Samples/Dapper/README.md new file mode 100644 index 0000000..d376edc --- /dev/null +++ b/Samples/Dapper/README.md @@ -0,0 +1,4 @@ + +For documentation see: + +https://learn.microsoft.com/azure/azure-sql/database/elastic-scale-working-with-dapper \ No newline at end of file diff --git a/Samples/EFCodeFirst/LICENSE b/Samples/EFCodeFirst/LICENSE index b8b569d..bfa6449 100644 --- a/Samples/EFCodeFirst/LICENSE +++ b/Samples/EFCodeFirst/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 Microsoft +Copyright (c) 2023 Microsoft Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Samples/EFCodeFirst/README.md b/Samples/EFCodeFirst/README.md new file mode 100644 index 0000000..d88deda --- /dev/null +++ b/Samples/EFCodeFirst/README.md @@ -0,0 +1,4 @@ + +For documentation see: + +https://learn.microsoft.com/azure/azure-sql/database/elastic-scale-use-entity-framework-applications-visual-studio \ No newline at end of file diff --git a/Samples/EFCodeFirst/SqlDatabaseUtils.cs b/Samples/EFCodeFirst/SqlDatabaseUtils.cs index 96830f4..43a5e50 100644 --- a/Samples/EFCodeFirst/SqlDatabaseUtils.cs +++ b/Samples/EFCodeFirst/SqlDatabaseUtils.cs @@ -3,7 +3,6 @@ using System; using Microsoft.Data.SqlClient; -using Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling; namespace EFCodeFirstElasticScale { diff --git a/Samples/EFMultiTenant/LICENSE b/Samples/EFMultiTenant/LICENSE index b8b569d..bfa6449 100644 --- a/Samples/EFMultiTenant/LICENSE +++ b/Samples/EFMultiTenant/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 Microsoft +Copyright (c) 2023 Microsoft Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Samples/EFMultiTenant/README.md b/Samples/EFMultiTenant/README.md new file mode 100644 index 0000000..d88deda --- /dev/null +++ b/Samples/EFMultiTenant/README.md @@ -0,0 +1,4 @@ + +For documentation see: + +https://learn.microsoft.com/azure/azure-sql/database/elastic-scale-use-entity-framework-applications-visual-studio \ No newline at end of file diff --git a/Samples/EFMultiTenant/SqlDatabaseUtils.cs b/Samples/EFMultiTenant/SqlDatabaseUtils.cs index 03d777a..403f64a 100644 --- a/Samples/EFMultiTenant/SqlDatabaseUtils.cs +++ b/Samples/EFMultiTenant/SqlDatabaseUtils.cs @@ -3,7 +3,6 @@ using System; using Microsoft.Data.SqlClient; -using Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling; namespace EFMultiTenantElasticScale { diff --git a/Samples/ElasticScaleStarterKit/App.config b/Samples/ElasticScaleStarterKit/App.config new file mode 100644 index 0000000..569cc77 --- /dev/null +++ b/Samples/ElasticScaleStarterKit/App.config @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Samples/ElasticScaleStarterKit/Configuration.cs b/Samples/ElasticScaleStarterKit/Configuration.cs index c6f7578..06a3f3a 100644 --- a/Samples/ElasticScaleStarterKit/Configuration.cs +++ b/Samples/ElasticScaleStarterKit/Configuration.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using System.Configuration; using Microsoft.Data.SqlClient; @@ -76,19 +77,21 @@ public static string GetCredentialsConnectionString() string userId = ConfigurationManager.AppSettings["UserName"] ?? string.Empty; string password = ConfigurationManager.AppSettings["Password"] ?? string.Empty; - // Get Integrated Security from the app.config file. - // If it exists, then parse it (throw exception on failure), otherwise default to false. - string integratedSecurityString = ConfigurationManager.AppSettings["IntegratedSecurity"]; - bool integratedSecurity = integratedSecurityString != null && bool.Parse(integratedSecurityString); + string trustServerCertificateString = ConfigurationManager.AppSettings["TrustServerCertificate"] ?? string.Empty; - var entraSecurityString = ConfigurationManager.AppSettings["EntraSecurity"]; - var entraSecurity = entraSecurityString != null && bool.Parse(entraSecurityString); + var trustServerCertificate = trustServerCertificateString != null && bool.Parse(trustServerCertificateString); - SqlConnectionStringBuilder connStr = new SqlConnectionStringBuilder - { - IntegratedSecurity = integratedSecurity, + // Get Sql Auth method from the app.config file. + SqlAuthenticationMethod authMethod; + var enumString = ConfigurationManager.AppSettings["SqlAuthenticationMethod"]; + if (!Enum.TryParse(enumString, out authMethod)) + { + throw new ArgumentException("Invalid SqlAuthenticationMethod in app.config"); + } - Authentication = entraSecurity ? SqlAuthenticationMethod.ActiveDirectoryInteractive : SqlAuthenticationMethod.NotSpecified, + SqlConnectionStringBuilder connStr = new SqlConnectionStringBuilder + { + Authentication = authMethod, // DataSource and InitialCatalog cannot be set for DDR and MSQ APIs, because these APIs will // determine the DataSource and InitialCatalog for you. @@ -99,15 +102,49 @@ public static string GetCredentialsConnectionString() // // Other SqlClient ConnectionString keywords are supported. + TrustServerCertificate = trustServerCertificate, + ApplicationName = "ESC_SKv1.0", - ConnectTimeout = 30 + + // Set to 120 if ActiveDirectoryDeviceCodeFlow + // not even the fastest cut and pasters can get the device code + // into the browser and click through in 30 seconds. + ConnectTimeout = authMethod == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow ? 120 : 30 }; - if (!integratedSecurity && !entraSecurity) + // DEVNOTE: NotSpecified behaves the same as SqlPassword (i.e. Sql Auth) + if (authMethod == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity || + authMethod == SqlAuthenticationMethod.ActiveDirectoryMSI || + authMethod == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal || + authMethod == SqlAuthenticationMethod.ActiveDirectoryPassword || + authMethod == SqlAuthenticationMethod.SqlPassword || + authMethod == SqlAuthenticationMethod.NotSpecified) { // DDR and MSQ require credentials to be set + + // ActiveDirectoryManagedIdentity / ActiveDirectoryMSI when using a System Managed System Identify does not use a UserID + if (authMethod != SqlAuthenticationMethod.ActiveDirectoryManagedIdentity && + authMethod != SqlAuthenticationMethod.ActiveDirectoryMSI) + { + if (userId == string.Empty) + { + throw new ArgumentException("UserName must be specified in app.config"); + } + } + connStr.UserID = userId; - connStr.Password = password; + + // ActiveDirectoryManagedIdentity/ActiveDirectoryMSI does not use a Password. + if (authMethod != SqlAuthenticationMethod.ActiveDirectoryManagedIdentity && + authMethod != SqlAuthenticationMethod.ActiveDirectoryMSI) + { + if (password == string.Empty) + { + throw new ArgumentException("Password must be specified in app.config"); + } + + connStr.Password = password; + } } return connStr.ToString(); diff --git a/Samples/ElasticScaleStarterKit/LICENSE b/Samples/ElasticScaleStarterKit/LICENSE index b8b569d..bfa6449 100644 --- a/Samples/ElasticScaleStarterKit/LICENSE +++ b/Samples/ElasticScaleStarterKit/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 Microsoft +Copyright (c) 2023 Microsoft Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Samples/ElasticScaleStarterKit/Program.cs b/Samples/ElasticScaleStarterKit/Program.cs index 2a87424..a4d7317 100644 --- a/Samples/ElasticScaleStarterKit/Program.cs +++ b/Samples/ElasticScaleStarterKit/Program.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Configuration; using System.Diagnostics; using System.Linq; using Microsoft.Azure.SqlDatabase.ElasticScale.ShardManagement; @@ -19,9 +20,11 @@ public static void Main() Console.WriteLine("*** Welcome to Elastic Database Tools Starter Kit ***"); Console.WriteLine("***********************************************************"); Console.WriteLine(); + Console.WriteLine("Authentication method used: {0}", ConfigurationManager.AppSettings["SqlAuthenticationMethod"]); + Console.WriteLine(); // Verify that we can connect to the Sql Database that is specified in App.config settings - if (!SqlDatabaseUtils.TryConnectToSqlDatabase()) + if (!SqlDatabaseUtils.TryConnectToSqlDatabaseAsync()) { // Connecting to the server failed - please update the settings in App.Config diff --git a/Samples/ElasticScaleStarterKit/README.txt b/Samples/ElasticScaleStarterKit/README.txt index d11ba40..04efd5f 100644 --- a/Samples/ElasticScaleStarterKit/README.txt +++ b/Samples/ElasticScaleStarterKit/README.txt @@ -2,7 +2,7 @@ Prerequisites: - Visual Studio 2012 or later, Professional Edition or higher - Nuget 2.7 or later -- .NET Framework 4.5 or later +- .NET Framework 6.0 or later - Microsoft Azure SQL Database Before running this project, please fill in the values in App.config. @@ -10,3 +10,8 @@ Before running this project, please fill in the values in App.config. For detailed instructions and background information, please refer to Getting Started web page for Azure SQL Database Elastic Scale: http://go.microsoft.com/fwlink/?LinkID=510913 +## Authentication + +https://learn.microsoft.com/en-us/azure/azure-sql/database/authentication-aad-overview?view=azuresql#connect-by-using-microsoft-entra-identities + + diff --git a/Samples/ElasticScaleStarterKit/SqlDatabaseUtils.cs b/Samples/ElasticScaleStarterKit/SqlDatabaseUtils.cs index ef7313f..c88035a 100644 --- a/Samples/ElasticScaleStarterKit/SqlDatabaseUtils.cs +++ b/Samples/ElasticScaleStarterKit/SqlDatabaseUtils.cs @@ -3,12 +3,15 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using Microsoft.Data.SqlClient; using System.IO; using System.Text; using System.Threading; -using Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling; +using Azure.Identity; +using Azure.Core; +using System.Threading.Tasks; +using System.Configuration; +using Microsoft.Identity.Client.Platforms.Features.DesktopOs.Kerberos; namespace ElasticScaleStarterKit { @@ -25,19 +28,32 @@ internal static class SqlDatabaseUtils /// /// Returns true if we can connect to the database. /// - public static bool TryConnectToSqlDatabase() + public static bool TryConnectToSqlDatabaseAsync() { string connectionString = Configuration.GetConnectionString( Configuration.ShardMapManagerServerName, MasterDatabaseName); + // stuartpa: Testing MSI Auth + + // Uncomment one of the two lines depending on the identity type + //var credential = new DefaultAzureCredential(); // system-assigned identity + // var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions { ManagedIdentityClientId = ConfigurationManager.AppSettings["UserName"] }); // user-assigned identity + + // Get token for Azure SQL Database + // var token = credential.GetToken(new TokenRequestContext(new[] { "https://database.windows.net/.default" })); + try { using (SqlConnection conn = new SqlConnection( connectionString)) { conn.RetryLogicProvider = SqlRetryProvider; + + // Open the connection to the Azure SQL Database + // conn.AccessToken = token.Token; + conn.Open(); } diff --git a/Samples/ShardSqlCmd/LICENSE b/Samples/ShardSqlCmd/LICENSE index b8b569d..bfa6449 100644 --- a/Samples/ShardSqlCmd/LICENSE +++ b/Samples/ShardSqlCmd/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 Microsoft +Copyright (c) 2023 Microsoft Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Src/ElasticScale.Client/ElasticScale.Common/TransientFaultHandling/Implementation/RetryPolicy.cs b/Src/ElasticScale.Client/ElasticScale.Common/TransientFaultHandling/Implementation/RetryPolicy.cs index d06a445..fb7d695 100644 --- a/Src/ElasticScale.Client/ElasticScale.Common/TransientFaultHandling/Implementation/RetryPolicy.cs +++ b/Src/ElasticScale.Client/ElasticScale.Common/TransientFaultHandling/Implementation/RetryPolicy.cs @@ -6,7 +6,7 @@ namespace Microsoft.Azure.SqlDatabase.ElasticScale using System; using System.Threading; using System.Threading.Tasks; - + internal partial class TransientFaultHandling { /// diff --git a/Src/ElasticScale.Client/ShardManagement/ShardMap/ShardMapUtils.cs b/Src/ElasticScale.Client/ShardManagement/ShardMap/ShardMapUtils.cs index acf08de..34519a2 100644 --- a/Src/ElasticScale.Client/ShardManagement/ShardMap/ShardMapUtils.cs +++ b/Src/ElasticScale.Client/ShardManagement/ShardMap/ShardMapUtils.cs @@ -38,6 +38,7 @@ internal static class ShardMapUtils /// SqlAuthenticationMethod.ActiveDirectoryInteractive.ToString() cannot be used /// because it may not be available in the .NET framework version that we are running in /// + internal static readonly string ActiveDirectoryDefault = "ActiveDirectoryDefault"; internal static readonly string ActiveDirectoryInteractiveStr = "ActiveDirectoryInteractive"; internal static readonly string ActiveDirectoryServicePrincipal = "ActiveDirectoryServicePrincipal"; internal static readonly string ActiveDirectoryDeviceCodeFlow = "ActiveDirectoryDeviceCodeFlow"; diff --git a/Src/ElasticScale.Client/ShardManagement/SqlStore/SqlShardMapManagerCredentials.cs b/Src/ElasticScale.Client/ShardManagement/SqlStore/SqlShardMapManagerCredentials.cs index a0c45f2..cce84e1 100644 --- a/Src/ElasticScale.Client/ShardManagement/SqlStore/SqlShardMapManagerCredentials.cs +++ b/Src/ElasticScale.Client/ShardManagement/SqlStore/SqlShardMapManagerCredentials.cs @@ -167,6 +167,8 @@ internal static void EnsureCredentials( || authentication.Equals(ShardMapUtils.ActiveDirectoryServicePrincipal, StringComparison.OrdinalIgnoreCase) || authentication.Equals(ShardMapUtils.ActiveDirectoryDeviceCodeFlow, StringComparison.OrdinalIgnoreCase) || authentication.Equals(ShardMapUtils.ActiveDirectoryMSI, StringComparison.OrdinalIgnoreCase) + || authentication.Equals(ShardMapUtils.ActiveDirectoryDefault, StringComparison.OrdinalIgnoreCase) + ) { return; From 90ae369d454b90238787c2eaced055f41f8dd94d Mon Sep 17 00:00:00 2001 From: Stuart Padley Date: Tue, 21 Nov 2023 01:56:32 -0800 Subject: [PATCH 3/5] Saving changes --- Samples/Dapper/LICENSE | 2 +- Samples/EFCodeFirst/LICENSE | 2 +- Samples/EFMultiTenant/LICENSE | 2 +- Samples/ElasticScaleStarterKit/App.config | 11 ++++++----- Samples/ElasticScaleStarterKit/LICENSE | 2 +- Samples/ShardSqlCmd/LICENSE | 2 +- 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Samples/Dapper/LICENSE b/Samples/Dapper/LICENSE index bfa6449..a9d70d5 100644 --- a/Samples/Dapper/LICENSE +++ b/Samples/Dapper/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2023 Microsoft +Copyright (c) 2015-2023 Microsoft Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Samples/EFCodeFirst/LICENSE b/Samples/EFCodeFirst/LICENSE index bfa6449..a9d70d5 100644 --- a/Samples/EFCodeFirst/LICENSE +++ b/Samples/EFCodeFirst/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2023 Microsoft +Copyright (c) 2015-2023 Microsoft Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Samples/EFMultiTenant/LICENSE b/Samples/EFMultiTenant/LICENSE index bfa6449..a9d70d5 100644 --- a/Samples/EFMultiTenant/LICENSE +++ b/Samples/EFMultiTenant/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2023 Microsoft +Copyright (c) 2015-2023 Microsoft Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Samples/ElasticScaleStarterKit/App.config b/Samples/ElasticScaleStarterKit/App.config index 569cc77..2986cfb 100644 --- a/Samples/ElasticScaleStarterKit/App.config +++ b/Samples/ElasticScaleStarterKit/App.config @@ -8,15 +8,16 @@ When using Azure SQL DB, use the fully qualified DNS name like "abcdefghij.database.windows.net" --> - - + + + - +