I have a long running stored procedure that returns multiple results.
I'd like to iterate the results asynchronously and grab results as they are ready (AS THEY ARE AVAILABLE).
ExecuteReaderAsync with some WaitOne logic? (never used this so any example is appreciated)
Is this possible?
private IEnumerable<DataTable> validationResultSets(string MOName, DateTime StartDate, DateTime EndDate, string FilePath)
{
DataTable d;
using (SqlConnection conn =
new SqlConnection(connString))
{
conn.Open();
using (cmd = new SqlCommand("dbo.sp_ValidateAcceptanceFile", conn))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter
{
ParameterName = "#MOName",
Value = MOName,
SqlDbType = SqlDbType.VarChar,
Size = 255
});
cmd.Parameters.Add(new SqlParameter
{
ParameterName = "#StartDate",
Value = StartDate,
SqlDbType = SqlDbType.DateTime
});
cmd.Parameters.Add(new SqlParameter
{
ParameterName = "#EndDate",
Value = EndDate,
SqlDbType = SqlDbType.DateTime
});
cmd.Parameters.Add(new SqlParameter
{
ParameterName = "#FilePath",
Value = FilePath,
SqlDbType = SqlDbType.VarChar,
Size = 500
});
//IDataReader rdr = cmd.ExecuteReader();
IDataReader rdr = cmd.BeginExecuteReader(); //??
try
{
do
{
d = new DataTable();
d.Load(rdr);
yield return d;
} while (!rdr.IsClosed);
}
finally
{
rdr.Close();
rdr.Dispose();
}
}
}
}
You are mixing concepts. A procedure returning multiple results is not related in any way to MARS. A procedure returning multiple results can be invoked asynchronously, note that your async call will complete as soon as the first fragment of the result has returned from the server and from there on you read the results just like in the sync code.
Related
I execute a stored procedure from C# like this:
medianOfProjects = db.ExeSQLParamByDateTime("usp_TaskStatistics_Median_Calculation", parameters, "#TaskTypeTableType", 1, startDate, endDate
unitNumberFrom, unitNumberTo, unitNumberBldgsSegsFrom, unitNumberBldgsSegsTo, unitNumberSqrFrom, unitNumberSqrTo, unitNumberWoodStoriesFrom,
unitNumberWoodStoriesTo, currentRegionKey);
ExeSQLParamByDateTime method is too large:
public DataTable ExeSQLParamByDateTime(string sprocName, DataTable paramArray, string tableTypeName, int LegacyKey, DateTime startingDate, DateTime endingDate,
int unitNumberFrom, int unitNumberTo, int BldgSegsFrom, int BldgSegsFromTo, int SquareFootageFrom, int SquareFootageTo, int WoodStoriesFrom,
int WoodStoriesTo, int StatusKey)
{
SqlCommand cmd = new SqlCommand(sprocName, this.dbconn);
var startDate = startingDate.Date;
var endDate = endingDate.Date;
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter(tableTypeName, SqlDbType.Structured));
cmd.Parameters[tableTypeName].Value = paramArray;
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter("#LegacyKey", SqlDbType.Int));
cmd.Parameters["#LegacyKey"].Value = LegacyKey;
cmd.Parameters.Add(new SqlParameter("#StartingDate", SqlDbType.DateTime));
cmd.Parameters["#StartingDate"].Value = startDate;
cmd.Parameters.Add(new SqlParameter("#EndingDate", SqlDbType.DateTime));
cmd.Parameters["#EndingDate"].Value = endDate;
cmd.Parameters.Add(new SqlParameter("#UnitNumberFrom", SqlDbType.Int));
cmd.Parameters["#UnitNumberFrom"].Value = unitNumberFrom;
cmd.Parameters.Add(new SqlParameter("#UnitNumberTo", SqlDbType.Int));
cmd.Parameters["#UnitNumberTo"].Value = unitNumberTo;
//etc
//etc
}
There is no way to optimize this? I just checking for good practices, as you can see I send param DataTable to execute sql TableType and in addition send parameters, but I don't found anything similar like this. Help is appreciated. regards
public DataTable ExeSQLParamByDateTime(string sprocName, DataTable paramArray, string tableTypeName, int LegacyKey, DateTime startingDate, DateTime endingDate,
int unitNumberFrom, int unitNumberTo, int BldgSegsFrom, int BldgSegsFromTo, int SquareFootageFrom, int SquareFootageTo, int WoodStoriesFrom,
int WoodStoriesTo, int StatusKey
)
{
var result = new DataTable();
//Not good to re-use the same connection object.
// ADO.Net is designed to use connection pooling, which means you want a new connection each time.
// Instead, just re-use the connection string
using (var cn = new SqlConnection(this.dbconn.ConnectionString))
using (var cmd = new SqlCommand(sprocName, cn))
{
cmd.CommandType = CommandType.StoredProcedure; //only need to do this once
//Most parameters can get down to a single line
cmd.Parameters.Add(tableTypeName, SqlDbType.Structured).Value = paramArray;
cmd.Parameters.Add("#LegacyKey", SqlDbType.Int).Value = LegacyKey;
cmd.Parameters.Add("#StartingDate", SqlDbType.DateTime).Value = startingDate.Date;
cmd.Parameters.Add("#EndingDate", SqlDbType.DateTime).Value = endingDate.Date;
cmd.Parameters.Add("#UnitNumberFrom", SqlDbType.Int).Value = unitNumberFrom;
cmd.Parameters.Add("#UnitNumberTo", SqlDbType.Int).Value = unitNumberTo;
//etc
//etc
//you can also handle parameters with size scopes this way:
cmd.Parameters.Add("#FakeParam", SqlDbType.Decimal, 5, 2).Value = 123.45;
cmd.Parameters.Add("#AlsoFake", SqlDbType.NVarChar, 30).Value = "Hello World";
cn.Open(); // wait as long as possible to open the connection
using (var rdr = cmd.ExecuteReader())
{
result.Load(rdr);
rdr.Close();
}
} //using block handles closing the connection, even if an exception is thrown
return result;
}
In T-SQL I have an exec like this:
EXEC [dbo].[usp_TaskStatus_Time_Calculation_Final]
#EmployeeGuidIdTableType = #EmployeeGuidIdTableType,
#StartingDate = '2018-08-02 00:00:00.000',
#EndingDate = '2018-08-08 00:00:00.000'
It works correctly, parameters do their job correctly, so I want to reproduce this in C# and I do:
public DataTable ExeSQLParamAndType(string SprocName, DataTable paramArray, string tableTypeName, string parameters = null)
{
SqlCommand cmd = new SqlCommand(SprocName, this.dbconn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter(tableTypeName, SqlDbType.Structured));
cmd.Parameters[tableTypeName].Value = paramArray;
cmd.Parameters.Add(new SqlParameter(parameters, SqlDbType.NVarChar));
DataTable tbl = new DataTable("Table1")
{
Locale = System.Globalization.CultureInfo.InvariantCulture
};
SqlDataAdapter da = new SqlDataAdapter(cmd);
try
{
da.Fill(tbl);
}
catch (SqlException e)
{
this.HandleSQLError(e, "GetTableBySQL", SprocName);
}
finally
{
cmd.Dispose();
}
return tbl;
}
Execute method:
db.ExeSQLParamAndType("StoredProcedureCalc", parameters,
"#EmployeeGuidIdTableType",
$"#StartingDate = '{startDate}', #EndingDate = '{endDate}'");
I get an exception when trying to pass StaringDate and EndingDate parameter in line this.HandleSQLError(e, "GetTableBySQL", SprocName);
#StartingDate = ''2018-08-02 00:00:00.000'', #EndingDate = ''2018-08-08 00:00:00.000'' is not a parameter for procedure StoredProcedureCalc
Someone see something wrong there? Regards
Note: If I execute it from c# without this two parameters (just with table type) it works
Update
I change my code as comments below to:
var startDate = $"'{startingDate.Value.ToString("yyyy-MM-dd 00:00:00.000")}'";
var endDate = $"'{endingDate.Value.ToString("yyyy-MM-dd 00:00:00.000")}'";
db.ExeSQLParamAndType("usp_TaskStatus_Time_Calculation_Final", parameters, "#EmployeeGuidIdTableType", startDate, endDate);
and method like:
public DataTable ExeSQLParamAndType(string sprocName, DataTable paramArray, string tableTypeName, string startingDate, string endingDate)
{
SqlCommand cmd = new SqlCommand(sprocName, this.dbconn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter(tableTypeName, SqlDbType.Structured));
cmd.Parameters[tableTypeName].Value = paramArray;
DateTime.TryParse(startingDate, out var startDate);
cmd.Parameters.Add(new SqlParameter("#StartingDate", SqlDbType.DateTime));
cmd.Parameters["#StartingDate"].Value = startDate;
DateTime.TryParse(startingDate, out var endDate);
cmd.Parameters.Add(new SqlParameter("#EndingDate", SqlDbType.DateTime));
cmd.Parameters["#EndingDate"].Value = endDate;
DataTable tbl = new DataTable("Table1")
{
Locale = System.Globalization.CultureInfo.InvariantCulture
};
SqlDataAdapter da = new SqlDataAdapter(cmd);
try
{
da.Fill(tbl);
}
catch (SqlException e)
{
this.HandleSQLError(e, "GetTableBySQL", sprocName);
}
finally
{
cmd.Dispose();
}
return tbl;
}
But I have troubles with DateTime, how can I parse as desire output format: 2018-08-02 00:00:00.000 ?
The problems is caused because
cmd.Parameters.Add(new SqlParameter(parameters, SqlDbType.NVarChar));
is creating a parameter with the name of whatever value is held in the variable parameters which is the string value "#StartingDate = '{startDate}', #EndingDate = '{endDate}'" because that is what you passed into ExeSQLParamAndType with this code:
db.ExeSQLParamAndType("StoredProcedureCalc"
, parameters
, "#EmployeeGuidIdTableType"
,$"#StartingDate = '{startDate}', #EndingDate = '{endDate}'");
you need to create two parameters, one called startDate and one called EndDate
cmd.Parameters.Add(new SqlParameter(startingDate, SqlDbType.NVarChar));
cmd.Parameters.Add(new SqlParameter(EndingDate , SqlDbType.NVarChar));
C# DateTime and SQL Server DateTime are incompatible. You must change the setting in your SQL tables to use Datetime2 not DateTime. You can do this by directly issuing an ALTER statement to every table with a Datetime field in your Database or adding a one line command in your DbContext class telling Entity Framework to specifically use "datetime2" for all classes that use DateTime.
I have created the data source within Visual Studio, I am trying to access a field called Reference No_ by using the Brief Number which is stored as No_.
argclean="AW02464";
string connectionString = "Data Source=ERP-SERVER; Initial Catalog=RMS2015; Integrated Security=True";
SqlConnection con = new SqlConnection(connectionString);
var query = "SELECT [Reference No_] FROM [RMS2015].[dbo].[RMS Live$Artwork Brief] WHERE [No_] = " + argclean + " ";
Above is my code that I have tried but I cant seem to get it to work
It's better to get in the habit of using params in your queries, such as:
var query = "SELECT [Reference No_] FROM [RMS2015].[dbo].[RMS Live$Artwork Brief] WHERE [No_] = #ArtworkNumber;
...and then:
new SqlParameter()
{
ParameterName = "#ArtworkNumber",
SqlDbType = SqlDbType.VarChar,
Value = argclean
}
UPDATE
Here is a general purpose method I use to retreive a DataTable from a query:
public static DataTable ExecuteSQLReturnDataTable(string sql, CommandType cmdType, params SqlParameter[] parameters)
{
using (DataSet ds = new DataSet())
using (SqlConnection connStr = new SqlConnection(YourConnStr))
using (SqlCommand cmd = new SqlCommand(sql, connStr))
{
cmd.CommandType = cmdType;
cmd.CommandTimeout = EXTENDED_TIMEOUT;
foreach (var item in parameters)
{
cmd.Parameters.Add(item);
}
cmd.Connection.Open();
new SqlDataAdapter(cmd).Fill(ds);
return ds.Tables[0];
}
}
It can be called like so:
DataTable dtDeliveryPerformanceResults =
SQLDBHelper.ExecuteSQLReturnDataTable(
PLATYPUS_STOREDPROC,
CommandType.StoredProcedure,
new SqlParameter()
{
ParameterName = "#Unit",
SqlDbType = SqlDbType.VarChar,
Value = unit
},
new SqlParameter()
{
ParameterName = "#BeginDate",
SqlDbType = SqlDbType.DateTime,
Value = _begDate
},
new SqlParameter()
{
ParameterName = "#EndDate",
SqlDbType = SqlDbType.DateTime,
Value = _endDate
},
new SqlParameter()
{
ParameterName = "#PoisonToeLength",
SqlDbType = Convert.ToInt32(SqlDbType.Int),
Value = 42
}
);
The example shows calling a Stored Procedure (a recommended practice), but you can use it with "regular" or "plain vanilla" SQL queries, too.
Thanks to some tips and reminders here, I changed my code from this kludgy mess:
try
{
DataSet dsUsage = new DataSet();
SqlConnection conn = new SqlConnection("SERVER=PROSQL05;DATABASE=platypusdata;UID=duckbill;PWD=poisonToe42;Connection Timeout=0");
SqlDataAdapter da = new SqlDataAdapter();
SqlCommand cmd = conn.CreateCommand();
cmd.CommandText = String.Format("Exec sp_ViewProductUsage_MappingRS '{0}', '{1}', '{2}'", mammal, dateBegin, dateEnd);
da.SelectCommand = cmd;
conn.Open();
da.Fill(dsUsage);
conn.Close();
DataTable dtUsage = dsUsage.Tables[0];
if (dtUsage.Rows.Count > 0)
{
foreach (DataRow productUsageByMonthDataRow in dtUsage.Rows)
{
. . .
...to this:
try
{
SqlDataAdapter da = new SqlDataAdapter();
DataSet dsUsage = new DataSet();
using (SqlConnection conn = new SqlConnection(UsageRptConstsAndUtils.PlatypusConnStr))
{
using (SqlCommand cmd = new SqlCommand("sp_ViewProductUsage_MappingRS", conn))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add("#Unit", SqlDbType.VarChar).Value = _unit;
cmd.Parameters.Add("#BegDate", SqlDbType.DateTime).Value = dtBegin;
cmd.Parameters.Add("#EndDate", SqlDbType.DateTime).Value = dtEnd;
da.SelectCommand = cmd;
conn.Open();
//cmd.ExecuteReader(); <- Is this even necessary?
da.Fill(dsUsage);
}
}
DataTable dtUsage = dsUsage.Tables[0];
if (dtUsage.Rows.Count > 0)
{
// Populate the cells
foreach (DataRow productUsageByMonthDataRow in dtUsage.Rows)
{
. . .
Note that I have SqlCommand's ExecuteReader commented out in the new code because it seems unnecessary due to the SqlDataAdapter being provided the SqlCommand. It works fine. So: am I correct in assuming I can remove cmd.ExecuteReader() altogether? Is there any benefit in retaining it, or would that be totally redundant and create "busy work" for the process?
UPDATE
So, to pass an array of SqlParameter (to the ExecuteDataSet method in MethodMan's answer), I take it that I would first have to do something like:
SqlParameter sqlp = new SqlParameter();
sqlp.ParameterName = "Unit";
sqlp.Value = _unit;
cmd.Parameters.Add(sqlp);
...etc. (and then add them to an array - or, possibly better a generic list of SqlParameter).
UPDATE 2
I just ran into this for the first time: if you use MethodMan's example (which I do) and you use a parameterless query, you need to bypass the parameter-adding loop like so:
if (null != parameters)
{
foreach (var item in parameters)
{
cmd.Parameters.Add(item);
}
}
I would personally create a SqlDBHelper class and pass call the stored procedure using a method such as this
public static class SqlDBHelper
{
public static DataSet ExecuteDataSet(string sql, CommandType cmdType, params SqlParameter[] parameters)
{
using (DataSet ds = new DataSet())
using (SqlConnection connStr = new SqlConnection(ConfigurationManager.ConnectionStrings["DbConn"].ConnectionString))
using (SqlCommand cmd = new SqlCommand(sql, connStr))
{
cmd.CommandType = cmdType;
foreach (var item in parameters)
{
cmd.Parameters.Add(item);
}
try
{
cmd.Connection.Open();
new SqlDataAdapter(cmd).Fill(ds);
}
catch (SqlException ex)
{
//log to a file or write to Console for example
Console.WriteLine(ex.Message);
}
return ds;
}
}
}
If you want to return a DataTable then change the return type in the Method signature and call the following in the return statement below
public static DataTable ExecuteDataSet(string sql, CommandType cmdType, params SqlParameter[] parameters)
return ds.Tables[0];
Here is an example on how you would call the method
someDataTable = SqlDBHelper.ExecuteDataSet("sp_ViewProductUsage_MappingRS", CommandType.StoredProcedure,
new SqlParameter() { ParameterName = "#Unit", SqlDbType = SqlDbType.VarChar, Value = _unit },
new SqlParameter() { ParameterName = "#BegDate", SqlDbType = SqlDbType.DateTime, Value = dtBegin },
new SqlParameter() { ParameterName = "#EndDate", SqlDbType = SqlDbType.DateTime, Value = dtEnd }
);
I am trying to create a SqlParameterCollection, but gives error while adding some SqlParameter in sp.Add() method.
Please help me how to add parameter and how to pass it to my another function where I declare a SqlConnection and SqlCommand.
SqlParameterCollection sp = null;
sp.Add(new SqlParameter("#CmpyCode", SqlDbType.NVarChar)).Value = CV.Global.CMPYCODE;
sp.Add(new SqlParameter("#Code", SqlDbType.NVarChar)).Value = codeName;
sp.Add(new SqlParameter("#DisplayCode", SqlDbType.NVarChar)).Value = codeName + "-";
sp.Add(new SqlParameter("#TotalDigit", SqlDbType.Int)).Value = CV.Global.PARAMTOTALDIGIT;
insertData("<Sp Name>", sp);
My another function is insertData(...)
internal static int insertData(string spName, SqlParameterCollection sp)
{
int retObj = 0;
using (SqlConnection con = new SqlConnection(CV.Global.CONSTRING))
{
try
{
con.Open();
SqlCommand cmd = new SqlCommand(spName, con);
cmd.CommandType = CommandType.StoredProcedure;
if (sp.Count > 0)
{
foreach (SqlParameter param in sp)
cmd.Parameters.Add(param);
}
retObj = cmd.ExecuteNonQuery();
}
catch (Exception ev)
{
Util.Log(ev);
throw;
}
finally
{
try
{
con.Close();
}
catch (Exception ev) { Util.Log(ev); throw; }
}
}
return retObj;
}
I am trying to create a SqlParameterCollection and passed it to the insertData function. But it throws an error while I am calling sp.Add() method in my first function.
The error is
Object reference not set to an instance of an object
You cannot use any variable like SqlParameterCollection (a reference object) without a call to its constructor (new), but the SqlParameterCollection is an object that cannot be initialized directly with a new. It has no public constructor and can be retrieved only from the property of an existant SqlCommand.
SqlCommand cmd = new SqlCommand(commandText, connection);
SqlParameterCollection sp = cmd.Parameters;
I suggest to change your InsertData method to accept a List<SqlParameter> and let it handle the adding of the parameters to the SqlCommand that executes the command text
List<SqlParameter> sp = new List<SqlParameter>()
{
new SqlParameter() {ParameterName = "#CmpyCode", SqlDbType = SqlDbType.NVarChar, Value= CV.Global.CMPYCODE},
new SqlParameter() {ParameterName = "#Code", SqlDbType = SqlDbType.NVarChar, Value = codeName},
new SqlParameter() {ParameterName = "#DisplayCode", SqlDbType = SqlDbType.NVarChar, Value = codeName + "-"},
new SqlParameter() {ParameterName = "#TotalDigit", SqlDbType = SqlDbType.Int, Value = CV.Global.PARAMTOTALDIGIT}
};
insertData(CV.Sps.SP_INSERT_PARAM_TABLE, sp);
and insertData simply receives an optional list of SqlParameter and add them to the internal SqlCommand parameter collection if needed
internal static int insertData(string spName, List<SqlParameter> sp = null)
{
....
if(sp != null)
cmd.Parameters.AddRange(sp.ToArray());
....
}
Here is a simplified answer. I use this type of thing for a dynamic SQL query with dynamic parameters. Sometimes you don't need all parameters if you are writing a dynamic sqlquery when determining if a variable has a value.
List<SqlParameter> paramList = new List<SqlParameter>();
paramList.Add(new SqlParameter("#StartDate", StartDate));
paramList.Add(new SqlParameter("#EndDate", EndDate));
if (TicketID != "" && TicketID != null && TicketID != "undefined")
{
paramList.Add(new SqlParameter("#TicketID", TicketID));
SQLQuery = SQLQuery + " AND A.TicketID = #TicketID";
}
var Parameters = paramList.ToArray();
List<Report> ReportList = db.Database.SqlQuery<Report>(SQLQuery, Parameters).ToList();