In this article, we will discuss about replicate the local db
BacpacReplicaService.cs
-------------------------------------
using Microsoft.SqlServer.Dac;
namespace QaLocalDbReplica;
public sealed class BacpacReplicaService
{
public event EventHandler<string>? StatusChanged;
public event EventHandler<int>? ProgressChanged;
public async Task<string> CreateReplicaAsync(
ReplicaRequest request,
CancellationToken cancellationToken)
{
ValidateRequest(request);
await LocalDbService.EnsureLocalDbAvailableAsync(cancellationToken);
string tempDirectory = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"QaLocalDbReplica",
"Bacpac");
Directory.CreateDirectory(tempDirectory);
string bacpacPath = Path.Combine(
tempDirectory,
$"{SanitizeFileName(request.SourceDatabase)}_{DateTime.Now:yyyyMMdd_HHmmss}.bacpac");
string sourceConnectionString =
ConnectionStringFactory.BuildSource(
request.SourceServer,
request.SourceDatabase,
request.UseWindowsAuthentication,
request.UserName,
request.Password);
try
{
ReportStatus("Exporting the selected QA database to BACPAC...");
ReportProgress(0);
await Task.Run(
() => ExportBacpac(
sourceConnectionString,
request.SourceDatabase,
bacpacPath,
cancellationToken),
cancellationToken);
ReportProgress(50);
bool exists = await LocalDbService.DatabaseExistsAsync(
request.DestinationDatabase,
cancellationToken);
if (exists && !request.ReplaceExistingDestination)
{
throw new InvalidOperationException(
$"LocalDB database '{request.DestinationDatabase}' already exists.");
}
if (exists)
{
ReportStatus(
$"Removing existing LocalDB database '{request.DestinationDatabase}'...");
await LocalDbService.DropDatabaseIfExistsAsync(
request.DestinationDatabase,
cancellationToken);
}
ReportStatus("Importing BACPAC into LocalDB...");
await Task.Run(
() => ImportBacpac(
request.DestinationDatabase,
bacpacPath,
cancellationToken),
cancellationToken);
ReportProgress(100);
ReportStatus(
$"Replica '{request.DestinationDatabase}' was created successfully.");
return bacpacPath;
}
finally
{
if (request.DeleteTemporaryBacpac)
{
TryDeleteFile(bacpacPath);
}
}
}
private void ExportBacpac(
string sourceConnectionString,
string sourceDatabase,
string bacpacPath,
CancellationToken cancellationToken)
{
if (File.Exists(bacpacPath))
File.Delete(bacpacPath);
// Try to fix permissions issue proactively
TryFixAzureSqlPermissions(sourceConnectionString);
var services = new DacServices(sourceConnectionString);
services.Message += (_, e) =>
ReportStatus($"Export: {e.Message.Message}");
services.ProgressChanged += (_, e) =>
{
int mapped = Math.Clamp((int)e.Status / 2, 0, 50);
ReportProgress(mapped);
if (!string.IsNullOrWhiteSpace(e.Message))
ReportStatus($"Export: {e.Message}");
};
var options = new DacExportOptions
{
CommandTimeout = 0,
VerifyExtraction = false,
Storage = DacSchemaModelStorageType.Memory
};
services.ExportBacpac(
bacpacPath,
sourceDatabase,
options,
tables: null,
cancellationToken);
}
private void TryFixAzureSqlPermissions(string connectionString)
{
try
{
using var connection = new Microsoft.Data.SqlClient.SqlConnection(connectionString);
connection.Open();
ReportStatus("Checking database compatibility settings...");
// Try to revoke the problematic CONNECT permission from guest user
try
{
string fixSql = "REVOKE CONNECT FROM guest;";
using var command = new Microsoft.Data.SqlClient.SqlCommand(fixSql, connection);
command.CommandTimeout = 10;
command.ExecuteNonQuery();
ReportStatus("Revoked CONNECT permission from guest user.");
}
catch (Exception ex)
{
ReportStatus($"Could not revoke guest CONNECT permission: {ex.Message}");
}
}
catch (Exception ex)
{
ReportStatus($"Warning: Permission check failed: {ex.Message}");
}
}
private void ImportBacpac(
string destinationDatabase,
string bacpacPath,
CancellationToken cancellationToken)
{
var services =
new DacServices(ConnectionStringFactory.BuildLocalMaster());
services.Message += (_, e) =>
ReportStatus($"Import: {e.Message.Message}");
services.ProgressChanged += (_, e) =>
{
int mapped = 50 + Math.Clamp((int)e.Status / 2, 0, 50);
ReportProgress(mapped);
if (!string.IsNullOrWhiteSpace(e.Message))
ReportStatus($"Import: {e.Message}");
};
using BacPackage package = BacPackage.Load(bacpacPath);
var options = new DacImportOptions
{
CommandTimeout = 0
};
services.ImportBacpac(
package,
destinationDatabase,
options,
cancellationToken);
}
private static void ValidateRequest(ReplicaRequest request)
{
if (string.IsNullOrWhiteSpace(request.SourceServer))
throw new ArgumentException("Source server is required.");
if (string.IsNullOrWhiteSpace(request.SourceDatabase))
throw new ArgumentException("Source database is required.");
LocalDbService.ValidateDatabaseName(request.DestinationDatabase);
if (!request.UseWindowsAuthentication)
{
if (string.IsNullOrWhiteSpace(request.UserName))
throw new ArgumentException("SQL username is required.");
if (string.IsNullOrWhiteSpace(request.Password))
throw new ArgumentException("SQL password is required.");
}
}
private static string SanitizeFileName(string value)
{
foreach (char invalid in Path.GetInvalidFileNameChars())
value = value.Replace(invalid, '_');
return value;
}
private static void TryDeleteFile(string path)
{
try
{
if (File.Exists(path))
File.Delete(path);
}
catch
{
// Cleanup failure should not hide the real export/import result.
}
}
private void ReportStatus(string message) =>
StatusChanged?.Invoke(this, message);
private void ReportProgress(int percentage) =>
ProgressChanged?.Invoke(this, Math.Clamp(percentage, 0, 100));
}
-----------------------------------------------------------------------------
ConnectionStringFactory.cs
------------------------------------
using Microsoft.Data.SqlClient;
namespace QaLocalDbReplica;
public static class ConnectionStringFactory
{
public const string LocalDbInstance = @"(localdb)\MSSQLLocalDB";
public static string BuildSource(
string server,
string database,
bool windowsAuthentication,
string? userName,
string? password)
{
var builder = new SqlConnectionStringBuilder
{
DataSource = server.Trim(),
InitialCatalog = database,
IntegratedSecurity = windowsAuthentication,
Encrypt = false,
TrustServerCertificate = true,
ConnectTimeout = 30,
ApplicationName = "QA LocalDB Replica"
};
if (!windowsAuthentication)
{
builder.UserID = userName?.Trim() ?? string.Empty;
builder.Password = password ?? string.Empty;
}
return builder.ConnectionString;
}
public static string BuildSourceMaster(
string server,
bool windowsAuthentication,
string? userName,
string? password) =>
BuildSource(server, "master", windowsAuthentication, userName, password);
public static string BuildLocalMaster()
{
return new SqlConnectionStringBuilder
{
DataSource = LocalDbInstance,
InitialCatalog = "master",
IntegratedSecurity = true,
Encrypt = false,
TrustServerCertificate = true,
ConnectTimeout = 30,
ApplicationName = "QA LocalDB Replica"
}.ConnectionString;
}
public static string BuildLocalDatabase(string database)
{
return new SqlConnectionStringBuilder
{
DataSource = LocalDbInstance,
InitialCatalog = database,
IntegratedSecurity = true,
Encrypt = false,
TrustServerCertificate = true,
ConnectTimeout = 30,
ApplicationName = "QA LocalDB Re.csplica"
}.ConnectionString;
}
}
-------------------------------------------------------------------------------------------
DatabaseListItem.cs
---------------------------------
namespace QaLocalDbReplica;
public sealed class DatabaseListItem
{
public string Name { get; init; } = string.Empty;
public override string ToString() => Name;
}
----------------------------------------------------------------------------------------------------
LocalDbService.cs
----------------------------
using Microsoft.Data.SqlClient;
using System.Text.RegularExpressions;
namespace QaLocalDbReplica;
public static class LocalDbService
{
private static readonly Regex ValidDatabaseName =
new(@"^[A-Za-z0-9_]+$", RegexOptions.Compiled);
public static void ValidateDatabaseName(string databaseName)
{
if (string.IsNullOrWhiteSpace(databaseName))
throw new ArgumentException("Destination database name is required.");
if (!ValidDatabaseName.IsMatch(databaseName))
{
throw new ArgumentException(
"Destination database name can contain only letters, numbers, and underscore.");
}
}
public static async Task EnsureLocalDbAvailableAsync(
CancellationToken cancellationToken)
{
await using var connection =
new SqlConnection(ConnectionStringFactory.BuildLocalMaster());
await connection.OpenAsync(cancellationToken);
await using var command =
new SqlCommand("SELECT CAST(SERVERPROPERTY('ProductVersion') AS nvarchar(50));", connection);
_ = await command.ExecuteScalarAsync(cancellationToken);
}
public static async Task<bool> DatabaseExistsAsync(
string databaseName,
CancellationToken cancellationToken)
{
await using var connection =
new SqlConnection(ConnectionStringFactory.BuildLocalMaster());
await connection.OpenAsync(cancellationToken);
await using var command =
new SqlCommand("SELECT CASE WHEN DB_ID(@name) IS NULL THEN 0 ELSE 1 END;", connection);
command.Parameters.AddWithValue("@name", databaseName);
return Convert.ToInt32(
await command.ExecuteScalarAsync(cancellationToken)) == 1;
}
public static async Task DropDatabaseIfExistsAsync(
string databaseName,
CancellationToken cancellationToken)
{
ValidateDatabaseName(databaseName);
string quotedName = QuoteIdentifier(databaseName);
string sql = $"""
IF DB_ID(@databaseName) IS NOT NULL
BEGIN
ALTER DATABASE {quotedName}
SET SINGLE_USER
WITH ROLLBACK IMMEDIATE;
DROP DATABASE {quotedName};
END;
""";
await using var connection =
new SqlConnection(ConnectionStringFactory.BuildLocalMaster());
await connection.OpenAsync(cancellationToken);
await using var command = new SqlCommand(sql, connection)
{
CommandTimeout = 0
};
command.Parameters.AddWithValue("@databaseName", databaseName);
await command.ExecuteNonQueryAsync(cancellationToken);
}
private static string QuoteIdentifier(string value) =>
"[" + value.Replace("]", "]]") + "]";
}
----------------------------------------------------------------------------------------------------
MainForm.cs
-------------------------
namespace QaLocalDbReplica;
public partial class MainForm : Form
{
private CancellationTokenSource? _operationCancellation;
public MainForm()
{
InitializeComponent();
cmbAuthentication.SelectedIndex = 0;
txtDestinationDatabase.Text = "QA_Local";
UpdateAuthenticationControls();
}
private async void btnLoadDatabases_Click(object sender, EventArgs e)
{
try
{
SetBusy(true);
ClearLog();
ValidateSourceInput(requireDatabase: false);
Log("Connecting to source SQL Server...");
lblStatus.Text = "Loading source databases...";
using var cancellation = new CancellationTokenSource();
IReadOnlyList<DatabaseListItem> databases =
await SourceDatabaseService.GetDatabasesAsync(
txtSourceServer.Text.Trim(),
UseWindowsAuthentication,
txtUserName.Text.Trim(),
txtPassword.Text,
cancellation.Token);
cmbSourceDatabase.DataSource = databases.ToList();
Log($"Loaded {databases.Count} database(s).");
lblStatus.Text = "Select the QA database.";
}
catch (Exception ex)
{
ShowError("Unable to load databases", ex);
}
finally
{
SetBusy(false);
}
}
private async void btnSubmit_Click(object sender, EventArgs e)
{
try
{
ValidateSourceInput(requireDatabase: true);
LocalDbService.ValidateDatabaseName(txtDestinationDatabase.Text.Trim());
SetBusy(true);
btnCancel.Enabled = true;
ClearLog();
progressBar.Value = 0;
_operationCancellation = new CancellationTokenSource();
var request = new ReplicaRequest
{
SourceServer = txtSourceServer.Text.Trim(),
SourceDatabase =
((DatabaseListItem)cmbSourceDatabase.SelectedItem!).Name,
DestinationDatabase = txtDestinationDatabase.Text.Trim(),
UseWindowsAuthentication = UseWindowsAuthentication,
UserName = txtUserName.Text.Trim(),
Password = txtPassword.Text,
ReplaceExistingDestination = chkReplaceExisting.Checked,
DeleteTemporaryBacpac = chkDeleteBacpac.Checked
};
var service = new BacpacReplicaService();
service.StatusChanged += (_, message) =>
SafeUi(() =>
{
lblStatus.Text = message;
Log(message);
});
service.ProgressChanged += (_, percentage) =>
SafeUi(() => progressBar.Value = percentage);
string bacpacPath =
await service.CreateReplicaAsync(
request,
_operationCancellation.Token);
string localConnection =
ConnectionStringFactory.BuildLocalDatabase(
request.DestinationDatabase);
string message = request.DeleteTemporaryBacpac
? $"LocalDB replica '{request.DestinationDatabase}' was created successfully."
: $"LocalDB replica was created successfully.\n\nBACPAC: {bacpacPath}";
MessageBox.Show(
message +
$"\n\nConnection string:\n{localConnection}",
"Completed",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
catch (OperationCanceledException)
{
lblStatus.Text = "Operation cancelled.";
Log("Operation cancelled.");
}
catch (Exception ex)
{
ShowError("Replica creation failed", ex);
}
finally
{
_operationCancellation?.Dispose();
_operationCancellation = null;
btnCancel.Enabled = false;
SetBusy(false);
}
}
private void btnCancel_Click(object sender, EventArgs e)
{
btnCancel.Enabled = false;
lblStatus.Text = "Cancellation requested...";
Log("Cancellation requested...");
_operationCancellation?.Cancel();
}
private void cmbAuthentication_SelectedIndexChanged(
object sender,
EventArgs e)
{
UpdateAuthenticationControls();
}
private bool UseWindowsAuthentication =>
cmbAuthentication.SelectedIndex == 0;
private void UpdateAuthenticationControls()
{
bool sqlAuthentication = !UseWindowsAuthentication;
txtUserName.Enabled = sqlAuthentication;
txtPassword.Enabled = sqlAuthentication;
}
private void ValidateSourceInput(bool requireDatabase)
{
if (string.IsNullOrWhiteSpace(txtSourceServer.Text))
throw new InvalidOperationException("Enter the source SQL Server name.");
if (!UseWindowsAuthentication)
{
if (string.IsNullOrWhiteSpace(txtUserName.Text))
throw new InvalidOperationException("Enter the SQL username.");
if (string.IsNullOrWhiteSpace(txtPassword.Text))
throw new InvalidOperationException("Enter the SQL password.");
}
if (requireDatabase && cmbSourceDatabase.SelectedItem is null)
throw new InvalidOperationException("Select the source QA database.");
}
private void SetBusy(bool busy)
{
btnLoadDatabases.Enabled = !busy;
btnSubmit.Enabled = !busy;
txtSourceServer.Enabled = !busy;
cmbAuthentication.Enabled = !busy;
cmbSourceDatabase.Enabled = !busy;
txtDestinationDatabase.Enabled = !busy;
chkReplaceExisting.Enabled = !busy;
chkDeleteBacpac.Enabled = !busy;
if (!busy)
UpdateAuthenticationControls();
else
{
txtUserName.Enabled = false;
txtPassword.Enabled = false;
}
UseWaitCursor = busy;
}
private void Log(string message)
{
txtLog.AppendText(
$"{DateTime.Now:HH:mm:ss} {message}{Environment.NewLine}");
}
private void ClearLog()
{
txtLog.Clear();
lblStatus.Text = "Ready";
progressBar.Value = 0;
}
private void ShowError(string title, Exception exception)
{
lblStatus.Text = title;
Log(exception.ToString());
MessageBox.Show(
exception.Message,
title,
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
private void SafeUi(Action action)
{
if (IsDisposed)
return;
if (InvokeRequired)
BeginInvoke(action);
else
action();
}
}
-----------------------------------------------------------------------
MainForm.Designer.cs
-------------------------------------
namespace QaLocalDbReplica;
partial class MainForm
{
private System.ComponentModel.IContainer? components = null;
private Label lblSourceServer;
private TextBox txtSourceServer;
private Label lblAuthentication;
private ComboBox cmbAuthentication;
private Label lblUserName;
private TextBox txtUserName;
private Label lblPassword;
private TextBox txtPassword;
private Button btnLoadDatabases;
private Label lblSourceDatabase;
private ComboBox cmbSourceDatabase;
private Label lblDestinationDatabase;
private TextBox txtDestinationDatabase;
private CheckBox chkReplaceExisting;
private CheckBox chkDeleteBacpac;
private Button btnSubmit;
private Button btnCancel;
private ProgressBar progressBar;
private Label lblStatus;
private TextBox txtLog;
protected override void Dispose(bool disposing)
{
if (disposing)
components?.Dispose();
base.Dispose(disposing);
}
private void InitializeComponent()
{
lblSourceServer = new Label();
txtSourceServer = new TextBox();
lblAuthentication = new Label();
cmbAuthentication = new ComboBox();
lblUserName = new Label();
txtUserName = new TextBox();
lblPassword = new Label();
txtPassword = new TextBox();
btnLoadDatabases = new Button();
lblSourceDatabase = new Label();
cmbSourceDatabase = new ComboBox();
lblDestinationDatabase = new Label();
txtDestinationDatabase = new TextBox();
chkReplaceExisting = new CheckBox();
chkDeleteBacpac = new CheckBox();
btnSubmit = new Button();
btnCancel = new Button();
progressBar = new ProgressBar();
lblStatus = new Label();
txtLog = new TextBox();
SuspendLayout();
lblSourceServer.AutoSize = true;
lblSourceServer.Location = new Point(24, 25);
lblSourceServer.Text = "Source SQL Server";
txtSourceServer.Location = new Point(190, 21);
txtSourceServer.Size = new Size(420, 27);
txtSourceServer.PlaceholderText = @"Example: QA-SERVER or QA-SERVER\INSTANCE";
lblAuthentication.AutoSize = true;
lblAuthentication.Location = new Point(24, 66);
lblAuthentication.Text = "Authentication";
cmbAuthentication.DropDownStyle = ComboBoxStyle.DropDownList;
cmbAuthentication.Items.AddRange(
["Windows Authentication", "SQL Server Authentication"]);
cmbAuthentication.Location = new Point(190, 62);
cmbAuthentication.Size = new Size(260, 28);
cmbAuthentication.SelectedIndexChanged +=
cmbAuthentication_SelectedIndexChanged;
lblUserName.AutoSize = true;
lblUserName.Location = new Point(24, 107);
lblUserName.Text = "SQL username";
txtUserName.Location = new Point(190, 103);
txtUserName.Size = new Size(260, 27);
lblPassword.AutoSize = true;
lblPassword.Location = new Point(24, 148);
lblPassword.Text = "SQL password";
txtPassword.Location = new Point(190, 144);
txtPassword.Size = new Size(260, 27);
txtPassword.UseSystemPasswordChar = true;
btnLoadDatabases.Location = new Point(470, 102);
btnLoadDatabases.Size = new Size(140, 69);
btnLoadDatabases.Text = "Load Databases";
btnLoadDatabases.UseVisualStyleBackColor = true;
btnLoadDatabases.Click += btnLoadDatabases_Click;
lblSourceDatabase.AutoSize = true;
lblSourceDatabase.Location = new Point(24, 192);
lblSourceDatabase.Text = "Source database";
cmbSourceDatabase.DropDownStyle = ComboBoxStyle.DropDownList;
cmbSourceDatabase.Location = new Point(190, 188);
cmbSourceDatabase.Size = new Size(420, 28);
lblDestinationDatabase.AutoSize = true;
lblDestinationDatabase.Location = new Point(24, 233);
lblDestinationDatabase.Text = "LocalDB database";
txtDestinationDatabase.Location = new Point(190, 229);
txtDestinationDatabase.Size = new Size(420, 27);
chkReplaceExisting.AutoSize = true;
chkReplaceExisting.Checked = true;
chkReplaceExisting.CheckState = CheckState.Checked;
chkReplaceExisting.Location = new Point(190, 273);
chkReplaceExisting.Text = "Replace destination database when it already exists";
chkDeleteBacpac.AutoSize = true;
chkDeleteBacpac.Checked = true;
chkDeleteBacpac.CheckState = CheckState.Checked;
chkDeleteBacpac.Location = new Point(190, 307);
chkDeleteBacpac.Text = "Delete temporary BACPAC after successful/failed operation";
btnSubmit.Location = new Point(190, 350);
btnSubmit.Size = new Size(160, 42);
btnSubmit.Text = "Create Replica";
btnSubmit.UseVisualStyleBackColor = true;
btnSubmit.Click += btnSubmit_Click;
btnCancel.Enabled = false;
btnCancel.Location = new Point(370, 350);
btnCancel.Size = new Size(120, 42);
btnCancel.Text = "Cancel";
btnCancel.UseVisualStyleBackColor = true;
btnCancel.Click += btnCancel_Click;
progressBar.Location = new Point(24, 414);
progressBar.Size = new Size(586, 25);
lblStatus.AutoEllipsis = true;
lblStatus.Location = new Point(24, 450);
lblStatus.Size = new Size(586, 42);
lblStatus.Text = "Ready";
txtLog.Location = new Point(24, 500);
txtLog.Multiline = true;
txtLog.ReadOnly = true;
txtLog.ScrollBars = ScrollBars.Both;
txtLog.Size = new Size(586, 180);
txtLog.WordWrap = false;
AutoScaleDimensions = new SizeF(8F, 20F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(638, 704);
Controls.Add(txtLog);
Controls.Add(lblStatus);
Controls.Add(progressBar);
Controls.Add(btnCancel);
Controls.Add(btnSubmit);
Controls.Add(chkDeleteBacpac);
Controls.Add(chkReplaceExisting);
Controls.Add(txtDestinationDatabase);
Controls.Add(lblDestinationDatabase);
Controls.Add(cmbSourceDatabase);
Controls.Add(lblSourceDatabase);
Controls.Add(btnLoadDatabases);
Controls.Add(txtPassword);
Controls.Add(lblPassword);
Controls.Add(txtUserName);
Controls.Add(lblUserName);
Controls.Add(cmbAuthentication);
Controls.Add(lblAuthentication);
Controls.Add(txtSourceServer);
Controls.Add(lblSourceServer);
FormBorderStyle = FormBorderStyle.FixedDialog;
MaximizeBox = false;
StartPosition = FormStartPosition.CenterScreen;
Text = "QA to LocalDB Replica";
ResumeLayout(false);
PerformLayout();
}
}
--------------------------------------------------------------------------------------------------------
Program.cs
-----------------------
namespace QaLocalDbReplica
{
internal static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
// To customize application configuration such as set high DPI settings or default font,
// see https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize();
Application.Run(new MainForm());
}
}
}
----------------------------------------------------------------------------------------------------------------------
ReplicaRequest.cs
----------------------------
namespace QaLocalDbReplica;
public sealed class ReplicaRequest
{
public required string SourceServer { get; init; }
public required string SourceDatabase { get; init; }
public required string DestinationDatabase { get; init; }
public bool UseWindowsAuthentication { get; init; }
public string? UserName { get; init; }
public string? Password { get; init; }
public bool ReplaceExistingDestination { get; init; } = true;
public bool DeleteTemporaryBacpac { get; init; } = true;
}
-----------------------------------------------------------------------------------------------------------------
SourceDatabaseService.cs
--------------------------------------
using Microsoft.Data.SqlClient;
namespace QaLocalDbReplica;
public static class SourceDatabaseService
{
public static async Task<IReadOnlyList<DatabaseListItem>> GetDatabasesAsync(
string server,
bool windowsAuthentication,
string? userName,
string? password,
CancellationToken cancellationToken)
{
string connectionString =
ConnectionStringFactory.BuildSourceMaster(
server,
windowsAuthentication,
userName,
password);
const string sql = """
SELECT name
FROM sys.databases
ORDER BY name;
""";
var result = new List<DatabaseListItem>();
await using var connection = new SqlConnection(connectionString);
await connection.OpenAsync(cancellationToken);
await using var command = new SqlCommand(sql, connection)
{
CommandTimeout = 60
};
await using var reader =
await command.ExecuteReaderAsync(cancellationToken);
while (await reader.ReadAsync(cancellationToken))
{
result.Add(new DatabaseListItem
{
Name = reader.GetString(0)
});
}
return result;
}
}
0 comments:
Post a Comment