I've an issue with some sqlite code, it seems that something in the function below is causing the database to remain open after it ought to have been closed by the using statement, I can work round the issue with a call to GC.collect() in the calling function, but I'd like to try and work out the cause of the issue. I've tried adding a call to GC.Collect() after the using statment but this doesn't seem to be enough, asm assuming that something is blocking the GC from clearing up?
/// <summary>
/// Gets the Points of Interest for a given Category
/// </summary>
/// <param name="rootPath">Target Path</param>
/// <param name="category">Category to search</param>
/// <returns>A collection of Points of interest</returns>
public static Collection<PointOfInterest> GetPointsOfInterest(string rootPath, PointOfInterestCategory category)
{
if (category == null)
{
throw new ArgumentNullException("category");
}
Collection<PointOfInterest> pointsOfInterest = new Collection<PointOfInterest>();
string databaseLocation = string.Format(Resources.DataFilePath, rootPath, "poidata.db");
using (SQLiteConnection connection = new SQLiteConnection(string.Format("Data Source={0};Version=3;", databaseLocation)))
{
connection.Open();
using (SQLiteCommand command = new SQLiteCommand(
"SELECT latmin + (latmax - latmin / 2) as lat, lonmin + (lonmax - lonmin / 2) as lon, city, street, housenr, name FROM poicoord JOIN poidata ON poicoord.poiid = poidata.poiid JOIN poiname on poiname.docid = poicoord.poiid WHERE type = #category",
connection))
{
command.Parameters.Add(new SQLiteParameter("category", DbType.Int32) { Value = category.Id });
SQLiteDataReader reader = command.ExecuteReader();
int latOrdinal = reader.GetOrdinal("lat");
int lonOrdinal = reader.GetOrdinal("lon");
int nameOrdinal = reader.GetOrdinal("name");
int houseNrOrdinal = reader.GetOrdinal("housenr");
int streetOrdinal = reader.GetOrdinal("street");
int cityOrdinal = reader.GetOrdinal("city");
while (reader.Read())
{
pointsOfInterest.Add(new PointOfInterest()
{
Latitude = reader.GetDouble(latOrdinal),
Longitude = reader.GetDouble(lonOrdinal),
Name = reader.GetString(nameOrdinal),
HouseNumber = reader.IsDBNull(houseNrOrdinal) ? string.Empty : reader.GetString(houseNrOrdinal),
Street = reader.IsDBNull(streetOrdinal) ? string.Empty : reader.GetString(streetOrdinal),
City = reader.IsDBNull(cityOrdinal) ? string.Empty : reader.GetString(cityOrdinal),
});
}
}
}
return pointsOfInterest;
}
Try implementing a using statement instead of just assigning the reader to a variable:
using (SQLiteDataReader reader=command.ExecuteReader()) {
...
}
or close the reader manually:
reader.Close();
Related
Synopsis
I have a small local SQLite DB with a set records that are used to populate a ComboBoxList. The list is populated, and the DB connection released. No DB locking issues observed.
Then when a record is selected from the list, that record is retrieved and used to populate a LocalDBInstallRecord object. However, after populating the object, the DB connection seems to remain open and prevents any further access.
The Code
public LocalDBInstallRecord GetRecord(string recordID)
{
LocalDBInstallRecord lir = new LocalDBInstallRecord();
string query = "SELECT * FROM InstallationRecords WHERE RecordID = " + recordID;
using (SQLiteConnection localDBConnection = new SQLiteConnection(ConnectionStr))
{
localDBConnection.Open();
using (SQLiteCommand cmd = new SQLiteCommand(query, localDBConnection))
{
using (SQLiteDataReader rdr = cmd.ExecuteReader(CommandBehavior.KeyInfo))
{
while (rdr.Read())
{
lir.RecordID = (uint)rdr.GetInt32(rdr.GetOrdinal("RecordID"));
lir.DateCreated = rdr.GetDateTime(rdr.GetOrdinal("DateCreated"));
lir.Model = CheckDBNull(rdr, "Model");
lir.SCFirmwareVer = CheckDBNull(rdr, "SCFirmwareVer");
lir.DispFirmwareVer = CheckDBNull(rdr, "DispFirmwareVer");
lir.UCCFirmwareVer = CheckDBNull(rdr, "UCCFirmwareVer");
lir.Title = CheckDBNull(rdr, "Title");
lir.FOC = rdr.GetBoolean(rdr.GetOrdinal("FOC"));
lir.MCManufacturer = CheckDBNull(rdr, "MCManufacturer");
lir.MCType = CheckDBNull(rdr, "MCType");
lir.RailConvertor = CheckDBNull(rdr, "RailConvertor");
lir.PlantID = CheckDBNull(rdr, "PlantID");
lir.PlantOwner = CheckDBNull(rdr, "PlantOwner");
lir.PlantOwnerID = CheckDBNull(rdr, "PlantOwnerID");
lir.BoomLength = rdr.GetFloat(rdr.GetOrdinal("BoomLength"));
lir.ArticLength = rdr.GetFloat(rdr.GetOrdinal("ArticLength"));
lir.DipperLength = rdr.GetFloat(rdr.GetOrdinal("DipperLength"));
lir.MachineRecord = ReadDBBlob(rdr, "MachineRecord");
lir.DutyRecord = ReadDBBlob(rdr, "DutyRecord");
lir.CalibRecord = ReadDBBlob(rdr, "CalibRecord");
}
}
}
}
return lir;
}
Called Functions...
CheckDBNull
private static string CheckDBNull(SQLiteDataReader rdr, string col)
{
string str = null;
if (!rdr.IsDBNull(rdr.GetOrdinal(col)))
str = rdr.GetString(rdr.GetOrdinal(col));
return str;
}
ReadDBBlob
private static byte[] ReadDBBlob(SQLiteDataReader rdr, string col)
{
byte[] data = null;
if (!rdr.IsDBNull(rdr.GetOrdinal(col)))
{
SQLiteBlob blob = rdr.GetBlob(rdr.GetOrdinal(col), true);
data = new byte[blob.GetCount()];
blob.Read(data, blob.GetCount(), 0);
}
return data;
}
My Thoughts
I'm sure there's easier ways to load the object, but that's not the issue...
I think the DB connection is being held open because the SQLiteDataReader can't be disposed of cleanly. I suspect that something in this function (in the LocalDBInstallRecord object) has got hold of an object by reference. I just can't see what.
I've tried not accessing the DateTime object in the record, but that's not it.
Should I be using a different method to access the DB in this case?
Any suggestions greatly appreciated, I think it needs a fresh set of eyes. Thanks.
Further Research...
I've found that if I try and us the SQLite DB with another tool, whilst the GetRecord() function has hold of the reader, I'm able to read the DB, but can't write. I get a database locked (RELEASE 'RESTOREPOINT';) error message from "DB Browser SQLite" application. But I think that's just because it's waiting for the reader to release.
The problem came in the function ReadDBBlob. The SQLiteBlob is IDisposable, and it's use wasn't encapsulated in a using clause. As a result the SQLiteBlob wasn't being diposed of, and this held the reader and hence kept the connection open...
New ReadDBBlob
private static byte[] ReadDBBlob(SQLiteDataReader rdr, string col)
{
byte[] data = null;
if (!rdr.IsDBNull(rdr.GetOrdinal(col)))
{
using (SQLiteBlob blob = rdr.GetBlob(rdr.GetOrdinal(col), true))
{
data = new byte[blob.GetCount()];
blob.Read(data, blob.GetCount(), 0);
}
}
return data;
}
I hope this saves someone some time.
I have records in a SQL Server database table that need to be transmitted to a payment processing API. Only table records where Transmitted = 0 are to be sent to the API. I found this question which helps me to understand the error at hand.
I want to read the records from my database table into a C# class, parse into JSON, and send via API call. I've tried using Entity Framework Core but receive Null Reference exception when I called the stored procedure and add the results to my model class as follows:
public async Task<IEnumerator<Onboarding>> GetOnboardingList()
{
try
{
using (var context = new SOBOContext())
{
var ob = await context.Onboarding
//.Where(o => o.Transmitted == false)
.FromSql("Execute GetUnsentOnboardingRecords_sp")
.ToListAsync();
Console.WriteLine(ob.ToArray());
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return new OnboardingList().GetEnumerator();
}
When attempting to use Entity Framework Core, I created the OnboardingList class as below:
public class OnboardingList : IEnumerable<Onboarding>
{
private readonly List<Onboarding> _onboarding;
public IEnumerator<Onboarding> GetEnumerator()
{
return _onboarding?.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _onboarding.GetEnumerator();
}
}
I then reverted to SqlDataReader that calls the stored procedure. The method, in part, is as follows:
var listOnboardingModel = new List<Onboarding>();
try
{
using (SqlConnection connection = new SqlConnection(builder.ConnectionString))
{
SqlCommand cmd = new SqlCommand("GetUnsentOnboardingRecords_sp", connection)
{
CommandType = CommandType.StoredProcedure
};
connection.Open();
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
listOnboardingModel.Add(new Onboarding
{
OnboardingId = reader[1] as int? ?? 0,
UserId = reader[2] as int?,
UserName = reader[3].ToString(),
FirstName = reader[4].ToString(),
MiddleInitial = reader[5].ToString(),
Lastname = reader[6].ToString(),
DateOfBirth = reader[7].ToString(),
Ssn = reader[8].ToString(),
Email = reader[9].ToString(),
Address1Line1 = reader[10].ToString(),
Address1Line2 = reader[11].ToString(),
Address1ApartmentNumber = reader[12].ToString(),
Address1City = reader[13].ToString(),
Address1State = reader[14].ToString(),
Address1ZipCode = reader[15].ToString(),
Address1Country = reader[16].ToString(),
DayPhone = reader[17].ToString(),
EveningPhone = reader[18].ToString(),
PhonePin = reader[19].ToString(),
MerchantSourceIp = reader[20].ToString(),
ThreatMetrixPolicy = reader[21].ToString(),
SessionId = reader[22].ToString(),
BankAccount1CountryCode = reader[23].ToString(),
BankAccount1Name = reader[24].ToString(),
BankAccount1Description = reader[25].ToString(),
BankAccount1Number = reader[26].ToString(),
BankAccount1OwnershipType = reader[27].ToString(),
BankAccount1Type = reader[28].ToString(),
BankAccount1BankName = reader[29].ToString(),
BankAccount1RoutingNumber = reader[30].ToString(),
BankAccount2CountryCode = reader[31].ToString(),
BankAccount2Name = reader[32].ToString(),
BankAccount2Number = reader[33].ToString(),
BankAccount2OwnershipType = reader[34].ToString(),
BankAccount2Type = reader[35].ToString(),
BankAccount2BankName = reader[36].ToString(),
BankAccount2Description = reader[37].ToString(),
BankAccount2RoutingNumber = reader[38].ToString(),
AuthSginerFirstName = reader[39].ToString(),
AuthSignerLastName = reader[40].ToString(),
AuthSignerTitle = reader[41].ToString(),
AverageTicket = reader[42] as int? ?? 0,
BusinessLegalName = reader[43].ToString(),
BusinessApartmentNumber = reader[44].ToString(),
BusinessAddressLine1 = reader[45].ToString(),
BusinessAddressLine2 = reader[46].ToString(),
BusinessCity = reader[47].ToString(),
BusinessState = reader[48].ToString(),
BusinessZipCode = reader[49].ToString(),
BusinessCountry = reader[50].ToString(),
BusinessDescription = reader[51].ToString(),
DoingBusinessAs = reader[52].ToString(),
Ein = reader[53].ToString(),
HighestTicket = reader[54] as int? ?? 0,
MerchantCategoryCode = reader[55].ToString(),
MonthlyBankCardVolume = reader[56] as int? ?? 0,
OwnerFirstName = reader[57].ToString(),
OwnerLastName = reader[58].ToString(),
OwnerSsn = reader[59].ToString(),
OwnerDob = reader[60].ToString(),
OwnerApartmentNumber = reader[61].ToString(),
OwnerAddress = reader[62].ToString(),
OwnerAddress2 = reader[63].ToString(),
OwnerCity = reader[64].ToString(),
OwnerRegion = reader[65].ToString(),
OwnerZipCode = reader[66].ToString(),
OwnerCountry = reader[67].ToString(),
OwnerTitle = reader[68].ToString(),
OwnerPercentage = reader[69].ToString(),
BusinessUrl = reader[70].ToString(),
CreditCardNumber = reader[71].ToString(),
ExpirationDate = reader[72].ToString(),
NameOnCard = reader[73].ToString(),
PaymentMethodId = reader[74].ToString(),
PaymentBankAccountNumber = reader[75].ToString(),
PaymentBankRoutingNumber = reader[76].ToString(),
PaymentBankAccountType = reader[77].ToString(),
Transmitted = reader[78] as bool?,
TransmitDate = reader[79].ToString(),
InternationalId = reader[80].ToString(),
DriversLicenseVersion = reader[81].ToString(),
DocumentType = reader[82].ToString(),
DocumentExpDate = reader[83].ToString(),
DocumentIssuingState = reader[84].ToString(),
MedicareReferenceNumber = reader[85].ToString(),
MedicareCardColor = reader[86].ToString(),
PaymentBankAccountName = reader[87].ToString(),
PaymentBankAccountDescription = reader[88].ToString(),
PaymentBankCountryCode = reader[89].ToString(),
PaymentBankName = reader[90].ToString(),
PaymentBankOwnershipType = reader[91].ToString()
});
}
}
connection.Close();
}
Console.WriteLine(listOnboardingModel);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return listOnboardingModel;
When I run the console application, I receive the Error Exception thrown: 'System.IndexOutOfRangeException' in System.Data.SqlClient.dll
Index was outside the bounds of the array.
How do I prevent the above out of range exception and also ensure that all qualifying records from my database table are included for transmission?
You appear to have two distinct issues:
You .ToString() a null value, which causes null reference exception.
An ordinal is likely out of bounds, a column does not match the numeric value provided causing it to be out of bounds.
Check for DBNull.Value otherwise bullet point one will occur.
My assumption would be that you start with one, while index's are zero based. So the first column would start at zero not one.
You should lookup Dapper or some built in utilities for SqlDataReader to leverage cleaner code calls to build your object. Dapper would condense the code by simply doing:
IEnumerable<Sample> GetSamples() => dbConnection.Query<Sample>(...);
As long as the column name matches the property name, it'll populate. Based on the entity provided it looks pretty simple, but checkout the documentation.
Also you should wrap command in using statement like, that way you do not have someone accidentally call a command.Dispose() in the middle of your connection block on accident:
using(var connection = new SqlConnection(...))
using(var command = new SqlCommand(..., ...))
{
connection.Open();
...
using(var reader = command.ExecuteReader())
while(reader.Read())
{
}
}
I have a client running a web site portal which uses some legacy code that is periodically causing an issue. The system will work for days even weeks and then the worker process will die and no longer serve any data and you have to perform an IISRESET to get it working again
I have found numerous postings about this error and in my mind none of the solutions or explanations fit my code.
Here is the method in question that causes my error
/// <summary>
/// Returns Data from Database using Table Name using a field list (if supplied)
/// Otherwise will return all fields.
/// </summary>
/// <param name="TableName">The TableName rquired</param>
/// <param name="WHERE">Where clause if required</param>
/// <param name="FieldNames">String array of required field names (if any)</param>
/// <returns>Dictionary List of results.</returns>
public List<Dictionary<string, object>> Select(string TableName, string WHERE, string[] FieldNames, int TopRecords = -1, string OrderBy = null)
{
string query = string.Empty;
string sFieldNames = string.Empty;
if (FieldNames.Length > 0)
{
sFieldNames = string.Join(", ", FieldNames);
query = string.Format("SELECT {2}{0} FROM {1} ", sFieldNames, TableName, TopRecords > -1 ? "TOP (" + TopRecords + ") " : "");
if (!string.IsNullOrEmpty(WHERE))
{
query += string.Format(" WHERE {0}", WHERE);
}
}
else
{
// Select ALL fields
query = string.Format("SELECT {1}* FROM {0} ", TableName, TopRecords > -1 ? "TOP (" + TopRecords + ") " : "");
if (!string.IsNullOrEmpty(WHERE))
{
query += string.Format(" WHERE {0}", WHERE);
}
}
if (!string.IsNullOrEmpty(OrderBy))
{
query += " ORDER BY " + OrderBy;
}
//Open connection
if (this.OpenConnection() == true)
{
//System.Diagnostics.Debug.WriteLine( "SQL : " + query );
//Create Command
using (SqlCommand cmd = new SqlCommand(query, DBConnection))
{
//Create a data reader and Execute the command
//Read the data and store them in the list
List<Dictionary<string, object>> ResultsSet = null;//Create a list to store the result
using (SqlDataReader dataReader = cmd.ExecuteReader())
{
ResultsSet = new List<Dictionary<string, object>>();
while (dataReader.Read())
{
Dictionary<string, object> ROW = new Dictionary<string, object>();
for (int i = 0; i < dataReader.FieldCount; i++)
{
if (dataReader[i].GetType().ToString() == "System.Byte[]")
{
ROW.Add(dataReader.GetName(i), (byte[])dataReader[i]);
}
else
{
ROW.Add(dataReader.GetName(i), dataReader[i] + string.Empty);
}
}
ResultsSet.Add(ROW);
}
dataReader.Close(); //close Data Reader
cmd.Dispose(); // Only added today - have to wait for some time to see if it fails
}
return ResultsSet;
}
}
else
{
return null;
}
}
Many solutions state you cannot re-use a connection to perform updates but this method does not. I am pretty sure its obvious and it is only fetching the data from the database and no updates are performed.
I don't want to use MARS unless I have absolutely no choice.
Looking for pointers as to what I might have missed
Connection string
<add name="APP.Properties.Settings.DB" connectionString="server=trs-app;User Id=username;password=xxx;Persist Security Info=False;database=TRailS;Pooling=True" providerName="System.Data.SqlClient"/>
OpenConnection Method
//open connection to database
public bool OpenConnection()
{
try
{
if (DBConnection.State != ConnectionState.Open)
{
while (DBConnection.State == ConnectionState.Connecting)
{
// Do Nothing
}
DBConnection.Open();
System.Diagnostics.Debug.WriteLine("SQL Connection Opened");
}
return true;
}
catch (SqlException ex)
{
switch (ex.Number)
{
case 0:
LastError = "Cannot connect to server. Contact administrator";
return false;
case 1045:
LastError = "Invalid username/password, please try again";
return false;
}
LastError = "Unknown Error : " + ex.Message;
return false;
}
}
Just spotted this in the DAL Class - is this the cause!!!
private static SqlConnection DBConnection;
Solution might be to remove static from Sqlconnection variable (DBConnection) and implement the IDisposable pattern in the DAL class as suggested
protected virtual void Dispose(bool disposing)
{
if (disposing == true)
{
DBConnection.Close(); // call close here to close connection
}
}
~MSSQLConnector()
{
Dispose(false);
}
The code you have here ... while not perfect (see Zohar's comment - it is actively dangerous, in fact), does treat the SqlDataReader correctly - it is making use of using, etc. So: if this is throwing this error, there are three possibilities:
of the code we can see, one of the operations inside the while (dataReader.Read()) has side-effects that is causing another SQL operation to be executed on the same connection; frankly I suspect this is unlikely based on the code shown
there is code that we can't see that already has an open reader before this method is called
this could be because some code higher in the call-stack is doing another similar while (dataReader.Read()) {...} - typical in "N+1" scenarios
or it could be because something that happened earlier (but no-longer in the same call-stack) executed a query, and left the reader dangling
your this.OpenConnection() is sharing a connection between different call contexts without any consideration of what is going on (a textbook example of this would be a static connection, or a connection on some kind of "provider" that is shared between multiple call contexts)
2 and 3 are the most likely options. Unfortunately, diagnosing and fixing that requires a lot of code that we can't see.
After reading all the comments and reviewing the code in light of the information provided I have refactored the DAL class to ensure every method used in the class is now set to a using statement to create the connection. I understand IIS will handle the connection pool for this
I am also closing the db connection in code too (I know its not required with a using statement but its just for neatness.
I have a small monitoring application that will periodically refresh the login page of the portal to watch for an outage and log then perform an IISReset so I can monitor if the problem goes away after all.
Thanks for all the input.
//Open connection
using (SqlConnection DBConnection = new SqlConnection(connectionString))
{
DBConnection.Open();
//System.Diagnostics.Debug.WriteLine( "SQL : " + query );
//Create Command
using (SqlCommand cmd = new SqlCommand(query, DBConnection))
{
//Create a data reader and Execute the command
//Read the data and store them in the list
List<Dictionary<string, object>> ResultsSet = null;//Create a list to store the result
using (SqlDataReader dataReader = cmd.ExecuteReader())
{
ResultsSet = new List<Dictionary<string, object>>();
while (dataReader.Read())
{
Dictionary<string, object> ROW = new Dictionary<string, object>();
for (int i = 0; i < dataReader.FieldCount; i++)
{
if (dataReader[i].GetType().ToString() == "System.Byte[]")
{
ROW.Add(dataReader.GetName(i), (byte[])dataReader[i]);
}
else
{
ROW.Add(dataReader.GetName(i), dataReader[i] + string.Empty);
}
}
ResultsSet.Add(ROW);
}
}
DBConnection.Close();
return ResultsSet;
}
}
}
I'm trying to create a GetScalar method. We use a lot of DataTables in the legacy systems. I'm converting them to EF.
Here's the goal. This is a simple method that takes a Stored Procedure name and parameters, then returns the First Row, First Column of the result set.
public object GetScalar(string command, CommandType type = CommandType.StoredProcedure, List<SqlParameter> parameterList = null)
{
try
{
using (var cnn = new SqlConnection(ConnectionString))
{
using (var cmd = new SqlCommand(command, cnn))
{
cmd.CommandType = type;
if (parameterList != null)
{
foreach (var p in parameterList)
{
cmd.Parameters.Add(p);
}
}
cmd.Connection.Open();
object obj = cmd.ExecuteScalar();
cmd.Connection.Close();
cmd.Parameters.Clear();
return obj;
}
}
}
catch (Exception ex)
{
Logging.LogError(ex, "MSSqlUtility.GetScalar");
return -1;
}
}
I'd like to have a similar method in EF. Here's what I have so far, but this returns all columns - not just the first.
protected T SelectScalar<T>(string inStoredProcedure, ICollection<SqlParameter> inParameters = null)
{
T result = default(T);
if (inParameters != null && inParameters.Count > 0)
{
string paramNames = string.Join(",", inParameters.Select(parameter => parameter.ParameterName).ToList());
string sqlString = inStoredProcedure + " " + paramNames;
object[] paramValues = inParameters.Cast<object>().ToArray();
result = Database.SqlQuery<T>(sqlString, paramValues).FirstOrDefault();
}
else
{
result = Database.SqlQuery<T>(inStoredProcedure).FirstOrDefault();
}
return result;
}
Example usage. This returns a string object - "John Doe".
public string GetUserName(string employeeID)
{
if (string.IsNullOrWhiteSpace(employeeID))
{
return string.Empty;
}
var parameters = new Collection<SqlParameter>();
parameters.Add(StoredProcedureParameterBuilder.StringParam("#EmployeeID", employeeID, 20));
return this.SelectScalar<string>("dbo.GetUserName", parameters).Trim();
}
The SQL query looks something like this:
SELECT
FirstName + ' ' + Last Name
,EmployeeID
FROM Users
Changing the stored procedure isn't an option - we need both columns in other contexts. The current code returns both the name and the ID. I'd like to fetch just the first column, first row, of whatever my query may spit back.
Ended up having to do a bit more manual work. If anyone has a better solution, I'm all ears, but this works for my use case:
/// <summary>
/// Selects the first item in the query's result.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="inStoredProcedure">The in stored procedure.</param>
/// <param name="inParameters">The in parameters.</param>
/// <returns>The first object in the specified type T.</returns>
protected T SelectScalar<T>(string inStoredProcedure, ICollection<SqlParameter> inParameters = null)
{
using (System.Data.IDbCommand command = Database.Connection.CreateCommand())
{
try
{
command.CommandText = inStoredProcedure;
command.CommandTimeout = command.Connection.ConnectionTimeout;
if(inParameters != null && inParameters.Count > 0)
{
string paramNames = string.Join(",", inParameters.Select(parameter => parameter.ParameterName).ToList());
command.CommandText += " " + paramNames;
foreach (var param in inParameters)
{
command.Parameters.Add(param);
}
}
Database.Connection.Open();
return (T)command.ExecuteScalar();
}
finally
{
Database.Connection.Close();
command.Parameters.Clear();
}
}
}
I finally get it. It's not just the code I use to execute the ExecuteScalar method but it is mainly the code up stream that is executing the class. It is everything calling your code. That said, can someone please see if the code executing the my SQL class has faults. I still cant pass the scans. First I will show you two examples of the code calling my code, then the calling code, and finally the executing code, which I formulated and displayed from a previous post.
Calling code with three parameters:
public bool isTamAsp(int aspKey, int fy, string accountCode)
{
MyParam myParam;
string sqlQuery = "select isTamMacom = count(macom_key) FROM hier_fy " +
"WHERE hier_key = #aspKey AND fy = #fy AND #accountCode NOT IN (3,4,7,8) AND macom_key IN (select hier_key from lkup_e581_MacomThatRequireTAM) AND is_visible = 1 AND is_active = 1";
QueryContainer Instance = new QueryContainer(sqlQuery);
myParam = new MyParam();
myParam.SqlParam = new SqlParameter("#aspKey", Instance.AddParameterType(_DbTypes.Int));
myParam.SqlParam.Value = aspKey;
Instance.parameterList.Add(myParam);
myParam = new MyParam();
myParam.SqlParam = new SqlParameter("#fy", Instance.AddParameterType(_DbTypes.Int));
myParam.SqlParam.Value = fy;
Instance.parameterList.Add(myParam);
myParam = new MyParam();
myParam.SqlParam = new SqlParameter("#accountCode", Instance.AddParameterType(_DbTypes._string));
myParam.SqlParam.Value = accountCode;
Instance.parameterList.Add(myParam);
if (Convert.ToInt32(ExecuteScaler(Instance)) < 1)
return false;
return true;
}
Calling code with no parameters:
public long GetMarinesUploadNextUploadKey()
{
string query = "SELECT MAX(upload_key) FROM temp_auth_usmc_upload";
QueryContainer Instance = new QueryContainer(query);
string result = Convert.ToString(ExecuteScaler(Instance));
if (string.IsNullOrEmpty(result))
return 1;
else
return Convert.ToInt64(result) + 1;
}
Code calling my previous code with three parameters:
public bool isTamAsp(int aspKey, int fy, string accountCode)
{
return e581provider.isTamAsp(aspKey, fy, accountCode);
}
Method calling the SQL executing my code:
DbCommand command = _provider.CreateCommand();
command.Connection = _connection;
{
command.CommandText = Instance.Query;
command.CommandType = CommandType.Text;
if (Instance.parameterList.Count > 0)
{
foreach (var p in Instance.parameterList)
{
command.Parameters.Add(p.SqlParam);
}
}
if (_useTransaction) { command.Transaction = _transaction; }
try
{
returnValue = command.ExecuteScalar();
}
My Class containing the SQL string and the cmd parameter List
public enum _DbTypes
{
Int = 1, _string = 2, _long = 3, _bool = 4, _DateTime = 5,
_decimal = 6, _float = 7, _short = 8, _bite = 9
}
public class MyParam
{
public SqlParameter SqlParam { get; set; }
}
/// <summary>
/// Summary description for QueryContainer SGH
/// </summary>
public class QueryContainer
{
string _query;
public List<MyParam> parameterList = new List<MyParam>();
public QueryContainer(string query) { _query = query; }
public SqlDbType AddParameterType(_DbTypes id)
{
switch (id)
{
case _DbTypes.Int:
return (SqlDbType)Enum.Parse(typeof(SqlDbType), "int", true);
case _DbTypes._string:
return (SqlDbType)Enum.Parse(typeof(SqlDbType), "NVarChar", true);
case _DbTypes._long:
return (SqlDbType)Enum.Parse(typeof(SqlDbType), "SqlDbType.BigInt", true);
case _DbTypes._bool:
return (SqlDbType)Enum.Parse(typeof(SqlDbType), "SqlDbType.Bit", true);
}
return SqlDbType.VarChar;
}
public string Query
{
get
{
return _query;
}
set { _query = value; }
}
}
I don't see a vulnerability in that code, but I have an idea what the scan may be asking for. The problem could be that this code makes it too easy for developers to ignore the parameterList collection in your class. If I'm a new developer in your organization who hasn't discovered Sql injection yet, I'd be tempted to ignore all that complicated query parameter stuff and just use string concatenation before setting your Query property.
Instead of wrapping this in a class, what I'm more used to seeing is a single method that has a signature like this:
IEnumerable<T> GetData<T>(string query, IEnumerable<Sqlparameter> parameters)
...or some permutation of that method signature that may use arrays or lists instead of IEnumerable. This forces downstream developers to deal with that parameters argument to the method. They can't ignore it, and so the temptation to use a quick, lazy string concatenation call to substitute some user-provided data into the query is reduced.