SQLiteDataReader Appears to be Holding DB Connection Open - c#

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.

Related

How to Send Database Table Contents to API?

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())
{
}
}

Get list/multiset from informix to C#

I'm making a library for connecting an Informix database with a C# extension for use in Outsystems.
So, now I've hit a wall. I need to receive a list/multiset from the db. How can I do that? I'm using the IfxDataReader to receive the data. But I see no method that could work.
Here is how we can send a list/multiset input parameter. But I need to receive it from a result set.
EDIT: Seeing as this was frowned upon by someone I'll provide some code and my try at it to see if you think it's correct (can't test it right now as I don't yet have data on the database... I'll test it in the end):
ssBensAndFotos = new RLBensAndFotos_BemRecordList();
DatabaseConnection dc = new DatabaseConnection(ssDatabase, ssHost, ssServer, ssService, ssProtocol, ssUserID, ssPassword);
try
{
dc.Conn = new IfxConnection(dc.ConnectionString);
dc.Cmd = new IfxCommand("get_bens_and_fotos_by_id_diligencia", dc.Conn);
dc.Cmd.CommandType = CommandType.StoredProcedure;
dc.Cmd.Parameters.Add(new IfxParameter("pi_id_diligencia", IfxType.Integer) { Direction = ParameterDirection.Input, Value = sspi_id_diligencia });
dc.Conn.Open();
using (IfxDataReader reader = dc.Cmd.ExecuteReader())
if (reader.HasRows)
while (reader.Read())
{
var bensAndFotos = new STBensAndFotos_BemStructure()
{
ssid_bem = reader.GetInt32(0),
ssnumero = reader.GetInt32(1),
ssespecie = reader.GetString(2),
ssbem = reader.GetString(3),
ssvalor = reader.GetDecimal(4),
sscomum = reader.GetInt32(5),
ssvoice_record = AuxiliaryMethods.CreateByteArrayFromIfxBlob(reader.GetIfxBlob(6))
};
// Here I get the string, split it and check to see which members of the array are integers, since in this case I'll be getting a multiset of int's
var multisetString = reader.GetString(7).Split('\'');
int n;
foreach (var item in multisetString)
if (int.TryParse(item, out n))
bensAndFotos.ssfotos.Add(new STBensAndFotos_FotoStructure() { ssfoto = n });
ssBensAndFotos.Add(bensAndFotos);
}
dc.Conn.Close();
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
LIST, MULTISET and SET are mapped to String. You should be able to use IfxDataReader.GetString() to get the data.

C# parameter is not valid SQL

I have code that I copied from the tutorial that I watch and our code is so similar in the tutorial.
When the presenter runs the code, it runs ok, but when I try to run my code which is the same as in the tutorial, I get an error "the parameter is not valid".
Please help
private void Viewbutton_Click(object sender, EventArgs e)
{
conection.Open();
string sqlQuery = "select studnum, course, f_name, l_name, color_image from table3 where studnum='" + textBox1.Text + "'";
cmd = new SqlCommand(sqlQuery, conection);
SqlDataReader dataread = cmd.ExecuteReader();
dataread.Read();
if (dataread.HasRows)
{
lblstudnum.Text = dataread[0].ToString();
lblcourse.Text = dataread[1].ToString();
lblfname.Text = dataread[2].ToString();
lbllname.Text = dataread[3].ToString();
byte[] images = (byte[])dataread[4];
if(images==null)
{
pictureBox1.Image = null;
}
else
{
MemoryStream mstreem = new MemoryStream(images);
pictureBox1.Image = Image.FromStream(mstreem);
}
}
else
{
MessageBox.Show("this data not available");
}
}
The error line is the
pictureBox1.Image = Image.FromStream(mstreem);
Better to use parametric query and column name instead of using [0],[1] etc.. The Memory Stream is used by Data reader.So you shall use as below, provided a valid Image is saved in database
var con = new SqlConnection("the connection string to database");
con.Open();
SqlCommand cmd = new SqlCommand(#"sql query",con);
byte[] images = null;
using (SqlDataReader dataread = cmd.ExecuteReader())
{
if (dataread.Read())
{
//lblstudnum.Text = dataread[0].ToString();
//lblcourse.Text = dataread[1].ToString();
//lblfname.Text = dataread[2].ToString();
//lbllname.Text = dataread[3].ToString();
images = (byte[])dataread["color_image"];// column name is recommended
}
}
con.Close();
if (images == null)
{
pictureBox1.Image = null;
}
else
{
MemoryStream mstreem = new MemoryStream(images);
pictureBox1.Image = Image.FromStream(mstreem);
}
Probably not a valid image. Add some debugging code to your program (or set up a watch) that will output the length of the memory stream and its first few bytes. Make sure the length is what you were expecting. Make sure the file prefix is there, if any, e.g. bitmap files have a two-letter alphanumeric prefix. Make sure it didn't get truncated. Make sure it is an allowed file format. The problem may be that your instructor's database has data in it while yours doesn't.

Enterprise Library Database connection close

We are using Microsoft Enterprise library to access the sql server database.We are
having some doubts about how to close the DB Connection.Code is given below.
DatabaseFactory.SetDatabaseProviderFactory(new DatabaseProviderFactory(), false);
Database db = new DatabaseProviderFactory().Create("Dataconnectionstring");
string sqlCommand = "";
DbCommand dbCommand = db.GetStoredProcCommand(sqlCommand);
dbCommand.CommandTimeout = 0;
try
{
success = Convert.ToInt32(db.ExecuteScalar(dbCommand));
return success;
}
so how can we close database connection.
One way to do this is use using statements.
All Objects that are implementing IDisposable can be used in such way, when code reaches the closing bracket it calls Dispose() that is supposed to get rid of the object.
DatabaseFactory.SetDatabaseProviderFactory(new DatabaseProviderFactory(), false);
int success = 0;
using (var db = new DatabaseProviderFactory().Create("Dataconnectionstring"))
{
string sqlCommand = "";
using (var dbCommand = db.GetStoredProcCommand(sqlCommand))
{
dbCommand.CommandTimeout = 0;
try
{
success = Convert.ToInt32(db.ExecuteScalar(dbCommand));
}
}
}
return success;

Error: The name 'Name' does not exist in the current context

I've gone editing code -> which is never a good thing!
the code is for a file uploader in MVC. The thing was I was uploading two files at a time which meant they were inserted into separate rows in a database. This is the original code:
public ActionResult Index()
{
ViewData["Message"] = "Convert your eBooks!";
foreach (string upload in Request.Files)
{
if (!Request.Files[upload].HasFile1()) continue;
string mimeType = Request.Files[upload].ContentType;
Stream fileStream = Request.Files[upload].InputStream;
string fileName = Path.GetFileName(Request.Files[upload].FileName);
int fileLength = Request.Files[upload].ContentLength;
byte[] fileData = new byte[fileLength];
fileStream.Read(fileData, 0, fileLength);
const string connect = #"Server=localhost;Database=Images;user id=taraw; password=siemensbs;";
using (var conn = new SqlConnection(connect))
{
var qry = "INSERT INTO FileStore (FileContent, MimeType, FileName) VALUES (#FileContent, #MimeType, #FileName)";
var cmd = new SqlCommand(qry, conn);
cmd.Parameters.AddWithValue("#FileContent", fileData);
cmd.Parameters.AddWithValue("#MimeType", mimeType);
cmd.Parameters.AddWithValue("#FileName", fileName);
conn.Open();
cmd.ExecuteNonQuery();
}
}
return View();
}
Here is my attempt at modifying the code in order to take in the files separately as opposed to using a loop, and inserting them into a single row in a database table:
public ActionResult Index()
{
if (!Request.Files["FileUpload1"].HasFile1())
{
string mimeTypePDF = Request.Files["FileUpload1"].ContentType;
Stream fileStreamPDF = Request.Files["FileUpload1"].InputStream;
string fileNamePDF = Path.GetFileName(Request.Files["FileUpload1"].FileName);
int fileLengthPDF = Request.Files["FileUpload1"].ContentLength;
byte[] fileDataPDF = new byte[fileLengthPDF];
fileStreamPDF.Read(fileDataPDF, 0, fileLengthPDF);
}
if(!Request.Files["FileUpload2"].HasFile1())
{
string mimeTypeCover = Request.Files["FileUpload2"].ContentType;
Stream fileStreamCover = Request.Files["FileUpload2"].InputStream;
string fileNameCover = Path.GetFileName(Request.Files["FileUpload2"].FileName);
int fileLengthCover = Request.Files["FileUpload2"].ContentLength;
byte[] fileDataCover = new byte[fileLengthCover];
fileStreamCover.Read(fileDataCover, 0, fileLengthCover);
}
const string connect = #"Server=localhost;Database=Images;user id=taraw; password=siemensbs;";
using (var conn = new SqlConnection(connect))
{
var qry = "INSERT INTO Book (FileContentPDF, MimeTypePDF, FileNamePDF, FileContentCover, MimeTypeCover, FileNameCover) VALUES (#FileContentPDF, #MimeTypePDF, #FileNamePDF, #FileContentCover, #MimeTypeCover, #FileNameCover)";
var cmd = new SqlCommand(qry, conn);
cmd.Parameters.AddWithValue("#FileContentPDF", fileDataPDF);
cmd.Parameters.AddWithValue("#MimeTypePDF", mimeTypePDF);
cmd.Parameters.AddWithValue("#FileNamePDF", fileNamePDF);
cmd.Parameters.AddWithValue("#FileContentCover", fileDataCover);
cmd.Parameters.AddWithValue("#MimeTypeCover", mimeTypeCover);
cmd.Parameters.AddWithValue("#FileNameCover", fileNameCover);
conn.Open();
cmd.ExecuteNonQuery();
}
return View();
}
Now I get the following errors for each cmd.Parameters.AddWithValue:
The name 'fileDataPDF' does not exist
in the current context
I'm presuming this is because it is outside of the IF statements but I'm a bit stuck on how to structure it. I eventually want to use linq to insert the files into the database as the above method is not ideal, but my main aim for now is to just get this bit working.
Any help would be appreciated greatly :)
You need to declare the variables outside of the if conditional scopes. e.g.
string mimeTypePDF;
string fileNamePDF;
byte[] fileDataPDF;
if (!Request.Files["FileUpload1"].HasFile1())
{
mimeTypePDF = Request.Files["FileUpload1"].ContentType;
Stream fileStreamPDF = Request.Files["FileUpload1"].InputStream;
fileNamePDF = Path.GetFileName(Request.Files["FileUpload1"].FileName);
int fileLengthPDF = Request.Files["FileUpload1"].ContentLength;
fileDataPDF = new byte[fileLengthPDF];
fileStreamPDF.Read(fileDataPDF, 0, fileLengthPDF);
}
(Fwiw, I think there are better ways to handle this multifile upload, but the above is simplest way to handle your question.)
You need to declare fileDataPDF outside the if statement, otherwise its only visible to the if block.
How about byte[] fileDataPDF; before if
and fileDataPDF = new byte[fileLengthPDF]; inside if?

Categories

Resources