Nested SqlConnection.Open throws an exception inside TransactionScope - c#

I am using TransactionScope in my repository unit tests to rollback any changes made by tests.
Setup and teardown procedures for tests look like this:
[TestFixture]
public class DeviceRepositoryTests {
private static readonly string ConnectionString =
ConfigurationManager.ConnectionStrings["TestDB"].ConnectionString;
private TransactionScope transaction;
private DeviceRepository repository;
[SetUp]
public void SetUp() {
transaction = new TransactionScope(TransactionScopeOption.Required);
repository = new DeviceRepository(ConnectionString);
}
[TearDown]
public void TearDown() {
transaction.Dispose();
}
}
Problematic test consists of code which inserts records to database and CUT that retrieves those records.
[Test]
public async void GetAll_DeviceHasSensors_ReturnsDevicesWithSensors() {
int device1Id = AddDevice();
AddSensor();
var devices = await repository.GetAllAsync();
// Asserts
}
AddDevice and AddSensor methods open sql connection and insert a row into a database:
private int AddDevice() {
var sqlString = "<SQL>";
using (var connection = CreateConnection())
using (var command = new SqlCommand(sqlString, connection)) {
var insertedId = command.ExecuteScalar();
Assert.AreNotEqual(0, insertedId);
return (int) insertedId;
}
}
private void AddSensor() {
const string sqlString = "<SQL>";
using (var connection = CreateConnection())
using (var command = new SqlCommand(sqlString, connection)) {
var rowsAffected = command.ExecuteNonQuery();
Assert.AreEqual(1, rowsAffected);
}
}
private SqlConnection CreateConnection() {
var result = new SqlConnection(ConnectionString);
result.Open();
return result;
}
GetAllAsync method opens a connection, executes query, and for each fetched row opens new connection to fetch child objects.
public class DeviceRepository {
private readonly string connectionString;
public DeviceRepository(string connectionString) {
this.connectionString = connectionString;
}
public async Task<List<Device>> GetAllAsync() {
var result = new List<Device>();
const string sql = "<SQL>";
using (var connection = await CreateConnection())
using (var command = GetCommand(sql, connection, null))
using (var reader = await command.ExecuteReaderAsync()) {
while (await reader.ReadAsync()) {
var device = new Device {
Id = reader.GetInt32(reader.GetOrdinal("id"))
};
device.Sensors = await GetSensors(device.Id);
result.Add(device);
}
}
return result;
}
private async Task<List<Sensor>> GetSensors(int deviceId) {
var result = new List<Sensor>();
const string sql = "<SQL>";
using (var connection = await CreateConnection())
using (var command = GetCommand(sql, connection, null))
using (var reader = await command.ExecuteReaderAsync()) {
while (await reader.ReadAsync()) {
// Fetch row and add object to result
}
}
return result;
}
private async Task<SqlConnection> CreateConnection() {
var connection = new SqlConnection(connectionString);
await connection.OpenAsync();
return connection;
}
}
The problem is that when GetSensors method calls SqlConnection.Open I get following exception:
System.Transactions.TransactionAbortedException : The transaction has aborted.
----> System.Transactions.TransactionPromotionException : Failure while attempting to promote transaction.
----> System.Data.SqlClient.SqlException : There is already an open DataReader associated with this Command which must be closed first.
----> System.ComponentModel.Win32Exception : The wait operation timed out
I could move code that fetches child object out of the first connection scope (this would work), but let's say I don't want to.
Does this exception mean that it is impossible to open simultaneous connections to DB inside single TransactionScope?
Edit
GetCommand just calls SqlCommand contructor and do some logging.
private static SqlCommand GetCommand(string sql, SqlConnection connection, SqlParameter[] parameters) {
LogSql(sql);
var command = new SqlCommand(sql, connection);
if (parameters != null)
command.Parameters.AddRange(parameters);
return command;
}

The issue is that two DataReader objects can't be open at the same time against the database (unless MARS is enabled). This restriction is by design. As I see it you have a few options:
Enable MARS on your connection string; add this MultipleActiveResultSets=True
Don't use the DataReader if it's really not necessary. But the way you've got your code written, it's pretty necessary.
Populate the Sensor property after loading the devices.
Use Dapper, it can do all of this (including populate the Sensor) and likely faster.
Using Dapper you could do something like this (and you wouldn't need GetSensors):
public async Task<List<Device>> GetAllAsync() {
var result = new List<Device>();
const string sql = "<SQL>";
using (var connection = await CreateConnection())
using (var multi = connection.QueryMultiple(sql, parms)) {
result = multi.Read<Device>().ToList();
var sensors = multi.Read<Sensors>().ToList();
result.ForEach(device => device.Sensors =
sensors.Where(s => s.DeviceId = device.Id).ToList());
}
return result;
}
Here your sql would look like this:
SELECT * FROM Devices
SELECT * FROM Sensors
See the Multi Mapping documentation for Dapper.

Related

how to use only single connection to execute multiple queries in Npgsql?

I have created multiple connections in npgsql to execute multiple queries as shown below code.
class TransactionAccess
{
private const string connString = "Host=localhost;Username=postgres;Password=1234;Database=ExpenseManagerDB";
public static void GetTransactions()
{
using (var connection = new NpgsqlConnection(connString))
{
var transactions = connection.Query<TransactionView>(#"SELECT t.transaction_id,t.account_id,a.account_name, a.type,t.note, t.amount, t.date
FROM account AS a
INNER JOIN transaction AS t ON a.account_id = t.account_id");
transactions.Dump();
}
}
public static void GetTransactionInfo(int id)
{
using (var connection = new NpgsqlConnection(connString))
{
var transactionInfo = connection.Query<TransactionView>(#"SELECT a.account_name, a.type, DATE(t.date), t.amount, t.note, t.transaction_id
FROM transaction AS t
INNER JOIN account AS a ON t.account_id = a.account_id
WHERE t.transaction_id = #id", new { id });
transactionInfo.Dump();
}
}
public static void MakeTransaction(Transaction transaction, Account account)
{
using (var connection = new NpgsqlConnection(connString))
{
connection.Execute(#"INSERT INTO transaction(account_id,amount,date,note)
SELECT a.account_id,#amount, #date, #note
FROM account AS a
WHERE a.account_name=#account_name", new { transaction.Amount, transaction.Date, transaction.Note, account.Account_Name });
}
}
}
I wanted to execute all queries with a single connection. How can I do that?
Why cannot you use Batching as mentioned in Npgsql documentation.
await using var batch = new NpgsqlBatch(conn)
{
BatchCommands =
{
new("INSERT INTO table (col1) VALUES ('foo')"),
new("SELECT * FROM table")
}
};
await using var reader = await cmd.ExecuteReaderAsync();
Source : https://www.npgsql.org/doc/basic-usage.html
PS : Thought of commenting first, but cannot do it because of less reputation points :D

How to build connection string from existing connection for new database created dynamically?

I have query to dynamically create database.
private void ExecuteNonQuery(string sql)
{
using (var connection = new SqlConnection(_connectionString))
{
connection.Open();
SqlCommand command = connection.CreateCommand();
command.CommandText = sql;
command.ExecuteNonQuery();
}
}
private void CreateDatabase(string databaseName)
{
try
{
ExecuteNonQuery($"CREATE DATABASE {databaseName}");
}
catch (Exception e)
{
throw new Exception($"Can't create database '{databaseName}'");
}
}
Database will be created using my existing connection , But I need to create connection string for this new database to run migration and for various other purposes.
How is it possible ?
Update
Its actually for purpose where users can fill a form to create a new database where they can give their existing connection string or if they don't have one in hand we build it for them
use this function to get a connection to the new database:
private SqlConnection NewDatebaseConnection(string databaseName)
{
SqlConnection connection = new SqlConnection(_connectionString);
connection.ChangeDatabase(databaseName);
return SqlConnection;
}
To save new connection, you can use ConnectionStringBuilder with existing connection, change the name of database and save it to app.config.
1.Helper method to get connection string:
string GetConnectionString(string name) =>
ConfigurationManager.ConnectionStrings[name].ConnectionString;
2.Helper method which saves new connection string to app.config:
void CreateNewConnectionString(string baseName, string newName, string newDatabaseName)
{
// Get the connection string a new connection will be based on
string baseConnString =
ConfigurationManager.ConnectionStrings[baseName].ConnectionString;
// For simplicity of manipulations with connection strings,
// we use SqlConnectionStringBuilder, passing it an existing connection string
var connBuilder = new SqlConnectionStringBuilder(baseConnString);
// Change existing database name to the new database name
connBuilder.InitialCatalog = newDatabaseName;
// Create new settings, holding our new connection string
var newConnection = new ConnectionStringSettings(newName, connBuilder.ToString());
// Save new connection string
var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
config.ConnectionStrings.ConnectionStrings.Add(newConnection);
config.Save(ConfigurationSaveMode.Modified);
}
3.Usage
private void CreateDatabase(string databaseName)
{
try
{
// Create database using "main" connection string
ExecuteNonQuery($"CREATE DATABASE {databaseName}", "main");
// Create new connection string "my_db" based on "main"
CreateNewConnectionString("main", "my_db", databaseName);
}
catch (Exception e)
{
throw new Exception($"Can't create database '{databaseName}'");
}
}
If you are using EF, you should have a context to work with, something like the following:
public class MyContext : DbContext
{
public MyContext():base("myConnectionStringName")
{
}
}
The constructor has a parameter that specifies connection name or a connection string. So, you can obtain a context for any connections string you want:
using (var db = new MyContext("connection_string_containing_new_db")
{
// do stuff with the context
}
You should consider using a factory that builds such contexts:
class MyContextFactory
{
MyContext CreateContextForDatabase(string dbName)
{
var builder = new SqlConnectionStringBuilder();
builder["Data Source"] = "server\\instance";
builder["integrated Security"] = true;
builder["Initial Catalog"] = dbName;
return new new MyContext(builder.ConnectionString);
}
}
The code uses SqlConnectionStringBuilder to construct (from scratch) the connection string. An alternative is to parse a static connection string using var builder = new SqlConnectionStringBuilder(staticConnectionString) and only change the Initial Catalog.

how to fire a callback once data reader reads all result

I have a class like
class ReadData{
public IdataReader Execute(string sql){
// Ado.net code here
return cmd.ExecuteReader();
}
}
This is the sample implementation and it works fine.
I am calling like this in caller
class caller{
void CallMethod(){
var reader = Execute("Sql query here");
while(reader.Read()){
logic here
}
//Here i need to get the out params after reading the resultset.
//But the impplementation should in the class ReadData.
//because that class has implementation to get the out params for the
//other type means, execute without resultset get only output params
}
}
Some possible ways like calling the first method with callback and in that once the data is read completely then read the out params.
I don't know how to implement that stuff.
Any possible better ways to do ?
Please help me on this..
public void Execute(string sql, Action<IDataRecord> action)
{
using(var connection = new ...)
{
connection.Open();
using(var command = new ...)
{
using(var reader = command.ExecuteReader())
{
while(reader.Read())
{
action(reader);
}
}
}
}
}
This would allow you to do something like this:
var entries = List<object>();
Execute("Sql query here", row => entries.Add(row["Field"]));
Or you could try a more linqy appraoch:
public IEnumerable<IDataRecord> Execute(string sql)
{
using(var connection = new ...)
{
connection.Open();
using(var command = new ...)
{
using(var reader = command.ExecuteReader())
{
while(reader.Read())
{
yield return reader;
}
}
}
}
}
Which would allow something like this:
var list = Execute("Sql query here").Where(row => (int)row["Field"] == 17)).ToList();
However, this has some weird effects with defered execution if you don't materialize it properly.

Firebird FBConnection losing connectionString

The use of FBConnection is giving me some trouble.
In examples of the Firebird Ole-Db I find only examples using a static main method, but I'm not sure how to implement the use of an FbConnection in instance methods.
Right now I'm initializing and using the connection as shown in the code example below.
Every now and then I get the error "connectionstring is not initialized". The connection object is not null, but the connectionstring seems to be null.
What causes this behaviour? Should I reinitialize the FbConnect object every time I access the method (making it a local variable), or is this performance-wise a very bad idea?
public class MyUserStore<TUser> : IUserPasswordStore<TUser, int>, IUserStore<TUser, int>, IDisposable where TUser : ApplicationUser, new()
{
private FbConnection Connection = new FbConnection("User=-----;" +
"Password=-------;" +
"Database=C:\\------\\Testing.GDB;" +
"DataSource=localhost;" +
"Dialect=3;Charset=NONE;");
public Task<TUser> FindByIdAsync(int userId)
{
if (userId == 0)
{
throw new ArgumentNullException("userId");
}
TUser User = null;
if (Connection != null)
{
FbTransaction transaction = null;
FbDataReader Reader = null;
using (Connection)
{
try
{
Connection.Open();
FbCommand Command = new FbCommand(GetByIdQuery, Connection);
Command.Parameters.AddWithValue("id", userId);
Reader = Command.ExecuteReader();
catch (Exception e)
{
if (transaction != null)
{
transaction.Rollback();
}
System.Diagnostics.Debug.WriteLine(e.StackTrace);
return Task.FromResult<TUser>(null);
}
finally
{
if (Reader != null)
{
Reader.Close();
}
Connection.Close();
}
}
}
}
Mark Rotteveel is right in his comment.
Apparently, the using clause means the resource is being disposed of right at the end of the block. This means I would need to create a new connection every time I use the "using" block.

Async SQL query execution - Task.WaitAll(tasks) never completes

I have an app to move data from a MS SQL server into a MySQL server using async query execution; the data moves fine but Task.WaitAll(tasks) call in the RunAllTasks() method never completes.
The async tasks all follow the pattern of PumpLocsAsync() where the call to MS SQL is invoked asynchronously via BeginExecuteReader; when the reader returns with results, then MySQL inserts normally.
..
async Task PumpLocsAsync()
{
using (var conn = new SqlConnection(SqlConnStr))
using (var cn = new MySqlConnection(MysqlConnStr))
using (var cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = "SELECT HERE";
conn.Open();
var handle = cmd.BeginExecuteReader();
await Task.Factory.FromAsync(handle, (ar) =>
{
var rdr = cmd.EndExecuteReader(ar);
var qry = #"INSERT HERE";
cn.Open();
using (var cmdm = new MySqlCommand(qry, cn))
{
cmdm.CommandTimeout = MysqlCmdtimeout;
<PARAM SETUP HERE>
<COLUMN MAPPING HERE>
while (RetryUtility.RetryMethod<bool>(() => rdr.Read(), 3, 1000))
{
<LOADING PARAMS WITH BITS HERE>
RetryUtility.RetryAction(() => cmdm.ExecuteNonQuery(), 3, 1000);
}
}
Console.WriteLine("Finished Locs!");
cn.Close();
});
conn.Close();
}
}
...
void RunAllTasks()
{
Task[] tasks = { PumpLocsAsync(), PumpPicsAsync() };
try
{
Task.WaitAll(tasks);
Console.WriteLine("Finished with all tasks...");
foreach (var task in tasks)
{
Console.WriteLine("Id: {0}, Status: {1}", task.Id, task.Status);
}
}
....
You misunderstood how to call Task.Factory.FromAsync(), you pass to it the result of the BeginXxx method and a delegate to the EndXxx method:
var rdr = await Task.Factory.FromAsync(
cmd.BeginExecuteReader,
(Func<IAsyncResult, SqlDataReader>)cmd.EndExecuteReader,
null);
If you want to do something after the operation completes, simply put it below this line, await will make sure it executes at the right time.

Categories