EF Core 使用托管身份连接到 Azure SQL
EF Core Connection to Azure SQL with Managed Identity
我正在使用 EF Core 连接到部署到 Azure 应用服务的 Azure SQL 数据库。我正在使用访问令牌(通过托管身份获得)连接到 Azure SQL 数据库。
这是我的做法:
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
//code ignored for simplicity
services.AddDbContext<MyCustomDBContext>();
services.AddTransient<IDBAuthTokenService, AzureSqlAuthTokenService>();
}
MyCustomDBContext.cs
public partial class MyCustomDBContext : DbContext
{
public IConfiguration Configuration { get; }
public IDBAuthTokenService authTokenService { get; set; }
public CortexContext(IConfiguration configuration, IDBAuthTokenService tokenService, DbContextOptions<MyCustomDBContext> options)
: base(options)
{
Configuration = configuration;
authTokenService = tokenService;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
SqlConnection connection = new SqlConnection();
connection.ConnectionString = Configuration.GetConnectionString("defaultConnection");
connection.AccessToken = authTokenService.GetToken().Result;
optionsBuilder.UseSqlServer(connection);
}
}
AzureSqlAuthTokenService.cs
public class AzureSqlAuthTokenService : IDBAuthTokenService
{
public async Task<string> GetToken()
{
AzureServiceTokenProvider provider = new AzureServiceTokenProvider();
var token = await provider.GetAccessTokenAsync("https://database.windows.net/");
return token;
}
}
这很好用,我可以从数据库中获取数据。但我不确定这样做是否正确。
我的问题:
- 这是正确的方法吗?还是会出现性能问题?
- 我需要担心令牌过期吗?我现在没有缓存令牌。
- EF Core 是否有更好的方法来处理这个问题?
Is this a right way to do it or will it have issues with performance?
这是正确的方法。为每个新的 DbContext 调用 OnConfiguring,因此假设您没有任何长期存在的 DbContext 实例,这是正确的模式。
Do I need to worry about token expiration? I am not caching the token as of now.
AzureServiceTokenProvider
负责缓存。
Does EF Core has any better way to handle this?
记录了 .NET Core 中 SqlClient 的 AAD 身份验证方法 here。
对于使用 .NET Framework for Managed Identity 的开发人员,以下代码可能有助于获取实体连接:
app.config:
<add key="ResourceId" value="https://database.windows.net/" />
<add key="Con" value="data source=tcp:sampledbserver.database.windows.net,1433;initial catalog=sampledb;MultipleActiveResultSets=True;Connect Timeout=30;" />
c#文件
using System;
using System.Configuration;
using System.Data.Entity.Core.EntityClient;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.SqlClient;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Microsoft.Azure.Services.AppAuthentication;
public static EntityConnection GetEntityConnectionString()
{
MetadataWorkspace workspace = new MetadataWorkspace(
new string[] { "res://*/" },
new Assembly[] { Assembly.GetExecutingAssembly() });
SqlConnection sqlConnection = new SqlConnection(Con);
var result = (new AzureServiceTokenProvider()).GetAccessTokenAsync(ResourceId).Result;
sqlConnection.AccessToken = result ?? throw new InvalidOperationException("Failed to obtain the access token");
EntityConnection entityConnection = new EntityConnection(
workspace,
sqlConnection);
return entityConnection;
}
对于那些仍然遇到同样问题的人,我已经使用 DbInterceptor
解决了这个问题,这样我就可以在不阻塞应用程序的情况下异步获取令牌。我在 EF Core 存储库上打开了一个问题,但我已经用解决方案关闭了:
https://github.com/dotnet/efcore/issues/21043
希望对您有所帮助。
虽然这种方法通常是正确的,因为除了必须编写自定义代码来设置连接的 AccessToken
之外别无他法,但您的实现中可能存在一些问题通过使用 DbConnectionInterceptor
来避免,我将在下面描述。这两个问题是:
- 您负责自己创建连接对象。但是你不处理它。处置在您的实施中会很棘手,这就是您可能跳过它的原因。
- 您的代码正在阻塞,因为您在等待访问令牌时使用
.Result
进行阻塞。
更好的替代方法是使用 EF Core 支持的拦截器。您将从这样的 DbContext
开始:
public class MyCustomDbContextFactory : IMyCustomDbContextFactory
{
private readonly string _connectionString;
private readonly AzureAuthenticationInterceptor _azureAuthenticationInterceptor;
public MyCustomDbContextFactory(DbContextFactoryOptions options, AzureAuthenticationInterceptor azureAuthenticationInterceptor)
{
_connectionString = options.ConnectionString;
_azureAuthenticationInterceptor = azureAuthenticationInterceptor;
}
public MyCustomDbContext Create()
{
var optionsBuilder = new DbContextOptionsBuilder<MyCustomDbContext>();
optionsBuilder
.UseSqlServer(_connectionString)
.AddInterceptors(_azureAuthenticationInterceptor);
return new MyCustomDbContext(optionsBuilder.Options);
}
}
这是拦截器的实现:
public class AzureAuthenticationInterceptor : DbConnectionInterceptor
{
private const string AzureDatabaseResourceIdentifier = "https://database.windows.net";
private readonly AzureServiceTokenProvider _azureServiceTokenProvider;
public AzureAuthenticationInterceptor(AzureServiceTokenProvider azureServiceTokenProvider) : base()
{
_azureServiceTokenProvider = azureServiceTokenProvider;
}
public override async ValueTask<InterceptionResult> ConnectionOpeningAsync(DbConnection connection, ConnectionEventData eventData, InterceptionResult result, CancellationToken cancellationToken = default)
{
if (connection is SqlConnection sqlConnection)
{
sqlConnection.AccessToken = await GetAccessToken();
}
return result;
}
public override InterceptionResult ConnectionOpening(DbConnection connection, ConnectionEventData eventData, InterceptionResult result)
{
if (connection is SqlConnection sqlConnection)
{
sqlConnection.AccessToken = GetAccessToken().Result;
}
return result;
}
private Task<string> GetAccessToken() => _azureServiceTokenProvider.GetAccessTokenAsync(AzureDatabaseResourceIdentifier);
}
这是配置服务的方法:
services.AddSingleton(new DbContextFactoryOptions(connection_string));
services.AddSingleton(new AzureAuthenticationInterceptor(new AzureServiceTokenProvider()));
最后,这是在您的存储库中实例化 DbContext
对象的方法:
public async Task<IEnumerable<MyCustomEntity>> GetAll()
{
using var context = _notificationsDbContextFactory.Create(); // Injected in ctor
var dbos = await context.MyCustomEntity.ToListAsync();
return ... // something;
}
已投票。
这是对 Romar 出色回答的附加回答。这对我们非常有用,使我们能够消除 ConnectionString 中的用户凭据。然而,这给我们带来了需要使用秘密检索访问令牌的问题,这是我们也不希望包含在 appsettings 文件中的敏感信息。因此,我们将一个问题换成了另一个问题。
网络上还有其他帖子处理此问题。因此,我发布了一个综合而全面的答案,从 appsettings 文件中完全删除了敏感数据。注意:您需要将机密迁移到 KeyVault 中。在这种情况下,我们将其命名为AzureSqlSecret
。这是为了检索数据库用户的凭据。
调用AzureAuthenticationInterceptor
的实体class构造函数如下:
public ProjectNameEntities() :
base(new DbContextOptionsBuilder<ProjectNameEntities>()
.UseSqlServer(ConfigurationManager.ConnectionStrings["ProjectNameEntities"].ConnectionString)
.AddInterceptors(new AzureAuthenticationInterceptor())
.Options)
{ }
AzureAuthenticationInterceptor:
#region NameSpaces
using Azure.Core;
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System;
using System.Configuration;
using System.Data.Common;
using System.Threading;
using System.Threading.Tasks;
#endregion
namespace <ProjectName>.DataAccess.Helpers
{
public class AzureAuthenticationInterceptor : DbConnectionInterceptor
{
#region Constructor
public AzureAuthenticationInterceptor()
{
SecretClientOptions objSecretClientOptions;
string strAzureKeyVaultResourceIdentifier;
string strAzureKeyVault;
string strAzureKeyVaultUri;
strAzureKeyVaultResourceIdentifier = ConfigurationManager.AppSettings["Azure:ResourceIdentifiers:KeyVault"];
strAzureKeyVault = ConfigurationManager.AppSettings["Azure:KeyVaults:TaxPaymentSystem"];
strAzureKeyVaultUri = strAzureKeyVaultResourceIdentifier.Replace("{0}", strAzureKeyVault);
// Set the options on the SecretClient. These are default values that are recommended by Microsoft.
objSecretClientOptions = new SecretClientOptions()
{
Retry =
{
Delay= TimeSpan.FromSeconds(2),
MaxDelay = TimeSpan.FromSeconds(16),
MaxRetries = 5,
Mode = RetryMode.Exponential
}
};
this.SecretClient = new SecretClient(
vaultUri: new Uri(strAzureKeyVaultUri),
credential: new DefaultAzureCredential(),
objSecretClientOptions
);
this.KeyVaultSecret = this.SecretClient.GetSecret("AzureSqlSecret");
this.strKeyVaultSecret = this.KeyVaultSecret.Value;
this.strAzureResourceIdentifierAuthentication = ConfigurationManager.AppSettings["Azure:ResourceIdentifiers:Authentication"];
this.strAzureResourceIdentifierDatabase = ConfigurationManager.AppSettings["Azure:ResourceIdentifiers:DataBase"];
this.strClientId = ConfigurationManager.AppSettings["Azure:DatabaseUsername:ClientId"];
this.strTenantId = ConfigurationManager.AppSettings["Azure:TenantId"];
}
#endregion
#region Methods
public override async ValueTask<InterceptionResult> ConnectionOpeningAsync(
DbConnection objDbConnection,
ConnectionEventData objEventData,
InterceptionResult objReturn,
CancellationToken objCancellationToken = default)
{
_ILogger.Debug("Reached the Async Interceptor method");
if (objDbConnection is SqlConnection objSqlConnection)
{
objSqlConnection.AccessToken = GetAccessToken();
}
return objReturn;
}
public override InterceptionResult ConnectionOpening(
DbConnection objDbConnection,
ConnectionEventData objConnectionEventData,
InterceptionResult objReturn)
{
_ILogger.Debug("Reached the non-Async Interceptor method");
if (objDbConnection is SqlConnection objSqlConnection)
{
objSqlConnection.AccessToken = GetAccessToken();
}
return objReturn;
}
private string GetAccessToken()
{
AuthenticationContext objAuthenticationContext;
AuthenticationResult objAuthenticationResult;
ClientCredential objClientCredential;
objAuthenticationContext = new AuthenticationContext(string.Format("{0}/{1}"
, this.strAzureResourceIdentifierAuthentication
, this.strTenantId));
objClientCredential = new ClientCredential(this.strClientId, this.strKeyVaultSecret);
objAuthenticationResult = objAuthenticationContext.AcquireTokenAsync(this.strAzureResourceIdentifierDatabase, objClientCredential).Result;
return objAuthenticationResult.AccessToken;
}
#endregion
#region Properties
readonly <ProjectName>.Common.Logging.ILogger _ILogger = <ProjectName>.Common.Logging.LogWrapper.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
private SecretClient SecretClient;
private KeyVaultSecret KeyVaultSecret;
private string strAzureResourceIdentifierDatabase;
private string strAzureResourceIdentifierAuthentication;
private string strKeyVaultSecret;
private string strClientId;
private string strTenantId;
#endregion
}
}
Microsoft.Data.SqlClient 到来后 - Entity framework 核心连接器的新版本 sql - 现在非常简单:
Install-Package Microsoft.Data.SqlClient -Version 4.0.1
将连接字符串添加到 Dotnet 核心应用程序,如下所示:
"Server=tcp:<server-name>.database.windows.net;Authentication=Active Directory Default; Database=<database-name>;"
然后使用它通过 Azure SQL 连接使用托管身份连接到 Azure SQL,如下所示:
using (SqlConnection _connection = new SqlConnection(sqlConnectionString))
{
_connection.Open();
// do some stuff with the sqlconnection to read or write record in SQL.
_connection.Close();
return true;
}
我正在使用 EF Core 连接到部署到 Azure 应用服务的 Azure SQL 数据库。我正在使用访问令牌(通过托管身份获得)连接到 Azure SQL 数据库。
这是我的做法:
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
//code ignored for simplicity
services.AddDbContext<MyCustomDBContext>();
services.AddTransient<IDBAuthTokenService, AzureSqlAuthTokenService>();
}
MyCustomDBContext.cs
public partial class MyCustomDBContext : DbContext
{
public IConfiguration Configuration { get; }
public IDBAuthTokenService authTokenService { get; set; }
public CortexContext(IConfiguration configuration, IDBAuthTokenService tokenService, DbContextOptions<MyCustomDBContext> options)
: base(options)
{
Configuration = configuration;
authTokenService = tokenService;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
SqlConnection connection = new SqlConnection();
connection.ConnectionString = Configuration.GetConnectionString("defaultConnection");
connection.AccessToken = authTokenService.GetToken().Result;
optionsBuilder.UseSqlServer(connection);
}
}
AzureSqlAuthTokenService.cs
public class AzureSqlAuthTokenService : IDBAuthTokenService
{
public async Task<string> GetToken()
{
AzureServiceTokenProvider provider = new AzureServiceTokenProvider();
var token = await provider.GetAccessTokenAsync("https://database.windows.net/");
return token;
}
}
这很好用,我可以从数据库中获取数据。但我不确定这样做是否正确。
我的问题:
- 这是正确的方法吗?还是会出现性能问题?
- 我需要担心令牌过期吗?我现在没有缓存令牌。
- EF Core 是否有更好的方法来处理这个问题?
Is this a right way to do it or will it have issues with performance?
这是正确的方法。为每个新的 DbContext 调用 OnConfiguring,因此假设您没有任何长期存在的 DbContext 实例,这是正确的模式。
Do I need to worry about token expiration? I am not caching the token as of now.
AzureServiceTokenProvider
负责缓存。
Does EF Core has any better way to handle this?
记录了 .NET Core 中 SqlClient 的 AAD 身份验证方法 here。
对于使用 .NET Framework for Managed Identity 的开发人员,以下代码可能有助于获取实体连接:
app.config:
<add key="ResourceId" value="https://database.windows.net/" />
<add key="Con" value="data source=tcp:sampledbserver.database.windows.net,1433;initial catalog=sampledb;MultipleActiveResultSets=True;Connect Timeout=30;" />
c#文件
using System;
using System.Configuration;
using System.Data.Entity.Core.EntityClient;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.SqlClient;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Microsoft.Azure.Services.AppAuthentication;
public static EntityConnection GetEntityConnectionString()
{
MetadataWorkspace workspace = new MetadataWorkspace(
new string[] { "res://*/" },
new Assembly[] { Assembly.GetExecutingAssembly() });
SqlConnection sqlConnection = new SqlConnection(Con);
var result = (new AzureServiceTokenProvider()).GetAccessTokenAsync(ResourceId).Result;
sqlConnection.AccessToken = result ?? throw new InvalidOperationException("Failed to obtain the access token");
EntityConnection entityConnection = new EntityConnection(
workspace,
sqlConnection);
return entityConnection;
}
对于那些仍然遇到同样问题的人,我已经使用 DbInterceptor
解决了这个问题,这样我就可以在不阻塞应用程序的情况下异步获取令牌。我在 EF Core 存储库上打开了一个问题,但我已经用解决方案关闭了:
https://github.com/dotnet/efcore/issues/21043
希望对您有所帮助。
虽然这种方法通常是正确的,因为除了必须编写自定义代码来设置连接的 AccessToken
之外别无他法,但您的实现中可能存在一些问题通过使用 DbConnectionInterceptor
来避免,我将在下面描述。这两个问题是:
- 您负责自己创建连接对象。但是你不处理它。处置在您的实施中会很棘手,这就是您可能跳过它的原因。
- 您的代码正在阻塞,因为您在等待访问令牌时使用
.Result
进行阻塞。
更好的替代方法是使用 EF Core 支持的拦截器。您将从这样的 DbContext
开始:
public class MyCustomDbContextFactory : IMyCustomDbContextFactory
{
private readonly string _connectionString;
private readonly AzureAuthenticationInterceptor _azureAuthenticationInterceptor;
public MyCustomDbContextFactory(DbContextFactoryOptions options, AzureAuthenticationInterceptor azureAuthenticationInterceptor)
{
_connectionString = options.ConnectionString;
_azureAuthenticationInterceptor = azureAuthenticationInterceptor;
}
public MyCustomDbContext Create()
{
var optionsBuilder = new DbContextOptionsBuilder<MyCustomDbContext>();
optionsBuilder
.UseSqlServer(_connectionString)
.AddInterceptors(_azureAuthenticationInterceptor);
return new MyCustomDbContext(optionsBuilder.Options);
}
}
这是拦截器的实现:
public class AzureAuthenticationInterceptor : DbConnectionInterceptor
{
private const string AzureDatabaseResourceIdentifier = "https://database.windows.net";
private readonly AzureServiceTokenProvider _azureServiceTokenProvider;
public AzureAuthenticationInterceptor(AzureServiceTokenProvider azureServiceTokenProvider) : base()
{
_azureServiceTokenProvider = azureServiceTokenProvider;
}
public override async ValueTask<InterceptionResult> ConnectionOpeningAsync(DbConnection connection, ConnectionEventData eventData, InterceptionResult result, CancellationToken cancellationToken = default)
{
if (connection is SqlConnection sqlConnection)
{
sqlConnection.AccessToken = await GetAccessToken();
}
return result;
}
public override InterceptionResult ConnectionOpening(DbConnection connection, ConnectionEventData eventData, InterceptionResult result)
{
if (connection is SqlConnection sqlConnection)
{
sqlConnection.AccessToken = GetAccessToken().Result;
}
return result;
}
private Task<string> GetAccessToken() => _azureServiceTokenProvider.GetAccessTokenAsync(AzureDatabaseResourceIdentifier);
}
这是配置服务的方法:
services.AddSingleton(new DbContextFactoryOptions(connection_string));
services.AddSingleton(new AzureAuthenticationInterceptor(new AzureServiceTokenProvider()));
最后,这是在您的存储库中实例化 DbContext
对象的方法:
public async Task<IEnumerable<MyCustomEntity>> GetAll()
{
using var context = _notificationsDbContextFactory.Create(); // Injected in ctor
var dbos = await context.MyCustomEntity.ToListAsync();
return ... // something;
}
已投票。
这是对 Romar 出色回答的附加回答。这对我们非常有用,使我们能够消除 ConnectionString 中的用户凭据。然而,这给我们带来了需要使用秘密检索访问令牌的问题,这是我们也不希望包含在 appsettings 文件中的敏感信息。因此,我们将一个问题换成了另一个问题。
网络上还有其他帖子处理此问题。因此,我发布了一个综合而全面的答案,从 appsettings 文件中完全删除了敏感数据。注意:您需要将机密迁移到 KeyVault 中。在这种情况下,我们将其命名为AzureSqlSecret
。这是为了检索数据库用户的凭据。
调用AzureAuthenticationInterceptor
的实体class构造函数如下:
public ProjectNameEntities() :
base(new DbContextOptionsBuilder<ProjectNameEntities>()
.UseSqlServer(ConfigurationManager.ConnectionStrings["ProjectNameEntities"].ConnectionString)
.AddInterceptors(new AzureAuthenticationInterceptor())
.Options)
{ }
AzureAuthenticationInterceptor:
#region NameSpaces
using Azure.Core;
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System;
using System.Configuration;
using System.Data.Common;
using System.Threading;
using System.Threading.Tasks;
#endregion
namespace <ProjectName>.DataAccess.Helpers
{
public class AzureAuthenticationInterceptor : DbConnectionInterceptor
{
#region Constructor
public AzureAuthenticationInterceptor()
{
SecretClientOptions objSecretClientOptions;
string strAzureKeyVaultResourceIdentifier;
string strAzureKeyVault;
string strAzureKeyVaultUri;
strAzureKeyVaultResourceIdentifier = ConfigurationManager.AppSettings["Azure:ResourceIdentifiers:KeyVault"];
strAzureKeyVault = ConfigurationManager.AppSettings["Azure:KeyVaults:TaxPaymentSystem"];
strAzureKeyVaultUri = strAzureKeyVaultResourceIdentifier.Replace("{0}", strAzureKeyVault);
// Set the options on the SecretClient. These are default values that are recommended by Microsoft.
objSecretClientOptions = new SecretClientOptions()
{
Retry =
{
Delay= TimeSpan.FromSeconds(2),
MaxDelay = TimeSpan.FromSeconds(16),
MaxRetries = 5,
Mode = RetryMode.Exponential
}
};
this.SecretClient = new SecretClient(
vaultUri: new Uri(strAzureKeyVaultUri),
credential: new DefaultAzureCredential(),
objSecretClientOptions
);
this.KeyVaultSecret = this.SecretClient.GetSecret("AzureSqlSecret");
this.strKeyVaultSecret = this.KeyVaultSecret.Value;
this.strAzureResourceIdentifierAuthentication = ConfigurationManager.AppSettings["Azure:ResourceIdentifiers:Authentication"];
this.strAzureResourceIdentifierDatabase = ConfigurationManager.AppSettings["Azure:ResourceIdentifiers:DataBase"];
this.strClientId = ConfigurationManager.AppSettings["Azure:DatabaseUsername:ClientId"];
this.strTenantId = ConfigurationManager.AppSettings["Azure:TenantId"];
}
#endregion
#region Methods
public override async ValueTask<InterceptionResult> ConnectionOpeningAsync(
DbConnection objDbConnection,
ConnectionEventData objEventData,
InterceptionResult objReturn,
CancellationToken objCancellationToken = default)
{
_ILogger.Debug("Reached the Async Interceptor method");
if (objDbConnection is SqlConnection objSqlConnection)
{
objSqlConnection.AccessToken = GetAccessToken();
}
return objReturn;
}
public override InterceptionResult ConnectionOpening(
DbConnection objDbConnection,
ConnectionEventData objConnectionEventData,
InterceptionResult objReturn)
{
_ILogger.Debug("Reached the non-Async Interceptor method");
if (objDbConnection is SqlConnection objSqlConnection)
{
objSqlConnection.AccessToken = GetAccessToken();
}
return objReturn;
}
private string GetAccessToken()
{
AuthenticationContext objAuthenticationContext;
AuthenticationResult objAuthenticationResult;
ClientCredential objClientCredential;
objAuthenticationContext = new AuthenticationContext(string.Format("{0}/{1}"
, this.strAzureResourceIdentifierAuthentication
, this.strTenantId));
objClientCredential = new ClientCredential(this.strClientId, this.strKeyVaultSecret);
objAuthenticationResult = objAuthenticationContext.AcquireTokenAsync(this.strAzureResourceIdentifierDatabase, objClientCredential).Result;
return objAuthenticationResult.AccessToken;
}
#endregion
#region Properties
readonly <ProjectName>.Common.Logging.ILogger _ILogger = <ProjectName>.Common.Logging.LogWrapper.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
private SecretClient SecretClient;
private KeyVaultSecret KeyVaultSecret;
private string strAzureResourceIdentifierDatabase;
private string strAzureResourceIdentifierAuthentication;
private string strKeyVaultSecret;
private string strClientId;
private string strTenantId;
#endregion
}
}
Microsoft.Data.SqlClient 到来后 - Entity framework 核心连接器的新版本 sql - 现在非常简单:
Install-Package Microsoft.Data.SqlClient -Version 4.0.1
将连接字符串添加到 Dotnet 核心应用程序,如下所示:
"Server=tcp:<server-name>.database.windows.net;Authentication=Active Directory Default; Database=<database-name>;"
然后使用它通过 Azure SQL 连接使用托管身份连接到 Azure SQL,如下所示:
using (SqlConnection _connection = new SqlConnection(sqlConnectionString))
{
_connection.Open();
// do some stuff with the sqlconnection to read or write record in SQL.
_connection.Close();
return true;
}