Execute stored procedures in parallel - c#

I have these 2 methods
public DataTable GetData1(int Id)
{
DataTable dt = new DataTable();
using (SqlConnection sqlcon = new SqlConnection(database.Connection.ConnectionString))
{
using (SqlCommand cmd = new SqlCommand("spGetData1", sqlcon))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter() { ParameterName = "#id", Value = Id});
using (SqlDataAdapter da = new SqlDataAdapter(cmd))
{
da.Fill(dt);
}
}
}
return dt;
}
public DataTable GetData2(int Id)
{
DataTable dt = new DataTable();
using (SqlConnection sqlcon = new SqlConnection(database.Connection.ConnectionString))
{
using (SqlCommand cmd = new SqlCommand("spGetData2", sqlcon))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter() { ParameterName = "#id", Value = Id});
using (SqlDataAdapter da = new SqlDataAdapter(cmd))
{
da.Fill(dt);
}
}
}
return dt;
}
and I would like to execute them at once and also get the data for further processing.
I tried something like
var task1 = Task.Factory.StartNew(() => database.Data.GetData1(1));
var task2 = Task.Factory.StartNew(() => database.Data.GetData2(2));
var taskList = new List<Task> { task1, task2 };
Task.WaitAll(taskList.ToArray());
but on the last line it crashes with
there is one or more errors.`
The inner exception is
Object reference not set to an instance of an object.
Stack trace
at System.Threading.Tasks.Task.WaitAll(Task[] tasks, Int32 millisecondsTimeout, CancellationToken cancellationToken)
The connectionString is obtain from System.Data.Entity.DbContext.Database class
public class DatabaseRepository : IDisposable
{
DbContext dbContext;
public DatabaseRepository()
{
dbContext = new DbContext("connection string ...");
Data = new DataRepository(dbContext.Database);
}
public DataRepository Data { get; set; }
}
but the error is the same even i set connection string manually, so i dont think the error is here.
using (SqlConnection sqlcon = new SqlConnection("connection string ..."))
{
using (SqlCommand cmd = new SqlCommand("spGetData2", sqlcon))
{
...
}
}
How can I do that? I see some examples use Async return type, but I don't want to duplicate these methods.

The database.Connection.ConnectionString is a static string otherwise you cant compile due to a "An object reference is required for the non-static field, method, or property".
With that in mind, its not the Connection String that's un-intstatiated because its static... and even if you purposely initialized the static string to Null then the error message would be:
InnerException = {"The ConnectionString property has not been initialized."}
Here is a repro and the error cannot be produced unless your GetData Methods are in empty objects:
namespace database
{
public class Program
{
static void Main(string[] args)
{
//WORKS!!
var repro = new database.Data();
var task1 = Task.Factory.StartNew(() => repro.GetData1(3));
var task2 = Task.Factory.StartNew(() => repro.GetData2(5));
var taskList = new List<Task> { task1, task2 };
Task.WaitAll(taskList.ToArray());
//FAILS WITH ERROR REPORTED!!
repro = null;
var task1 = Task.Factory.StartNew(() => repro.GetData1(3));
var task2 = Task.Factory.StartNew(() => repro.GetData2(5));
var taskList = new List<Task> { task1, task2 };
Task.WaitAll(taskList.ToArray());
}
}
class Data
{
private string connectionString = "Server=.;Database=CRUD_Sample;Integrated Security=True;Asynchronous Processing = True;";
public DataTable GetData1(int Id)
{
DataTable dt = new DataTable();
using (SqlConnection sqlcon = new SqlConnection(connectionString))
{
using (SqlCommand cmd = new SqlCommand("Get_CustomerbyID", sqlcon))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter() { ParameterName = "#id", Value = Id });
using (SqlDataAdapter da = new SqlDataAdapter(cmd))
{
da.Fill(dt);
}
}
}
return dt;
}
public DataTable GetData2(int Id)
{
DataTable dt = new DataTable();
using (SqlConnection sqlcon = new SqlConnection(connectionString))
{
using (SqlCommand cmd = new SqlCommand("Get_CustomerbyID", sqlcon))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter() { ParameterName = "#id", Value = Id });
using (SqlDataAdapter da = new SqlDataAdapter(cmd))
{
da.Fill(dt);
}
}
}
return dt;
}
}
}
Debugging
How do you find the source of a NullReferenceException? Apart from looking at the exception itself - the key is a NRE will be thrown exactly at the location where it occurs then you hover your mouse over the variables on the Line Of Code and see which object is null.

UPDATE:
Task.WaitAll is causing the current thread to block until everything has completed. Use Task.WhenAll so as not to tie up the other threads while waiting for the tasks to complete.
var task1 = Task.Factory.StartNew(() => database.Data.GetData1(1));
var task2 = Task.Factory.StartNew(() => database.Data.GetData2(2));
var taskList = new List<Task> { task1, task2 };
await Task.WhenAll(taskList.ToArray());
var result1 = await task1;
var result2 = await task2;
Original Answer. (Still applicable)
Based on the additional information provided in the comment, I am making assumptions about the class encapsulating the code in question. It is possible that the database.Connection is going out of scope when executed in parallel which may be causing the NRE. Extract the connection string earlier in the life cycle of the object and reuse it when getting the data.
public class MyDataClass {
string connectionString;
private Database database;
public MyDataClass(DbContext context) {
this.database = context.Database;
connectionString = database.Connection.ConnectionString;
}
public DataTable GetData1(int Id) {
var dt = new DataTable();
using (var sqlcon = new SqlConnection(connectionString)) {
using (var cmd = new SqlCommand("spGetData1", sqlcon)) {
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter() { ParameterName = "#id", Value = Id });
using (var da = new SqlDataAdapter(cmd)) {
da.Fill(dt);
}
}
}
return dt;
}
public DataTable GetData2(int Id) {
var dt = new DataTable();
using (var sqlcon = new SqlConnection(connectionString)) {
using (var cmd = new SqlCommand("spGetData2", sqlcon)) {
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter() { ParameterName = "#id", Value = Id });
using (var da = new SqlDataAdapter(cmd)) {
da.Fill(dt);
}
}
}
return dt;
}
}

Related

C# - Converting DataReader to DataTable

I want to convert a DataReader to DataTable to display all customers in a list(demoClients) from the Database.
Currently I have this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MySqlConnector;
using ProjectDatabase.Controllers;
using System.Data;
using System.Data.Common;
namespace ProjectDatabase.Models
{
public class demoClientsQuery
{
public AppDb Db { get; }
public demoClientsQuery(AppDb db)
{
Db = db;
}
public async Task<demoClients> FindAllClientsAsync(int id)
{
using var cmd = Db.conDemo.CreateCommand();
cmd.CommandText = #"Query";
cmd.Parameters.Add(new MySqlParameter
{
ParameterName = "#id_customer",
DbType = DbType.Int32,
Value = id,
});
MySqlDataReader dataReader = cmd.ExecuteReader();
DataTable dataTable = new DataTable();
dataTable.Load(dataReader);
var result = await ReadAllAsync(dataTable);
return result.Count > 0 ? result[0] : null;
}
public async Task<List<demoClients>> LatestClientsAsync()
{
using var cmd = Db.conDemo.CreateCommand();
cmd.CommandText = #"Query";
var aa = await LatestClientsAsync2();
MySqlDataReader dataReader = cmd.ExecuteReader();
DataTable dataTable = new DataTable();
dataTable.Load(dataReader);
return await ReadAllAsync(dataTable);
}
public async Task<DataTable> LatestClientsAsync2()
{
using var cmd = Db.conDemo.CreateCommand();
cmd.CommandText = #"Query";
//return await ReadAllAsync(await cmd.ExecuteReaderAsync());
return await ToDataTable(cmd);
}
private async Task<DataTable> ToDataTable(MySqlCommand cmd)
{
cmd.CommandType = CommandType.Text;
using (DbDataAdapter dataAdapter = new MySqlDataAdapter(cmd))
{
cmd.CommandType = CommandType.Text;
DataTable data = new DataTable();
dataAdapter.Fill(data);
return data;
}
}
private async Task<List<demoClients>> ReadAllAsync(DataTable dataTable)
{
var adb_demo_clients = new List<demoClients>();
using (dataTable)
{
foreach (DataRow dr in dataTable.Rows)
{
int id_customer = Convert.ToInt32(dr["id_customer"]);
string delivery_person_name = Convert.ToString(dr["delivery_person_name"]);
string firstname = Convert.ToString(dr["firstname"]);
string lastname = Convert.ToString(dr["lastname"]);
string email = Convert.ToString(dr["email"]);
}
}
return adb_demo_clients;
}
}
}
The only problem I am having is that it doesn't return nothing, it returns empty, something in my code is not returning my data from my database.
Any help would be appreciated.
Thank you for your time.
You have to add item to your adb_demo_clients list, inside the foreach, on the ReadAllAsync method:
private async Task<List<demoClients>> ReadAllAsync(DataTable dataTable)
{
var adb_demo_clients = new List<demoClients>();
using (dataTable)
{
foreach (DataRow dr in dataTable.Rows)
{
demoClients d = new demoClients();
d.id_customer = Convert.ToInt32(dr["id_customer"]);
d.delivery_person_name = Convert.ToString(dr["delivery_person_name"]);
// all fields that you need
...
// Add the item in your List
adb_demo_clients.add(d);
}
}
return adb_demo_clients;
}

set result of method to variable i can return in text field form c#

I have a method that returns a string from a database based on what i pass to it. The method works but i need to display the result in a textbox on a form. But when i run it the result i keep getting is "System.Collections.Generic.List`1[System.String]". I cant work this out. I try looping through the interest variable with for loop but this doesn't work either.
var interest = GetValue2(tvp, connectionString); -- this is my method that returns a string
textBox2.Text = string.Join(", ", interest.ToString());
this returns "System.Collections.Generic.List`1[System.String]" to textbox2. Any ideas what im missing here? thanks
PART 2:
public static List<String> GetValue2(DataTable tvp, String connectionString)
{
List<String> items = new List<String>();
using (var conn = new SqlConnection(connectionString))
{
conn.Open();
SqlCommand cmd = new SqlCommand("[dbo].[...]", conn);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter tvpParameter = new SqlParameter();
tvpParameter.ParameterName = "#..";
tvpParameter.SqlDbType = System.Data.SqlDbType.Structured;
tvpParameter.Value = tvp;
tvpParameter.TypeName = "[dbo].[....]";
cmd.Parameters.Add(tvpParameter);
using (SqlDataReader rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
Console.WriteLine((String)rdr["id"]);
}
Console.ReadLine();
}
}
return items;
}
Could you share the GetValue2 method? Probably you are calling ToString() on List in that method and it that case the GetValue2 method returns
"System.Collections.Generic.List1[System.String]"
Example:
var test = new List<string>{"some text"};
test.ToString(); // returns System.Collections.Generic.List`1[System.String]
Update:
Because the method GetValue2 returns List<string> avoid calling .ToString() on interest
var interest = GetValue2(tvp, connectionString); // returns List<string>
textBox2.Text = string.Join(", ", interest);
There's also the issue of the List<string> items inside the GetValue2() not being populated.
Here's the corrected method:
public static List<String> GetValue2(DataTable tvp, String connectionString)
{
List<String> items = new List<String>();
using (var conn = new SqlConnection(connectionString))
{
conn.Open();
SqlCommand cmd = new SqlCommand("[dbo].[...]", conn);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter tvpParameter = new SqlParameter();
tvpParameter.ParameterName = "#..";
tvpParameter.SqlDbType = System.Data.SqlDbType.Structured;
tvpParameter.Value = tvp;
tvpParameter.TypeName = "[dbo].[....]";
cmd.Parameters.Add(tvpParameter);
using (SqlDataReader rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
items.Add((String)rdr["id"]);
}
}
}
return items;
}

Misunderstood or Wrong Logic for Async and await in C# using ADO Code

I have a web api controller get action that is inserting data in database code is as
public async Task<HttpResponseMessage> Get(string UserName, string Password)
{
SqlParameter[] parameters = { new SqlParameter("#UserId", UserName), new SqlParameter("#Password", Password) };
AdoHelper DBHelper = new AdoHelper();
Company c = new Company();
await DBHelper.ExecDataSetProcAsync("[dbo].[usp_Insertuser]", parameters).ContinueWith(Task =>
{
DataSet ds = Task.Result;
if (ds != null && ds.Tables.Count == 1)
{
if (ds.Tables[0].Rows.Count >= 1)
{
}
}
ds.Dispose();
Task.Dispose();
});
HttpResponseMessage response = new HttpResponseMessage()
{
Content = new StringContent("ok", Encoding.UTF8, "text/plain")
};
return response;
}
Where as my AdoHelper methos is as
public async Task<DataSet> ExecDataSetProcAsync(string qry, params object[] args)
{
DataSet ds = new DataSet();
return await Task<DataSet>.Factory.StartNew(
() =>
{
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(DBConnection.GetConnectionString())
{
AsynchronousProcessing = true
};
using (SqlConnection conn = new SqlConnection(builder.ConnectionString))
{
conn.Open();
using (SqlCommand cmd = CreateCommand(qry, CommandType.StoredProcedure, args))
{
cmd.Connection = conn;
var reader = cmd.ExecuteReader();
while (!reader.IsClosed)
ds.Tables.Add().Load(reader);
cmd.Connection.Close();
cmd.Connection.Dispose();
cmd.Dispose();
}
if (conn != null && conn.State == ConnectionState.Open)
{
conn.Close();
conn.Dispose();
}
}
return ds;
});
}
now i am calling this web api controller method and want to insert Data of 10,000 users in loop but it opens 147 Connection with database and then threads start sending 500 internal server error i think Async and Await task are not closing Database Connection properly. Kindly advice what went wrong with my implementation or if i have misunderstood this Async and await in C#.
You should not create a new task instead use the Async methods that ADO expose.
Also, instead of disposing the connections manually, wrap everything in using statements
public async Task<DataSet> ExecDataSetProcAsync(string qry, params object[] args)
{
var ds = new DataSet();
var builder = new SqlConnectionStringBuilder(DBConnection.GetConnectionString())
{
AsynchronousProcessing = true
};
using (var conn = new SqlConnection(builder.ConnectionString))
{
await conn.OpenAsync();
using (var cmd = CreateCommand(qry, CommandType.StoredProcedure, args))
{
cmd.Connection = conn;
using(var reader = await cmd.ExecuteReaderAsync())
{
if (await reader.ReadAsync())
{
while (await reader.ReadAsync())
{
ds.Tables.Add().Load(reader);
}
}
}
}
}
return ds;
}

SqlHelper DataAdapter

I am using a SqlHelper class which has common methods for CRUD operations.
public static void Fill(DataSet dataSet, String procedureName)
{
SqlConnection oConnection = new SqlConnection(DBInterface.ConnectionString);
SqlCommand oCommand = new SqlCommand(procedureName, oConnection);
oCommand.CommandType = CommandType.StoredProcedure;
SqlDataAdapter oAdapter = new SqlDataAdapter();
oAdapter.SelectCommand = oCommand;
oConnection.Open();
using (SqlTransaction oTransaction = oConnection.BeginTransaction())
{
try
{
oAdapter.SelectCommand.Transaction = oTransaction;
oAdapter.Fill(dataSet);
oTransaction.Commit();
}
catch
{
oTransaction.Rollback();
throw;
}
finally
{
if (oConnection.State == ConnectionState.Open)
oConnection.Close();
oConnection.Dispose();
oAdapter.Dispose();
}
}
}
Now in my code, I am calling this method as,
private void BindCustomers()
{
DataSet dsCust = new DataSet();
SqlHelper.Fill(dsCust, "getCustomers");
--then I bind this dataset to datagridview
}
This all works fine. Now I want to update the data in the database. But I am confused how do I call DataAdatpaer.Update(dataset) here to update the changes made in datagridview into database. Is this possible here? Or I need to do it conventionally to find the updated row and call the ExecuteNonQuery function in the SqlHelper? Is there anything which can be done to use dataadapter.update(ds)
Thanks
You don't need to hide data adapter, or if for any reason you did so, you need to expose a method in your class to push updates to server.
Example
Public class SqlHelper
{
string commandText;
string connectionString;
public SqlHelper(string command, string connection)
{
commandText = command;
connectionString = connection;
}
public DataTable Select()
{
var table = new DataTable();
using (var adapter = new SqlDataAdapter(this.commandText, this.connectionString))
adapter.Fill(table)
return table;
}
public void Update(DataTable table)
{
using (var adapter = new SqlDataAdapter(this.commandText, this.connectionString))
{
var builder = new SqlCommandBuilder(adapter);
adapter.Update(table);
}
}
}
By calling this method in your code you can perform all crud operation select, update, delete and insert. you just need to pass connection string, procedure name and parameters list. Using this method you will retrieve data in the DataTable.if anyone want Dataset(Multiple Result) then just need to replace DataSet on the place of DataTable
SqlConnection conn = new SqlConnection("Your ConnectionString");
public DataTable ExecuteDataTable(string ProcedureName, SqlParameter[] _Param)
{
try
{
DataTable dataTable = new DataTable();
SqlCommand cmd = new SqlCommand(ProcedureName, conn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Clear();
if (_Param is not null)
{
for (int i = 0; i < _Param.Length; i++)
{
if (_Param[i].ParameterName is not null)
{
if (_Param[i].Value is not null)
{
cmd.Parameters.AddWithValue(_Param[i].ParameterName, _Param[i].Value);
}
else
{
cmd.Parameters.AddWithValue(_Param[i].ParameterName, DBNull.Value);
}
}
}
}
conn.Open();
SqlDataAdapter DA = new SqlDataAdapter(cmd);
DA.Fill(dataTable);
conn.Close();
return dataTable;
}
catch (Exception ex)
{
conn.Close();
throw;
}
}

How to get data from DB, store as List<>, and display the results to view

I am trying to get data as a list from the database but it is not showing any results. I tried to debug but after this line it doesn't let me to step over / F10:
DataSet ds = new DataSet();
da.Fill(ds);
I am trying to do this by following this example on here: link 1 and here link 2 but finding it difficult hence I thought that I should ask here.
Can someone please explain why it is not displaying the results and as such what I may be doing wrong here? how do I work around and achieve it to display the data?
Here's the full controller code for your inspection:
public static List<DBTrack> GetListOfTracks()
{
if (DBTrackData == null)
{
string myConnectionString = "Data Source="; // I have taken off the source
string mySelectString = "SELECT TrackID, AddedDate, TrackName, ArtistName from TBL_Track";
SqlDataAdapter da = new SqlDataAdapter();
DataSet ds = new DataSet();
da.Fill(ds);
OleDbConnection myConnection = new OleDbConnection(myConnectionString);
OleDbCommand myCommand = new OleDbCommand(mySelectString, myConnection);
myCommand.Connection.Open();
OleDbDataReader myReader = myCommand.ExecuteReader(CommandBehavior.CloseConnection);
List<DBTrack> list = new List<DBTrack>();
while (myReader.Read())
{
DBTrack data = new DBTrack();
data.TrackID = (Guid)(myReader["TrackID"]);
data.AddedDate = (DateTime)myReader["AddedDate"];
data.TrackName = (string)myReader["TrackName"];
data.ArtistName = (string)myReader["ArtistName"];
list.Add(data);
};
}
return DBTrackData;
}
EDIT:
SqlConnection mySQLconnection = new SqlConnection(#"Data Source=server-2\SQLExpress;Initial Catalog=End;Integrated Security=False;User ID=test1;Password=**");
SqlCommand mySelectString = new SqlCommand("SELECT TrackID, AddedDate, TrackName, ArtistName from TBL_Track");
using (SqlConnection connection = new SqlConnection("context connection=true"))
{
connection.Open();
using (SqlCommand myCommand = new SqlCommand // The best overload method System.Data.SqlClient.SqlCommand.SqlCommand has some invalid arguments
(mySelectString, mySQLconnection)) // Cannot convert from System.Data.SqlClient.SqlCommand to 'string'
{
You should set the DBTrackData to the result of the list:
while (myReader.Read())
{
...
}
DBTrackData = list;
You could also directly edit the DBTrackData in your code:
private static List<DBTrack> DBTrackData
public static List<DBTrack> GetListOfTracks()
{
if (DBTrackData == null)
{
...
DBTrackData = new List<DBTrack>();
while (myReader.Read())
{
DBTrack data = new DBTrack();
...
DBTrackData.Add(data);
};
}
return DBTrackData;
}
So, in full it should be:
public static List<DBTrack> GetListOfTracks()
{
try
{
if (DBTrackData == null)
{
string myConnectionString = "Data Source="; // I have taken off the source
string mySelectString = "SELECT TrackID, AddedDate, TrackName, ArtistName from TBL_Track";
using (OleDbConnection myConnection = new OleDbConnection(myConnectionString))
{
myConnection.Open();
using (OleDbCommand myCommand = new OleDbCommand(mySelectString, myConnection))
{
OleDbDataReader myReader = myCommand.ExecuteReader(CommandBehavior.CloseConnection);
List<DBTrack> list = new List<DBTrack>();
while (myReader.Read())
{
DBTrack data = new DBTrack();
data.TrackID = (Guid)(myReader["TrackID"]);
data.AddedDate = (DateTime)myReader["AddedDate"];
data.TrackName = (string)myReader["TrackName"];
data.ArtistName = (string)myReader["ArtistName"];
list.Add(data);
}
DBTrackData = list;
}
}
}
return DBTrackData;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return DBTrackData;
}
Your main problem is that you are using two different ways of getting the data, but the first is only partially implemented. You are using a data adapter and a data reader. Your data adapter isn't even being passed a connection or query, so this is why it is not working. So you can just remove that part of the code.
It's also easier to read if you immediately return when DBTrackData is not null rather than have a big if block over the whole code. You will then have something like:
public static List<DBTrack> GetListOfTracks()
{
if (DBTrackData != null) return DBTrackData;
string myConnectionString = "Data Source="; // I have taken off the source
string mySelectString = "SELECT TrackID, AddedDate, TrackName, ArtistName from TBL_Track";
OleDbConnection myConnection = new OleDbConnection(myConnectionString);
OleDbCommand myCommand = new OleDbCommand(mySelectString, myConnection);
myCommand.Connection.Open();
OleDbDataReader myReader = myCommand.ExecuteReader(CommandBehavior.CloseConnection);
List<DBTrack> list = new List<DBTrack>();
while (myReader.Read())
{
DBTrack data = new DBTrack();
data.TrackID = (Guid)(myReader["TrackID"]);
data.AddedDate = (DateTime)myReader["AddedDate"];
data.TrackName = (string)myReader["TrackName"];
data.ArtistName = (string)myReader["ArtistName"];
list.Add(data);
};
//Setting DBTrackData means these values will get returned on every call to GetListOfTracks for this instance of the class
DBTrackData = list;
return list;
}

Categories