SQL Server connections in ASP.NET application [closed] - c#

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 7 years ago.
Improve this question
I have created a web application with asp.net, roughly 100 users are using it.
However from time to time people are getting the error message that the connection is still open. Indicating that it was not closed properly.
It appears on random places, not one specific place and no other errors before it.
I know that when I have a bug in the application and it crashes without me gracefully dealing with the error the connection remains open as well and basically everyone will crash because of it. This made me think that everyone uses the same connection object, is it possible that 2 users might have the perfect timing and invoke a function using a DB connection at the same time causing the error? Is there a way to make sure everyone uses their own connection objects, like put it in their session or something?
I hope you can understand what I mean, I don't think posting any of my code will help since it happens on random places within my project.
They are connection to a SQL Server using System.Data.SqlClient.
Find below the function which generates the error. This function is called by the Page_Load, nothing is before it.
public static SqlConnection conn = new SqlConnection("Data Source=Server00\\SQLEXPRESS;Initial Catalog=r2;Integrated Security=true;Connect Timeout=0");
private void populateGameDrop()
{
try
{
conn.Open();
drop_game.Items.Clear();
SqlCommand cmd = conn.CreateCommand();
Access ac = (Access)Session["Access"];
cmd.CommandText = "Select * from dbo.Games where " + ac.GameQuery;
SqlDataReader r = cmd.ExecuteReader();
while (r.Read())
{
drop_game.Items.Add(new ListItem(r["name"].ToString(), r["Abbr"].ToString()));
}
conn.Close();
}
catch (Exception exc)
{
conn.Close();
Log.Error(exc.ToString());
Session["Error"] = exc.ToString();
Response.Redirect("~/YouBrokeIt.aspx");
}
populateServers();
SetSplitScreen();
}

Don't try to share SqlConnection objects.
Try this instead:
private static string connString = "Data Source=Server00\\SQLEXPRESS;Initial Catalog=r2;Integrated Security=true;Connect Timeout=0";
private void populateGameDrop()
{
try
{
using (var conn = new SqlConnection(connString))
{
conn.Open();
drop_game.Items.Clear();
using (var cmd = conn.CreateCommand())
{
Access ac = (Access)Session["Access"];
//TODO
//TODO - Introduce parameters to avoid SQL Injection risk
//TODO
cmd.CommandText = "Select name,Abbr from dbo.Games where " + ac.GameQuery;
using(SqlDataReader r = cmd.ExecuteReader())
{
while (r.Read())
{
drop_game.Items.Add(new ListItem(r["name"].ToString(),
r["Abbr"].ToString()));
}
}
}
}
}
catch (Exception exc)
{
Log.Error(exc.ToString());
Session["Error"] = exc.ToString();
Response.Redirect("~/YouBrokeIt.aspx");
}
populateServers();
SetSplitScreen();
}
Behind the scenes, .NET uses a concept called connection pooling so that the actual number of real connections to SQL Server are minimized. But SqlConnection objects aren't designed to be shared by multiple threads.

Don't place your database code directly in your ASPX pages. Creating an extra layer (i.e. DAL) allows you to test the DB methods without using the page.
Try something like this.
//Don't embed database logic directly in the aspx files
public class GamesProvider
{
//Put the ConnectionString in you configuration file
private string ConnectionString
{
get { return ConfigurationManager.ConnectionStrings["GameDB"].ConnectionString; }
}
public IEnumerable<Game> LoadGames(string x, string y)
{
var games = new List<Game>();
const string queryString = "select name, Abbr from dbo.Games where x = #x and y = #y";
using (var connection = new SqlConnection(ConnectionString))
using (var command = new SqlCommand(queryString, connection))
{
command.Parameters.AddWithValue("#x", x);
command.Parameters.AddWithValue("#y", y);
using (var dateReader = command.ExecuteReader())
{
while (dateReader.Read())
{
var game = new Game
{
Name = dateReader["name"].ToString(),
Abbr = dateReader["Abbr"].ToString(),
};
games.Add(game);
}
}
}
return games;
}
}
//Use types
public class Game
{
public string Name { get; set; }
public string Abbr { get; set; }
}

Your SQL connections shouldn't be static, use the following to create them
var connectionString = "YOUR CONNECTION STRING";
var queryString = "SQL QUERY";
using (SqlConnection connection = new SqlConnection(connectionString))
using (SqlCommand command = new SqlCommand(queryString, connection))
using (SqlDataReader dateReader = command.ExecuteReader()) {
}
VERY IMPORTANT
You should use parameterised SQL your code is open to SQL injection attacks.
please see Parameterize SQL query

Related

extracting a fill method from a form

I have a combobox on a windows form that I fill with a list of names. At the moment I have the following code inside the Form class and it works fine
// This section opens a connection to the database, selects all the portfolio names that have an "in Use" value of 1, and then
// fills Combo Box 2 with the values.
SqlConnection myConnection = new SqlConnection(#"Data Source = (LocalDB)\MSSQLLocalDB; AttachDbFilename = ""C:\Users\Nick\Documents\Investments 4.mdf""; Integrated Security = True; Connect Timeout = 30");
myConnection.Open();
SqlCommand myCommand2 = new SqlCommand();
myCommand2.Connection = myConnection;
myCommand2.CommandText = "SELECT Portfolio_Name FROM Dbo.Name WHERE In_use = 1";
SqlDataReader myReader2 = myCommand2.ExecuteReader();
while (myReader2.Read())
{
comboBox2.Items.Add(myReader2[0]);
}
myConnection.Close();
I would like to be able to extract this into a separate method, and put it into a separate class for general utility methods. However, I'm stuck on a really simple issue. When I put the code into a class, I need to be able to tell it which combox box I want to fill, and I can't figure out how to pass in that information. Sorry if the answer is obvious, but any help would be gratefully received.
Thanks!
Well, if you want to extract, then extract:
// Let's extract a class: it should provide us standard cursors,
// e.g. Protfolio Names
public static class MyData {
// Let's enumerate items returned
public static IEnumerable<string> PortfolioNames() {
// Wrap IDisposable into using
//TODO: move Connection String into a separated method/property
using (SqlConnection con = new SqlConnection(/*connection string here*/)) {
con.Open();
// Make sql readable
//DONE: when presenting something to user, sort it (order by) esp. strings
string sql =
#" select Portfolio_Name
from Dbo.Name
where In_use = 1
order by Portfolio_Name";
// Wrap IDisposable into using
using (SqlCommand q = new SqlCommand(sql, con)) {
// Wrap IDisposable into using
using (var reader = q.ExecuteReader()) {
while (reader.Read())
yield return Convert.ToString(reader[0]);
}
}
}
}
}
And then use
// Adding items in one after one manner is often a bad idea:
// it makes UI repaint each time you add an item and cause blinking.
// Let's fill the ComboBox in one go via AddRange
comboBox2.Items.AddRange(MyData.PortfolioNames().ToArray());
You can use a helper class names Portfolio for data access. The method GetNames does not require a ComboBox instance. This increases the chance that you can reuse the method in another context.
public static class Portfolio
{
public static IList<string> GetNames()
{
SqlConnection myConnection = new SqlConnection(#"Data Source = (LocalDB)\MSSQLLocalDB; AttachDbFilename = ""C:\Users\Nick\Documents\Investments 4.mdf""; Integrated Security = True; Connect Timeout = 30");
myConnection.Open();
SqlCommand myCommand2 = new SqlCommand();
myCommand2.Connection = myConnection;
myCommand2.CommandText = "SELECT Portfolio_Name FROM Dbo.Name WHERE In_use = 1";
SqlDataReader myReader2 = myCommand2.ExecuteReader();
var portfolioNames = new List<string>();
while (myReader2.Read())
{
portfolioNames.Add(myReader2[0]);
}
myConnection.Close();
return portfolioNames;
}
}
Then in your Form you can do something like this:
var names = Portfolio.GetNames();
foreach (var name in names)
{
combobox2.Items.Add(name);
}
It is so simple:
public class MyUtility
{
public static void FillComboBox(System.Windows.Forms.ComboBox comboBox)
{
//comboBox.Items.Clear(); //enable this line if required
using (SqlConnection myConnection = new SqlConnection(#"Data Source = (LocalDB)\MSSQLLocalDB; AttachDbFilename = ""C:\Users\Nick\Documents\Investments 4.mdf""; Integrated Security = True; Connect Timeout = 30"))
{
myConnection.Open();
using (SqlCommand myCommand2 = new SqlCommand())
{
myCommand2.Connection = myConnection;
myCommand2.CommandText = "SELECT Portfolio_Name FROM Dbo.Name WHERE In_use = 1";
using (SqlDataReader myReader2 = myCommand2.ExecuteReader())
{
while (myReader2.Read())
{
comboBox.Items.Add(myReader2[0]);
}
}
}
//myConnection.Close(); //not required inside using block
}
}
}
you may use other methods to get connection string (e.g. from config file).
The usage is so simple, no extra code required:
MyUtility.FillComboBox(comboBox2);

How to copy MySqlDataReader into an array and then loop throught the array?

I am new to C# so yes this should be a faily easy question but I can't seem to find the answer to it.
I have a method that query a database.
What I am trying to do here is handle the loop though the data outside the method.
public MySqlDataReader getDataSet(string query)
{
MySqlDataReader dataset = null;
MySqlConnection conn = new MySqlConnection(conn_string);
if (startConnection(conn) == true)
{
MySqlCommand cmd = new MySqlCommand(query, conn);
dataset = cmd.ExecuteReader();
closeConnection(conn);
}
return dataset;
}
what I could do is write a while loop just before the closeConnection(conn); line and handle the data. But, I don't want to do it inside this method and I want to do it somewhere else in my code.
In one of my forms I want to read the database on the load so here is what I tried to do
public newDepartment()
{
InitializeComponent();
inputDepartmentName.Text = "Hi";
dbConnetion db = new dbConnetion();
MySqlDataReader ds = db.getDataSet("SELECT name FROM test;");
while (ds.Read())
{
//Do Something
}
}
The problem that I am having is that I get an error Invalid attempt to Read when reader is closed
Which I belive I get this issue because I close the connection and then I am trying to read it. so What I need to do is read the data from the query and put it in an array and then loop through the array and deal with the data in a different form.
How can I workaround this issue? if my idea is good then how can I copy the data into an array and how do I loop though the array?
Here is the full class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MySql.Data.MySqlClient;
using System.Windows.Forms;
namespace POS
{
public class dbConnetion
{
//private OdbcConnection conn;
private readonly string mServer;
private readonly string mDatabase;
private readonly string mUid;
private readonly string mPassword;
private readonly string mPort;
private readonly string conn_string;
public dbConnetion()
{
mServer = "localhost";
mDatabase = "pos";
mUid = "root";
mPassword = "";
mPort = "3306";
conn_string = String.Format("server={0};user={1};database={2};port={3};password={4};", mServer, mUid, mDatabase, mPort, mPassword);
}
//Start connection to database
private bool startConnection(MySqlConnection mConnection)
{
try
{
mConnection.Open();
return true;
}
catch (MySqlException ex)
{
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK);
return false;
}
}
//Close connection
private bool closeConnection(MySqlConnection mConnection)
{
try
{
mConnection.Close();
return true;
}
catch (MySqlException ex)
{
MessageBox.Show(ex.Message);
return false;
}
}
public MySqlDataReader getDataSet(string query)
{
MySqlDataReader dataset = null;
MySqlConnection conn = new MySqlConnection(conn_string);
if (startConnection(conn) == true)
{
MySqlCommand cmd = new MySqlCommand(query, conn);
dataset = cmd.ExecuteReader();
closeConnection(conn);
}
return dataset;
}
public void processQuery(string strSQL, List<MySqlParameter> pars)
{
MySqlConnection conn = new MySqlConnection(conn_string);
if (startConnection(conn) == true)
{
MySqlCommand cmd = new MySqlCommand(strSQL, conn);
foreach (MySqlParameter param in pars)
{
cmd.Parameters.Add(param);
}
cmd.ExecuteNonQuery();
closeConnection(conn);
}
}
}
}
Putting the records into an array would destroy the best feature of a using a datareader: that you only need to allocate memory for one record at a time. Try doing something like this:
public IEnumerable<T> getData<T>(string query, Func<IDataRecord, T> transform)
{
using (var conn = new MySqlConnection(conn_string))
using (var cmd = new MySqlCommand(query, conn))
{
conn.Open();
using (var rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
yield return transform(rdr);
}
}
}
}
While I'm here, there's a very serious security flaw with this code and the original. A method like this that only accepts a query string, with no separate mechanism for parameters, forces you to write code that will be horribly horribly vulnerable to sql injection attacks. The processQuery() method already accounts for this, so let's extend getDataset() to avoid that security issue as well:
public IEnumerable<T> getData<T>(string query, List<MySqlParameter> pars, Func<IDataRecord, T> transform)
{
using (var conn = new MySqlConnection(conn_string))
using (var cmd = new MySqlCommand(query, conn))
{
if (pars != null)
{
foreach(MySqlParameter p in pars) cmd.Parameters.Add(p);
}
conn.Open();
using (var rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
yield return transform(rdr);
}
}
}
}
Much better. Now we don't have to write code that's just asking to get hacked anymore. Here's how your newDepartment() method will look now:
public newDepartment()
{
InitializeComponent();
inputDepartmentName.Text = "Hi";
dbConnetion db = new dbConnetion();
foreach(string name in db.getDataSet("SELECT name FROM test;", null, r => r["name"].ToString() ))
{
//Do Something
}
}
One thing about this code is that is uses a delegate to have you provide a method to create a strongly-typed object. It does this because of the way the datareaders work: if you don't create a new object at each iteration, you're working on the same object, which can have undesirable results. In this case, I don't know what kind of object you're working with, so I just used a string based on what your SELECT query was doing.
Based on a separate discussion, here's an example of calling this for a more complicated result set:
foreach(var item in db.getDataSet(" long query here ", null, r =>
new columnClass()
{
firstname = r["firstname"].ToString(),
lastname = r["lastname"].ToString(),
//...
}
) )
{
//Do something
}
Since you are new to .Net I thought I point out that there are two layers of database access in ADO.Net. There are the data reader way that you are using and all of that is online only forward reading of queries. This is the lowest level access and will give you the best performance but it is more work. For most connection types you can only execute one command or have one active data reader per connection (And you can't close the connection before you have read the query as you are doing).
The other form is the offline data adapter and requires just a little bit different code, but is generally easier to use.
public DataTable getDataSet(string query)
{
MySqlConnection conn = new MySqlConnection(conn_string);
if (startConnection(conn) == true)
{
MySqlDataAdapter adapter = new MySqlDataAdapter(query, conn);
DataTable table = new DataTable();
adapter.Fill(table);
closeConnection(conn);
return table;
}
return null;
}
This will result in you getting a DataTable with columns and rows corresponding to the result of your query (Also look into command builders if you want to post changes back to the database later on from it, but for that you will need to keep the connection open).
One nice thing with using the data adapter is that it will figure out what the correct data types should be so you don't have to worry about invalid cast exceptions while reading the data from the data reader.
As somebody pointed out though you will need to read all the data into memory which could be a problem if you are dealing with a lot of memory. Also the DataTable class is really slow when you start dealing with a lot of records. Finally DataTable and DataSet classes also generally hook well into UI components in .Net so that their contents can easily be displayed to users.

Insert Sql function is not working

I created a sale table which Insert function does not work properly. It shows the error message like this "ExecuteNonQuery requires an open and available Connection. The connection's current state is closed." If I removed Sql Close Statement on Line 14, it shows this error msg "There is already an open DataReader associated with this Command which must be closed first." My code below work like this. I checked available stocks from my Product table. If quantity order is greater than quantity from Product Table, show error message. Otherwise, proceed to inserting order information into Sale Table. Any help is appreciated.
private void btnOrder_Click(object sender, EventArgs e)
{
int iQuantityDB;
int iCustomerID = Convert.ToInt32(txtCustomerID.Text);
int iProductID = Convert.ToInt32(txtProductID.Text);
decimal dPrice = Convert.ToDecimal(txtPrice.Text);
int iQuantity = Convert.ToInt32(txtQuantity.Text);
decimal dSubtotal = Convert.ToDecimal(txtSubTotal.Text);
decimal dGST = Convert.ToDecimal(txtGST.Text);
decimal dTotal = Convert.ToDecimal(txtTotal.Text);
string strConnectionString = #"Data Source = KK\SQLEXPRESS; Integrated Security = SSPI; Initial Catalog = JeanDB; MultipleActiveResultSets=True;";
using (var sqlconn = new SqlConnection(strConnectionString))
{
sqlconn.Open();
string querySelectQuantity = #"Select Quantity from dbo.JeanProduct WHERE ProductID = #iProductID";
using (var cmdOrder = new SqlCommand(querySelectQuantity, sqlconn))
{
using (var sdRead = cmdOrder.ExecuteReader())
{
sdRead.Read();
iQuantityDB = Convert.ToInt32(sdRead["Quantity"]);
}
}
if (iQuantityDB > iQuantity)
{
string InsertQuery = #"INSERT INTO Sale(CustomerID, ProductID, Price, Quantity, Subtotal, GST, Total)VALUES(#iCustomerID, #iProductID, #dPrice, #iQuantity, #dSubtotal, #dGST, #Total)";
using (var InsertCMD = new SqlCommand(InsertQuery, sqlconn))
{
InsertCMD.Connection = sqlconn;
InsertCMD.Parameters.AddWithValue("#iCustomerID", iCustomerID);
InsertCMD.Parameters.AddWithValue("#iProdcutID", iProductID);
InsertCMD.Parameters.AddWithValue("#dPrice", dPrice);
InsertCMD.Parameters.AddWithValue("#iQuantity", iQuantity);
InsertCMD.Parameters.AddWithValue("#dSubtotal", dSubtotal);
InsertCMD.Parameters.AddWithValue("#dGST", dGST);
InsertCMD.Parameters.AddWithValue("#dTotal", dTotal);
InsertCMD.ExecuteNonQuery();
LoadDataonTable();
}
}
else
{
MessageBox.Show("no more stock");
}
sqlconn.Close();
}
}
You should change your connection string to
string strConnectionString = #"Data Source = KK\SQLEXPRESS;
Integrated Security = SSPI;
Initial Catalog = JeanDB;
MultipleActiveResultSets=True";
And do not close the connection between the Reader.Read and the ExecuteNonQuery.
You need at least Sql Server 2005 for this to work.
The connection used by a SqlDataReader cannot be used for other operations unless you set the connection string with the MultipleActiveResultSets key. Of course you could open two connection objects (with the same connection string) and use one for the SqlDataReader and one to Execute your command.
Not really linked to your problem, but I suggest to use a parameterized query also for the SELECT part of your code.
Moreover, you should use the Using Statement around the disposable object to ensure the proper closing and disposing also in case of exceptions. Finally, the syntax used in the INSERT INTO is not correct. I think that this code could explain some of the points explained above.
string strConnectionString = #"......;MultipleActiveResultSets=True;";
using(SqlConnection sqlconn = new SqlConnection(strConnectionString))
{
sqlconn.Open();
string querySelectQuantity = #"Select Quantity from dbo.JeanProduct
WHERE ProductID = #id";
using(SqlCommand cmdOrder = new SqlCommand(querySelectQuantity, sqlconn))
{
cmdOrder.AddWithValue("#id", Convert.ToInt32(txtProductID.Text));
using(SqlDataReader sdRead = cmdOrder.ExecuteReader())
{
if(sdRead.Read())
{
.....
string InsertQuery = #"INSERT INTO Sale(SaleID, CustomerID, ProductID,
Price, Quantity, Subtotal, GST, Total)VALUES(#iCustomerID,
#iProductID, #dPrice, #iQuantity,
#dSubtotal, #dGST, #Total)";
using(SqlCommand InsertCMD = new SqlCommand(InsertQuery, sqlconn))
{
InsertCMD.Parameters.AddWithValue("#iCustomerID", iCustomerID);
....
InsertCMD.ExecuteNonQuery();
LoadDataonTable();
}
}
else
{
MessageBox.Show("no more stock");
}
}
}
}
You've closed your SqlConnection after the reader execution / read cycle (and in the other error, you've kept the reader open while trying to execute another command).
Either close the reader and leave the connection open for the insert, or open a new connection for the insert.
Better still, use using to handle the disposal of the resources for you, and scope the DB resources to be released as soon as you are done with them, e.g.:
using (var sqlconn = new SqlConnection(strConnectionString))
{
sqlconn.Open();
string querySelectQuantity = "Select Quantity ...";
using var (cmdOrder = new SqlCommand(querySelectQuantity, sqlconn))
{
int iQuantityDB;
using (var sdRead = cmdOrder.ExecuteReader())
{
sdRead.Read();
iQuantityDB = Convert.ToInt32(sdRead["Quantity"]);
} // Dispose reader
// sqlconn.Close(); <-- Don't close
} // cmdOrder disposed here
if (iQuantityDB > iQuantity)
{
string InsertQuery = "INSERT INTO ...";
using var (InsertCMD = new SqlCommand(InsertQuery, sqlconn))
{
// ...
} // InsertCmd disposed here
}
} // Sql Connection disposed here
This will overcome many bugs, such as the one you've got where you are conditionally closing the command + connection in an if branch.

Execute Select with more than one column and assign to Labels

I am working on a way to be able to run a SELECT Statement that returns more than one value into two different labels on my application.
The idea is to be able to get some version information from the database by running a select on the DB picked, I got the code to work but I know it's super sloppy and im just not really sure how to clean it up. I know there has to be a better way than what I have going on here.
// Update Version & Version2
string sqlCom1 = String.Format(#"SELECT [Version]
FROM ConfigSystem");
string sqlCom = String.Format(#"SELECT Version2
FROM ConfigSystem");
SqlConnectionStringBuilder ConnectionString = new SqlConnectionStringBuilder();
ConnectionString.DataSource = "SQL06";
ConnectionString.InitialCatalog = "SuperSweetDB";
ConnectionString.IntegratedSecurity = true;
SqlConnection cnn;
cnn = new SqlConnection(ConnectionString.ToString());
using (var version = new SqlCommand(sqlCom1, cnn))
{
cnn.Open();
label.Text = (string)version.ExecuteScalar();
cnn.Close();
};
using (var version = new SqlCommand(sqlCom, cnn))
{
cnn.Open();
label2.Text = (string)version.ExecuteScalar();
};
I think, and I'm not certain, that I am opening to connections to get data that I could easily get in SQL with one. The issue that it returns to columns worth of data and I have not been able to find it in Google on how to handle this. (I'm probably looking for the wrong thing)
I only did it this way because I really needed it to work, now I'm trying to clean everything up.
Just a heads up, Fairly new to C# or anything that is not SQL.
Any help is appreciated, if this is a duplicate question I apologize.
You can separate your 2 SQL statements with a semi-colon in 1 string variable, and then use the NextResult() method on the SqlDataReader to get multiple resultsets back.
I updated your code to work. See below. Notice the use of using keyword automatically disposes off the resources after code execution. I tested the code using SQL Server built in variables in 2 sql statements, which are commented.
You should be able to paste the following code in a console app and run it successfully.
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
namespace SqlMultipleResultsets
{
class Program
{
static void Main(string[] args)
{
SqlConnectionStringBuilder ConnectionString = new SqlConnectionStringBuilder();
ConnectionString.DataSource = "SQL06";
ConnectionString.InitialCatalog = "SuperSweetDB";
//ConnectionString.DataSource = "(localdb)\\Projects";
//ConnectionString.InitialCatalog = "tempdb";
ConnectionString.IntegratedSecurity = true;
string sqlSelect = #"SELECT [Version] FROM ConfigSystem;" +
#"SELECT Version2 FROM ConfigSystem";
// string sqlSelect = #"SELECT [Version] = ##VERSION;"
// + #"SELECT Version2 = ##LANGUAGE;" ;
int recordCount;
using (SqlConnection cnn = new SqlConnection(ConnectionString.ToString()))
{
using (SqlCommand command = new SqlCommand(sqlSelect, cnn))
{
cnn.Open( );
SqlDataReader dr = command.ExecuteReader( );
recordCount = 0;
do
{
Console.WriteLine("Result set: {0}", ++recordCount);
while (dr.Read( ))
{
Console.WriteLine("Version: {0}", dr[0]);
}
Console.WriteLine(Environment.NewLine);
}
while (dr.NextResult( ));
} // END command
} // END connection
Console.Write("Press a key to exit...");
Console.ReadKey();
} // END Main
}
}
You could create the SqlCommand only once and reuse it - you can set the command dynamically:
using (var version = new SqlCommand())
{
version.CommandType = CommandType.Text;
version.Connection = cnn;
cnn.Open();
version.CommandText = sqlCom1;
label.Text = (string)version.ExecuteScalar();
version.CommandText = sqlCom2;
label2.Text = (string)version.ExecuteScalar();
cnn.Close();
};
There are a lot of examples in the official SqlCommand class documentation at MSDN.
Here is the answer that I came up with in case anyone needs help on this in the future:
string sqlCom = String.Format(#"SELECT [Version],version2 FROM ConfigSystem");
SqlConnectionStringBuilder ConnectionString = new SqlConnectionStringBuilder();
ConnectionString.DataSource = SQL06;
ConnectionString.InitialCatalog = "SuperSweetdb";
ConnectionString.IntegratedSecurity = true;
SqlConnection cnn = new SqlConnection(ConnectionString.ToString());
using (var version = new SqlCommand(sqlCom, cnn))
{
cnn.Open();
using(IDataReader dataReader = version.ExecuteReader())
{
while (dataReader.Read())
{
label7.Text = dataReader["Version"].ToString();
label9.Text = dataReader["VertexDataVersion"].ToString();
}
}
};
What I ended up doing was breaking it down with datareader. This was kind of difficult to figure out and required a lot of tinkering but in the end it's a cleaner connection that prior to what I had.
Basically DataReader is what I was looking for.
Here is a link to something that helped out quite e bit: Invalid attempt to read when no data is present
As well as this: Multiple SQL queries asp.net c#
Hopefully anyone who has a similar problem will be able to get some help from this.

Avoid enabling MSDTC when using TransactionScope

[Using: C# 3.5 + SQL Server 2005]
I have some code in the Business Layer that wraps in a TransactionScope the creation of an order and its details:
DAL.DAL_OrdenDeCompra dalOrdenDeCompra = new GOA.DAL.DAL_OrdenDeCompra();
DAL.DAL_ItemDeUnaOrden dalItemDeUnaOrden = new GOA.DAL.DAL_ItemDeUnaOrden();
using (TransactionScope transaccion = new TransactionScope())
{
//Insertion of the order
orden.Id = dalOrdenDeCompra.InsertarOrdenDeCompra(orden.NumeroOrden, orden.PuntoDeEntregaParaLaOrden.Id, (int)orden.TipoDeCompra, orden.FechaOrden, orden.Observaciones);
foreach (ItemDeUnaOrden item in orden.Items)
{
//Insertion of each one of its items.
dalItemDeUnaOrden.InsertarItemDeUnaOrden(orden.Id, item.CodigoProductoAudifarma, item.CodigoProductoJanssen, item.CodigoEAN13, item.Descripcion, item.CantidadOriginal, item.ValorUnitario);
}
transaccion.Complete();
}
return true;
And here is the DAL code that perform the inserts:
public int InsertarOrdenDeCompra(string pNumeroOrden, int pPuntoEntregaId, int pTipoDeCompra, DateTime pFechaOrden, string pObservaciones)
{
try
{
DataTable dataTable = new DataTable();
using (SqlConnection conexion = new SqlConnection())
{
using (SqlCommand comando = new SqlCommand())
{
ConnectionStringSettings conString = ConfigurationManager.ConnectionStrings["CSMARTDB"];
conexion.ConnectionString = conString.ConnectionString;
conexion.Open();
comando.Connection = conexion;
comando.CommandType = CommandType.StoredProcedure;
comando.CommandText = "GOA_InsertarOrdenDeCompra";
//...parameters setting
return (int)comando.ExecuteScalar();
...
public int InsertarItemDeUnaOrden(int pOrdenDeCompraId, string pCodigoProductoAudifarma, string pCodigoProductoJanssen, string pCodigoEAN13, string pDescripcion, int pCantidadOriginal, decimal pValorUnitario)
{
try
{
DataTable dataTable = new DataTable();
using (SqlConnection conexion = new SqlConnection())
{
using (SqlCommand comando = new SqlCommand())
{
ConnectionStringSettings conString = ConfigurationManager.ConnectionStrings["CSMARTDB"];
conexion.ConnectionString = conString.ConnectionString;
conexion.Open();
comando.Connection = conexion;
comando.CommandType = CommandType.StoredProcedure;
comando.CommandText = "GOA_InsertarItemDeUnaOrden";
//... parameters setting
return comando.ExecuteNonQuery();
Now, my problem is in the items insertion; when the InsertarItemDeUnaOrden tries to open a new connection an exception is rised because that would cause the TransactionScope to try promoting to MSDTC, wich I don't have enabled and I would prefer not to enable.
My doubts:
Understandig that the method tht starts the transaction is in the business layer and I don't want there any SqlConnection, ¿can I use another design for my data access so I'm able to reuse the same connection?
Should I enable MSDTC and forget about it?
Thanks.
EDIT: solution
I created a new class in the DAL to hold transactions like this:
namespace GOA.DAL
{
public class DAL_Management
{
public SqlConnection ConexionTransaccional { get; set; }
public bool TransaccionAbierta { get; set; }
public DAL_Management(bool pIniciarTransaccion)
{
if (pIniciarTransaccion)
{
this.IniciarTransaccion();
}
else
{
TransaccionAbierta = false;
}
}
private void IniciarTransaccion()
{
this.TransaccionAbierta = true;
this.ConexionTransaccional = new SqlConnection();
ConnectionStringSettings conString = ConfigurationManager.ConnectionStrings["CSMARTDB"];
this.ConexionTransaccional.ConnectionString = conString.ConnectionString;
this.ConexionTransaccional.Open();
}
public void FinalizarTransaccion()
{
this.ConexionTransaccional.Close();
this.ConexionTransaccional = null;
this.TransaccionAbierta = false;
}
}
}
I modified the DAL execution methods to receive a parameter of that new class, and use it like this:
public int InsertarItemDeUnaOrden(int pOrdenDeCompraId, string pCodigoProductoAudifarma, string pCodigoProductoJanssen, string pCodigoEAN13, string pDescripcion, int pCantidadOriginal, decimal pValorUnitario, DAL_Management pManejadorDAL)
{
try
{
DataTable dataTable = new DataTable();
using (SqlConnection conexion = new SqlConnection())
{
using (SqlCommand comando = new SqlCommand())
{
if (pManejadorDAL.TransaccionAbierta == true)
{
comando.Connection = pManejadorDAL.ConexionTransaccional;
}
else
{
ConnectionStringSettings conString = ConfigurationManager.ConnectionStrings["CSMARTDB"];
conexion.ConnectionString = conString.ConnectionString;
conexion.Open();
comando.Connection = conexion;
}
comando.CommandType = CommandType.StoredProcedure;
comando.CommandText = "GOA_InsertarItemDeUnaOrden";
And finally, modified the calling class:
DAL.DAL_OrdenDeCompra dalOrdenDeCompra = new GOA.DAL.DAL_OrdenDeCompra();
DAL.DAL_ItemDeUnaOrden dalItemDeUnaOrden = new GOA.DAL.DAL_ItemDeUnaOrden();
using (TransactionScope transaccion = new TransactionScope())
{
DAL.DAL_Management dalManagement = new GOA.DAL.DAL_Management(true);
orden.Id = dalOrdenDeCompra.InsertarOrdenDeCompra(orden.NumeroOrden, orden.PuntoDeEntregaParaLaOrden.Id, (int)orden.TipoDeCompra, orden.FechaOrden, orden.Observaciones, dalManagement);
foreach (ItemDeUnaOrden item in orden.Items)
{
dalItemDeUnaOrden.InsertarItemDeUnaOrden(orden.Id, item.CodigoProductoAudifarma, item.CodigoProductoJanssen, item.CodigoEAN13, item.Descripcion, item.CantidadOriginal, item.ValorUnitario, dalManagement);
}
transaccion.Complete();
}
dalManagement.FinalizarTransaccion();
With this changes I'm inserting orders and items without enabling MSDTC.
When using TransactionScope with multiple connections against SQL Server 2005, the transaction will always escalate to a distributed one (meaning MSDTC will be used).
This is a known issue, fixed in SQL Server 2008.
One option you have is to write a single stored procedure that does all the required operations (folding up GOA_InsertarOrdenDeCompra and all calls GOA_InsertarItemDeUnaOrden). With SQL Server 2005 this can be accomplished with an XML parameter, though SQL Server 2008 (apart from not having this issue) has table-valued parameters.
Can't you create the connection outside the methods and pass the same connection to both methods through the parameters?
That way you use the same connection avoiding the promotion.
My good solution would be to rethink the architecture of the DAL.
Something like having an central DAL, that stores an connection object, and have an reference to your DAL_OrdenDeCompra and DAL_ItemDeUnaOrden objects, and passing the reference of the DAL to this objects so they can interact with the connection stored in the DAL.
And then the DAL could have an Open and Close method with reference count, open increments, close decrements and it should only dispose the connection when it reaches zero and create a new one when incrementing to one. Also the DAL should implement the IDisposable to clean the resources of the connection. Then in your Business Layer you do something like this:
using(DAL dal = new DAL())
{
DAL.DAL_OrdenDeCompra dalOrdenDeCompra = dal.OrdenDeCompra;
DAL.DAL_ItemDeUnaOrden dalItemDeUnaOrden = dal.ItemDeUnaOrden;
using (TransactionScope transaccion = new TransactionScope())
{
dal.Open();
//Insertion of the order
orden.Id = dalOrdenDeCompra.InsertarOrdenDeCompra(orden.NumeroOrden, orden.PuntoDeEntregaParaLaOrden.Id, (int)orden.TipoDeCompra, orden.FechaOrden, orden.Observaciones);
foreach (ItemDeUnaOrden item in orden.Items)
{
//Insertion of each one of its items.
dalItemDeUnaOrden.InsertarItemDeUnaOrden(orden.Id, item.CodigoProductoAudifarma, item.CodigoProductoJanssen, item.CodigoEAN13, item.Descripcion, item.CantidadOriginal, item.ValorUnitario);
}
transaccion.Complete();
}
return true;
}
You could have a method in DAL.DAL_ItemDeUnaOrden which receives a collection of ItemDeUnaOrden instead of a single item, that way you can use a SqlTransaction (or TransactionScope) and iterate over the items within the DA method.
orden.Id = dalOrdenDeCompra.InsertarOrdenDeCompra(...);
dalItemDeUnaOrden.InsertarVariosItemsDeUnaOrden(orden.Items);
Depending on your code, you might not have access to your busiess objects (ItemDeUnaOrden) within you DAL, so you might need to pass the values some other way, maybe DTOs or a DataTable.

Categories