Timeouts + Prepared Transactions with ADO.NET / Npgsql - ado.net

Looking for some clarification on the below scenarios. CanAddConcurrently and DoesNotTimeout are failing.
CanAddConcurrently - (Npgsql.PostgresException: 55000: prepared transactions are disabled). I understand it's because I have this disabled in my postgres config, but WHY is this escalating to a prepared transaction? Is it because it's actually getting a different NpgsqlConnection? If so, does this really call for a distributed transaction? I can run the same sample with knex, node-postgres with same pool limits, prepared transactions disabled in postgres without issue under node.js
DoesNotTimeout - (The connection pool has been exhausted) I don't understand why the pooled connections are not being reused here. Are they not being disposed because they are associated with the top level TransactionScope in the test? Even under this scenario why can't the connection be reused if it's associated with the same transaction. I can run the same test case with knex, node-postgres with the same pool limit without issue under node.js.
using Npgsql;
using System.Threading.Tasks;
using System.Transactions;
namespace TestCases
{
public class Service
{
private readonly string connectionString;
public Service(string connectionString)
{
this.connectionString = connectionString;
}
// I am aware this is only executing 1 query so does not have a need for an embedded transaction, this is just to keep example simple
// removing the TransactionScope does not fix the issue, but for closer sample to original code it is here
public async Task Add(string val)
{
using (var nestedScope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
using (var conn = new NpgsqlConnection(this.connectionString))
{
await conn.OpenAsync();
var cmd = new NpgsqlCommand("INSERT INTO data(value) values(#p);", conn);
cmd.Parameters.AddWithValue("p", val);
await cmd.ExecuteNonQueryAsync();
nestedScope.Complete();
}
}
}
}
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Npgsql;
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Transactions;
namespace TestCases
{
[TestClass]
public class ServiceTests
{
readonly string connectionString = "Server=127.0.0.1;Port=5432;Database=test_db;User Id=postgres;Password=postgres;MaxPoolSize=10;Pooling=true;";
private Service service;
[TestInitialize]
public async Task Initialize()
{
service = new Service(this.connectionString);
using (var conn = new NpgsqlConnection(this.connectionString))
{
await conn.OpenAsync();
var query = "CREATE TABLE IF NOT EXISTS data(value varchar(255));";
var cmd = new NpgsqlCommand(query, conn);
await cmd.ExecuteNonQueryAsync();
}
}
[TestCleanup]
public async Task Cleanup()
{
using (var conn = new NpgsqlConnection(this.connectionString))
{
await conn.OpenAsync();
var query = "DROP TABLE IF EXISTS data;";
var cmd = new NpgsqlCommand(query, conn);
await cmd.ExecuteNonQueryAsync();
}
}
/// <summary>
/// Failing with prepared PG 55000
/// </summary>
/// <returns></returns>
[TestMethod]
public async Task CanAddConcurrently()
{
using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
await Task.WhenAll(
Enumerable.Range(1, 10).Select(async i =>
{
var val = string.Format("CanAddConcurrently_Q{0};", i);
await service.Add(val);
})
);
scope.Complete();
}
using (var conn = new NpgsqlConnection(this.connectionString))
{
await conn.OpenAsync();
var query = "select count(*) from data WHERE value like 'CanAddConcurrently_Q%';";
var cmd = new NpgsqlCommand(query, conn);
long count = (long)await cmd.ExecuteScalarAsync();
Assert.AreEqual((long)100, count);
}
}
/// <summary>
/// Timing out
/// </summary>
/// <returns></returns>
[TestMethod]
public async Task DoesNotTimeout()
{
using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
await Task.WhenAll(
Enumerable.Range(1, 100).Select(async i =>
{
var val = string.Format("DoesNotTimeout_Q{0};", i);
await service.Add(val);
})
);
scope.Complete();
}
using (var conn = new NpgsqlConnection(this.connectionString))
{
await conn.OpenAsync();
var query = "select count(*) from data WHERE value like 'DoesNotTimeout_Q%';";
var cmd = new NpgsqlCommand(query, conn);
long count = (long)await cmd.ExecuteScalarAsync();
Assert.AreEqual((long)100, count);
}
}
/// <summary>
/// Passes OK
/// </summary>
/// <returns></returns>
[TestMethod]
public async Task CanAddSequentially()
{
using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
for (long i = 0; i < 100; i++)
{
var val = string.Format("CanAddSequentially_Q{0};", i);
await service.Add(val);
}
scope.Complete();
}
using (var conn = new NpgsqlConnection(this.connectionString))
{
await conn.OpenAsync();
var query = "select count(*) from data WHERE value like 'CanAddSequentially_Q%';";
var cmd = new NpgsqlCommand(query, conn);
long count = (long)await cmd.ExecuteScalarAsync();
Assert.AreEqual((long)100, count);
}
}
/// <summary>
/// Passes OK
/// </summary>
/// <returns></returns>
[TestMethod]
public async Task RollsBackIfError()
{
try
{
using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
for (long i = 0; i < 100; i++)
{
var val = string.Format("RollsBackIfError_Q{0};", i);
if (i == 99)
{
val = val.PadRight(300, 'e'); // trigger error on last item
}
await service.Add(val);
}
scope.Complete();
}
}
catch (Exception ex)
{
Assert.IsInstanceOfType(ex, typeof(NpgsqlException));
}
using (var conn = new NpgsqlConnection(this.connectionString))
{
await conn.OpenAsync();
var query = "select count(*) from data WHERE value like 'RollsBackIfError_Q%';";
var cmd = new NpgsqlCommand(query, conn);
long count = (long)await cmd.ExecuteScalarAsync();
Assert.AreEqual((long)0, count);
}
}
}
}

You need to understand exactly how Task.WhenAll() (and possibly async) operates.
Your code above invokes Service.Add() 10 times, concurrently, on the same transaction scope, and then waits for those 10 invocations to complete. Each invocation opens a new pooled connection and attempts to enlist to the single transaction scope that exists in your program; the moment more than one connection is enlisted to a transaction scope, that is a distributed transaction, which is why the escalation occurs.
The same likely explains the pool exhaustion in your second example - it's not about pool connections not being reused, it's about you trying to use too many at the same time.
You should run your code serially with a standard foreach, executing the next operation only after the previous one completed. It is possible to run multiple operations at the same time - for better performance - but you definitely can't share the same transaction scope between them.

Related

ADO.NET add SQLParameters

I am trying to execute a stored procedure with parameters using ADO.NET in .NET Core.
When I try and pass in a SqlParameter object I get the below error.
I have a using statement at the top for System.Data.SqlClient.
Is there another way or new ways of passing Sql parameters to a stored procedure?
public async Task<List<SynchStockDto>> GetStockForSync(int locationBinId, DateTime lastSyncDate)
{
await EnsureConnectionOpenAsync();
var command = GetConnection().CreateCommand();
command.CommandText = "sp_GetStockForSync #LocationBinId, #LastSyncDate";
command.CommandType = CommandType.StoredProcedure;
command.Transaction = GetActiveTransaction();
command.Parameters.Add(new SqlParameter("LocationBinId",locationBinId)); //not finding SQLParameter method
using (var dataReader = await command.ExecuteReaderAsync())
{
var result = new List<SynchStockDto>();
while (dataReader.Read())
{
var stockItem = new SynchStockDto
{
};
result.Add(stockItem);
}
return result;
}
}

How to handle job recovering in Quartz.Net

I use Quartz 3.0.7 in my application that use .NET Core 2.2 platform.
I use ms sql server for Quartz action tracking. Quartz tracks and stores its actions in database and it's fine.
Configuration of my StdSchedulerFactory:
["quartz.scheduler.instanceName"] = "StdScheduler",
["quartz.scheduler.instanceId"] = $"{Environment.MachineName}-{Guid.NewGuid()}",
["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz",
["quartz.jobStore.useProperties"] = "true",
["quartz.jobStore.dataSource"] = "default",
["quartz.jobStore.tablePrefix"] = "QRTZ_",
// if running MS SQL Server we need this
["quartz.jobStore.lockHandler.type"] = "Quartz.Impl.AdoJobStore.UpdateLockRowSemaphore, Quartz",
["quartz.dataSource.default.connectionString"] = #"Server=DESKTOP-D64SJFJ\MSSQLSERVER14;Database=quartz;Trusted_Connection=True;",
["quartz.dataSource.default.provider"] = "SqlServer",
[$"{StdSchedulerFactory.PropertyObjectSerializer}.type"] = "json",
[StdSchedulerFactory.PropertySchedulerInterruptJobsOnShutdownWithWait] = "true",
I want to recover each interrupted Job. How should I organize logic of my IHostedService for supporting Job Recovering?
When I Shutdown my application during my job is running then When I start my application again interrupted job doesn't run.
My IHostedService code:
public class QuartzHostedService : IHostedService
{
private readonly ISchedulerFactory _schedulerFactory;
private readonly IJobFactory _jobFactory;
private readonly IEnumerable<JobSchedule> _jobSchedules;
public QuartzHostedService(
ISchedulerFactory schedulerFactory,
IJobFactory jobFactory,
IEnumerable<JobSchedule> jobSchedules)
{
_schedulerFactory = schedulerFactory;
_jobSchedules = jobSchedules;
_jobFactory = jobFactory;
}
public IScheduler Scheduler { get; set; }
public async Task StartAsync(CancellationToken cancellationToken)
{
Scheduler = await _schedulerFactory.GetScheduler(cancellationToken);
Scheduler.JobFactory = _jobFactory;
await Scheduler.Start(cancellationToken);
foreach (var jobSchedule in _jobSchedules)
{
var job = CreateJob(jobSchedule);
var trigger = CreateTrigger(jobSchedule);
if (!await Scheduler.CheckExists(job.Key, cancellationToken))
{
// if the job doesn't already exist, we can create it, along with its trigger. this prevents us
// from creating multiple instances of the same job when running in a clustered environment
await Scheduler.ScheduleJob(job, trigger);
}
else
{
// if the job has exactly one trigger, we can just reschedule it, which allows us to update the schedule for
// that trigger.
var triggers = await Scheduler.GetTriggersOfJob(job.Key);
if (triggers.Count == 1)
{
await Scheduler.RescheduleJob(triggers.First().Key, trigger);
}
else
{
// if for some reason the job has multiple triggers, it's easiest to just delete and re-create the job,
// since we want to enforce a one-to-one relationship between jobs and triggers
await Scheduler.DeleteJob(job.Key);
await Scheduler.ScheduleJob(job, trigger);
}
}
}
}
public async Task StopAsync(CancellationToken cancellationToken)
{
await Scheduler?.Shutdown(cancellationToken);
}
private static IJobDetail CreateJob(JobSchedule schedule)
{
var jobType = schedule.JobType;
return JobBuilder
.Create(jobType)
.WithIdentity(jobType.FullName)
.WithDescription(jobType.Name)
.RequestRecovery(true)
.StoreDurably()
.Build();
}
private static ITrigger CreateTrigger(JobSchedule schedule)
{
return TriggerBuilder
.Create()
.WithIdentity($"{schedule.JobType.FullName}.trigger")
.WithCronSchedule(schedule.CronExpression)
.WithDescription(schedule.CronExpression)
.Build();
}
}
My startup.cs:
private void ConfigureQuartz(IServiceCollection services)
{
services.AddHostedService<QuartzHostedService>();
services.AddSingleton<IJobFactory, SingletonJobFactory>();
services.AddSingleton<ISchedulerFactory>(new StdSchedulerFactory(StdSchedulerFactoryConfiguration()));
services.AddSingleton<AuthKeyExpiresJob>();
//services.AddSingleton<AuthKeyWillExpireJob>();
services.AddSingleton(new JobSchedule(
typeof(AuthKeyExpiresJob),
"0 14 11 ? * *"));
}

Npgsql Performance

I am trying to implement Npgsql in our DAL and running into issues under heavy load. the following sample application is a decent representation of just a simple query that under heavy load, throws a 'A command is already in progress' exception. I am assuming this is due to the lack of MARS support so I also tried creating a connection each time with a using statement around each command only to have the performance become unusable. I checked that the username is indexed so that shouldn't be an issue.
Not sure what I am doing wrong here but I need some advice on how to get this performing well.
OS: Docker Container: microsoft/dotnet:2.1.301-sdk
using Npgsql;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Threading.Tasks;
namespace npgsqlTest
{
class Program
{
static async Task Main(string[] args)
{
DAL dal = new DAL();
dal.Prepare();
var tasks = dal.Users.Select(async user =>
{
Console.WriteLine(await dal.RunTest(user));
});
await Task.WhenAll(tasks);
}
}
public class DAL
{
private static string _ConnectionString;
private NpgsqlConnection _Connection;
public List<string> Users { get; set; } = new List<string>();
public DAL()
{
_ConnectionString = $"Host=192.168.1.1;Username=admin;Port=5432;Password=password;Database=BigDB;";
_Connection = new NpgsqlConnection(_ConnectionString);
_Connection.Open();
}
public void Prepare()
{
string query = "SELECT username FROM usertable;";
using (var cmd = new NpgsqlCommand(query, _Connection))
{
var reader = cmd.ExecuteReader();
using (reader)
{
while (reader.Read())
{
Users.Add(reader[0].ToString());
}
}
}
}
public async Task<string> RunTest(string user)
{
var parameters = new Dictionary<string, Object> { { "username", user } };
var query = $"SELECT name FROM usertable WHERE username = (#username);";
var reader = await QueryAsync(query, parameters);
using (reader)
{
if (reader.HasRows)
{
while (await reader.ReadAsync())
{
var name = reader["name"];
if (!(hash is DBNull))
return (string)name;
}
}
}
return String.Empty;
}
public async Task<DbDataReader> QueryAsync(string query, Dictionary<string, Object> parameters)
{
using (var cmd = new NpgsqlCommand(query, _Connection))
{
foreach (var parameter in parameters)
{
cmd.Parameters.AddWithValue(parameter.Key, parameter.Value == null ? DBNull.Value : parameter.Value);
}
cmd.Prepare();
return await cmd.ExecuteReaderAsync();
}
}
}
}

Hangfire , EF context used in the Task is not updated, the query inside the task always gives the old values

The context used inside the Hangfire task , always gives the old database values, it seems like the context is not updating inside the task. How to get the updated info context data inside hangfire task.
calling the task
BLL.Extraction NewExtractionCls = new BLL.Extraction();
var jobId = BackgroundJob.Enqueue(() => NewExtractionCls.SearchEngineExtraction("SearchURL", "1","1", null));
This is implementation
[Authorize]
public void SearchEngineExtraction(string SearchURL, int PageLimit, int SearchEngineID, PerformContext context)
{
WebClient wc = new WebClient();
#region Main Table - SearchEngineTbl
var NewExtraction = db.SearchEngineTbls.Where(x => x.SearchEngineID == SearchEngineID).FirstOrDefault();
var JobID = context.BackgroundJob.Id;
NewExtraction.JobID = Convert.ToInt32(JobID);
NewExtraction.SeachEngineURL = SearchURL;
NewExtraction.Status = "Processing";
db.SaveChanges();
var LinkCollectionRefined = ExtractLinkFromThisPage(i, SearchURL, wc).Distinct().ToList();//.Skip(10);
foreach (var Link in LinkCollectionRefined)
{
using (Entities dbRefreshed = new Entities())
{
// I get the same old value here, even if I update the table manually, when I rerun, everything is fine.
var CurrentStatusOfExtraction = db.SearchEngineTbls.Where(x => x.SearchEngineID == NewExtraction.SearchEngineID).FirstOrDefault();
if (CurrentStatusOfExtraction.IsActive == false)
{
return;
}
}
}
#endregion
}

Review of Connection handling and Data access layer using C#, sql server compact 3.5

I am developing a stand alone application, using sql server compact 3.5 sp2 which runs in process. No Database writes involved. Its purely a reporting application. Read many articles about reusing open db connections in case of sql compact(connection pooling) due to its different behavior from sql server.
Quoting the comments from a quiz opened by Erik Ejlskov Jensen Link, where its discussed an open early close late strategy for sql server compact databases. Based on this, with my limited experience I have implemented a not so complex Connection handling+Data access layer. Basically I am unsure if i am writing it in a recommended way. Please could any one point me in the right direction with rooms for improvement in this connection handling approach i have written?
The DbConnection class
public class FkDbConnection
{
private static SqlCeConnection conn;
private static DataTable table;
private static SqlCeCommand cmd;
~FkDbConnection() { conn = null; }
//This will be called when the main winform loads and connection will be open as long as the main form is open
public static string ConnectToDatabase()
{
try {
conn = new SqlCeConnection(ConfigurationManager.ConnectionStrings["Connstr"].ConnectionString);
if (conn.State == ConnectionState.Closed || conn.State == ConnectionState.Broken)
{
conn.Open();
}
return "Connected";
}
catch(SqlCeException e) { return e.Message; }
}
public static void Disconnect()
{
if (conn.State == ConnectionState.Open || conn.State == ConnectionState.Connecting || conn.State == ConnectionState.Fetching)
{
conn.Close();
conn.Dispose();
//conn = null; //does conn have to be set to null?
}
//else the connection might be already closed due to failure in opening it
else if (conn.State == ConnectionState.Closed) {
conn.Dispose();
//conn = null; //does conn have to be set to null?
}
}
/// <summary>
/// Generic Select DataAccess
/// </summary>
/// <param name="sql"> the sql query which needs to be executed by command object </param>
public static DataTable ExecuteSelectCommand(SqlCeCommand comm)
{
if (conn != null && conn.State == ConnectionState.Open)
{
#region block using datareader
using (table = new DataTable())
{
//using statement needed for reader? Its closed below
using (SqlCeDataReader reader = comm.ExecuteReader())
{
table.Load(reader);
reader.Close(); //is it needed?
}
}
#endregion
# region block using dataadpater
//I read DataReader is faster?
//using (SqlCeDataAdapter sda = new SqlCeDataAdapter(cmd))
//{
// using (table = new DataTable())
// {
// sda.Fill(table);
// }
//}
#endregion
//}
}
return table;
}
/// <summary>
/// Get Data
/// </summary>
/// <param name="selectedMPs"> string csv, generated from a list of selected posts(checkboxes) from the UI, which forms the field names used in SELECT </param>
public static DataTable GetDataPostsCars(string selectedMPs)
{
DataTable dt;
//i know this it not secure sql, but will be a separate question to pass column names to select as parameters
string sql = string.Format(
"SELECT " + selectedMPs + " "+
"FROM GdRateFixedPosts");
using (cmd = new SqlCeCommand(sql,conn))
{
cmd.CommandType = CommandType.Text;
//cmd.Parameters.Add("#fromDateTime",DbType.DateTime);
//cmd.Parameters.Add("#toDateTime",DbType.DateTime);
dt = ExecuteSelectCommand(cmd);
}
return dt;
}
}
The Main UI (Form) in which connection opened, for connection to be open through out. 2 other reporting forms are opened from here. Closing main form closes all, at which point connection is closed and disposed.
private void FrmMain_Load(object sender, EventArgs e)
{
string str = FkDbConnection.ConnectToDatabase();
statStDbConnection.Items[0].Text = str;
}
private void FrmMain_FormClosing(object sender, FormClosingEventArgs e)
{
FkDbConnection.Disconnect();
}
Comments, improvements on this connection class much appreciated. See my questions also inline code
Thank you.
Updated classes as per Erik's suggestion. with a correction on ExecuteSelectCommand() and an additional class which will instantiate command objs in "using" and pass data to the UI. I intent to add separate GetDataForFormX() methods since the dynamic sql for each form may differ. Hope this is ok?
Correction to Erik's code:
public static DataTable ExecuteSelectCommand(SqlCeCommand comm)
{
var table = new DataTable();
if (conn != null && conn.State == ConnectionState.Open)
{
comm.Connection = conn;
using (SqlCeDataReader reader = comm.ExecuteReader())
{
table.Load(reader);
}
}
return table;
}
New FkDataAccess class for passing Data to UI
public class FkDataAccess
{
public static DataTable GetDataPostsCars(string selectedMPs)
{
var table = new DataTable();
string sql = string.Format(
"SELECT " + selectedMPs + " " +
"FROM GdRateFixedPosts");
if (FkDbConnection.conn != null && FkDbConnection.conn.State == ConnectionState.Open)
{
using (SqlCeCommand cmd = new SqlCeCommand(sql, FkDbConnection.conn))
{
cmd.CommandType = CommandType.Text;
//cmd.Parameters.Add("#fromDateTime",DbType.DateTime);
table = FkDbConnection.ExecuteSelectCommand(cmd);
}
}
return table;
}
//public static DataTable GetDataXY(string selectedvals)
// and so on
}
Too much code in your data access class, makes it unreadable and hard to maintain
The SqlCeonnection object will be disposed when you close it (and when the app closes)
You cannot dispose the DataTable if you want to use it elsewhere, and it is an completely managed object anyway.
It is a good pattern to limit your classes to a single responsibility
public class FkDbConnection
{
private static SqlCeConnection conn;
~FkDbConnection() { conn = null; }
//This will be called when the main winform loads and connection will be open as long as the main form is open
public static void ConnectToDatabase()
{
// Handle failure to open in the caller
conn = new SqlCeConnection(ConfigurationManager.ConnectionStrings["Connstr"].ConnectionString);
conn.Open();
}
public static void Disconnect()
{
if (conn != null)
{
conn.Close();
}
}
public static DataTable ExecuteSelectCommand(SqlCeCommand comm)
{
var table = new DataTable();
if (conn != null && conn.State == ConnectionState.Open)
{
comm.Connection = conn;
using (SqlCeDataReader reader = comm.ExecuteReader())
{
table.Load(reader);
}
}
return table;
}
private void FrmMain_Load(object sender, EventArgs e)
{
try
{
FkDbConnection.ConnectToDatabase();
statStDbConnection.Items[0].Text = "Connected";
}
catch (Exception ex)
{
//Inform use that we canot proceed, what she can do to remedy, and exit
}
}
private void FrmMain_FormClosing(object sender, FormClosingEventArgs e)
{
FkDbConnection.Disconnect();
}