In my program I got multiple spots where I open a Connection to an Oracle database, read some lines via a stored procedure which returns me a cursor, put them in an IDataReader, close the connection and go on.
Now everything works fine till the point of closing the connection. I've watched the connections opened to the database via TOAD for Oracle and I figured out, that the database seems to keep an connection opened after I said m_Connection.Close and m_Connection.Dispose.
I use Oracle.DataAccess and get an error after a short time, that there are to many connections. I debuged my session and made sure, that vb performs the close() command and it does it, but the database not. Anyone got an idea how to fix this?
In .Net, you need to wrap database connections in a Try/Catch/Finally block, and always close the connections in the Finally section. There is a shorthand for this called a Using block. This means keeping a connection as member of a class (as you seem to be doing) is almost always the wrong way to go about it. .Net is optimized so that it's better to create a new connection and command object for each query.
DataReaders are a little special: if you return a datareader out of using block, the connection can be closed before your DataReader is done with it. In C#, I normally get around the problem with an iterator (yield return). Since VB.Net lacks support for this construct, I might use an Action(Of IDataRecord) instead, like so:
Public Sub MyOracleQuery(ByVal id As Integer, ByVal ProcessRecord As Action(Of IDataRecord))
Dim sql As String = "SELECT <columns> FROM MyTable WHERE ID= #Id"
Using cn As New OracleConnection("connection string"), _
cmd As New OracleCommand(sql, cn)
cmd.Parameters.Add("#Id", SqlDbTypes.Int).Value = id
cn.Open()
Using (rdr As IDataReader = cmd.ExecuteReader())
While rdr.Read()
ProcessRecord(rdr)
End While
End Using
End Using
End Sub
You can now pass an anonymous method to this code when you call it:
Dim id As Integer
If Integer.TryParse(IDTextBox.Text, id) Then
MyOracleQuery(id, _
Function(r)
''#... Do something with each "r" here
End Function _
)
Else
''# Complain to user about invalid ID
End If
Note that this requires Visual Studio 2010 /.Net 4 for the mutli-line anonymous method. For older platforms, you'll want to declare the method.
Related
Hi friends of stackoverflow,
I'm writing a question here because i'm having problems to detect why sometimes a read to a field of a datareader returns a invalid cast exception.
I will give all information possible to understand my situation. I'm working with ASP.NET 3.5
I have a Module that have a function that returns a IDataReader and receives a sql query. something like this
function get_dr(query as String) as IDataReader
dim connection = new SqlClient.SqlConnection("connection string")
connection.open()
dim command = connection.createCommand
command.commandText = query
dim reader = command.executeReader(CommandBehavior.CloseConnection)
return reader
end function
I have a Class with a Shared function that recovers a new dataReader and returns a date. something like this:
public shared function getDate() as Date
using dr = get_dr("SELECT dbo.getDate()")
if dr.read() and dr(0) isnot dbnull.value then
return dr.GetDateTime(0)
end if
end using
end function
when in another code i call the getDate() function, it gives me a call stack like this.
System.InvalidCastException: Specified cast is not valid.
at System.Data.SqlClient.SqlBuffer.get_DateTime()
at System.Data.SqlClient.SqlDataReader.GetDateTime(Int32 i)
Why sometimes i'm getting this error? i was thinking this is because that a lot of users is calling this function in conjunction with another functions of my application (those functions eventually uses get_dr too), mixing the data of the dataReader on another executions, but i need to know if im doing something wrong or maybe to do something better.
Notes:
dbo.getDate is a sql function that ALWAYS returns a date.
don't worry about bad writing code, those are only examples but they have the necessary to understand the scenario.
sorry for my bad english
Really thanks in advance.
One possible reason - you declare connection inside of the function that returns DataReader. When you're out of the function that connection goes out of scope. That means that at some unpredictable point (depends on memory usage etc.) Garbage Collector will collect it. If you try to use the DataReader at that point - all bets are off.
One way to solve it is to declare connection outside of function get_dr and pass it there as a parameter. But also seeing that you're returning a single value and if you don't plan to use the reader for multiple values I suggest using ExecuteScalar instead - it will save you a lot of headaches.
In my Winforms app which uses a remote database, I have the function below. (I also have two other functions which work similarly: One for scalar queries which returns zero upon failure, the other for updates and inserts which returns false upon failure.)
Currently, ALL data manipulation is pumped through these three functions.
It works fine, but overall please advise if I'd be better off establishing the connection upon launching my app, then closing it as the app is killed? Or at another time? (Again, it's a windows forms app, so it has the potential to be sitting stagnant while a user takes a long lunch break.)
So far, I'm not seeing any ill effects as everything seems to happen "in the blink of an eye"... but am I getting data slower, or are there any other potential hazards, such as memory leaks? Please notice I am closing the connection no matter how the function terminates.
Public Function GetData(ByVal Query As String) As DataTable
Dim Con As New SqlConnection(GlobalConnectionString)
Dim da As New SqlDataAdapter
Dim dt As New DataTable
Try
Con.Open()
da = New SqlDataAdapter(Query, Con)
Con.Close()
da.Fill(dt)
Catch ex As Exception
Debug.Print(Query)
MsgBox("UNABLE TO RETRIEVE DATA" & vbCrLf & vbCrLf & ex.Message, MsgBoxStyle.Critical, "Unable to retrieve data.")
End Try
da.Dispose()
Con.Close()
Return dt
End Function
There are exceptions to this, but best practices in .Net do indeed call for creating a brand new connection object for most queries. Really.
To understand why is, first understand actually connecting to a database involves lots of work in terms of protocol negotiations, authentication, and more. It's not cheap. To help with this, ADO.Net provides a built-in connection pooling feature. Most platforms take advantage of this to help keep connections efficient. The actual SqlConnection, MySqlConnection, or similar object used in your code is comparatively light weight. When you try to re-use that object, you're optimizing for the small thing (the wrapper) at the expense of the much larger thing (the actual underlying connection resources).
Aside from the benefits created from connection pooling, using a new connection object makes it easier for your app to scale to multiple threads. Imagine writing an app which tries to rely on a single global connection object. Later you build a process which wants to spawn separate threads to work on a long-running task in the background, only to find your connection is blocked, or is itself blocking other normal access to the database. Worse, imagine trying to do this for a web app, and getting it wrong such that the single connection is shared for your entire Application Domain (all users to the site). This is a real thing I've seen happen.
So this is something that your existing code does right.
However, there are two serious problems with the existing method.
The first is that the author seems not to understand when to open and when to close a connection. Using the .Fill() method complicates this, because this method will open and close your connection all on its own.1 When using this method, there is no good reason to see a single call to .Open(), .Close(), or .Dispose() anywhere in that method. When not using the .Fill() method, connections should always be closed as part of a Finally block: and the easiest way to do that is with Using blocks.
The second is SQL Injection. The method as written allows no way to include parameter data in the query. It only allows a completed SQL command string. This practically forces you to write code that will be horribly vulnerable to SQL Injection attacks. If you don't already know what SQL Injection attacks are, stop whatever else you're doing and go spend some time Googling that phrase.
Let me suggest an alternative method for you to address these problems:
Public Function GetData(ByVal Query As String, ParamArray parameters() As SqlParameter) As DataTable
Dim result As New DataTable()
Using Con As New SqlConnection(GlobalConnectionString), _
Cmd As New SqlCommand(Query, Con),
da As New SqlDataAdpapter(Cmd)
If parameters IsNot Nothing Then Cmd.Parameters.AddRange(parameters)
Try
da.Fill(result)
Catch ex As Exception
Debug.Print(Query)
'Better to allow higher-level method to handle presenting the error to the user
'Just log it here and Rethrow so presentation tier can catch
Throw
End Try
End Using 'guarantees connection is not left hanging open
Return result
End Function
1See the first paragraph of the "Remarks" section.
This isn't a real "answer" to my own question, but I have something to add and I wanted to add some code.
To Joe: Thank you, my code is well on the way to using parameterized queries almost exclusively. Although I knew what SQL injection attacks were, and that they're a pretty big deal, here's my exuse: In the past I had used stored procedures for parameterized queries, and I kinda hate writing those and for the first year my code will be used only within by my small company of 5 employees who are family members... I had planned to switch everything to stored procedures later if I sold the software. This approach is better and I will probably not need stored procedures at all.
I especially like how elegantly parameterized queries handle dates, as I don't have to convert dates to appropriate text. Much easier.
Anopther advantage I'm seeing: Sometimes a "Save button" must execute either Insert or Update, depending on whether the record displayed is new. Using parameters allows me to write two alternate short basic queries, but to use the same parameters for either with less code.
Overall, this means a whole lot less code-intensive construction of the query string.
The part I didn't have, and I learned to do it elsewhere, was assigning the parameter array, calling the procedure, so I'm including an example here hoping others find it useful:
Dim query As String = "Select Phone from Employees where EmpNo = #EmployeeNumber and Age = #Age"
Dim params As SqlParameter() = {
New SqlParameter("#EmployeeNumber", txtEmployeeNumber.Value),
New SqlParameter("#Age", txtAge.Value)
}
Dim Phone as String = GetData(query, params).Rows.Item(0)
I was wondering what is the most basic way to avoid the following.
con.ConnectionString = connection_String
con.Open()
cmd.Connection = con
'database interaction here
cmd.Close()
I keep making those lines all over in my project, but I figure there has to be a better way to save on typing this over and over. It makes the code look even more sloppy than it already is!
Ended up with this, works well for me. Thanks for the help :)
Public Sub connectionState()
If con.State = 0 Then
con.ConnectionString = connection_String
con.Open()
cmd.Connection = con
Else
con.Close()
End If
End Sub
This is where a lot of programmers are tempted to create a "database layer" with a variations on method signatures that look like this:
Public DataSet ExecuteSQL(ByVal sql As String) As DataSet
That allows you to isolate all that boilerplate connection code in one place. An sql command string goes in, and data comes out. Easy.
Don't do it!
This is headed in the right direction, but has one very big flaw: it forces you to use string manipulation to substitute parameter values into your sql queries. That leads to horrible sql injection security vulnerabilities.
Instead, make sure you include some mechanism in your methods to prompt for the sql parameters separately. This usually comes in the form of an additional argument to the function, and could be as simple as an array of KeyValuePairs. If you're comfortable with lambdas, my preferred pattern looks like this:
Public Iterator Function GetData(Of T)(ByVal sql As String, ByVal addParameters As Action(Of SqlParameterCollection), ByVal translate As Func(Of IDatarecord, T)) As IEnumerable(Of T)
Using cn As New SqlConnection("connection string"), _
cmd As New SqlCommand(sql, cn)
addParameters(cmd.Parameters)
cn.Open()
Using rdr As SqlDataReader = cmd.ExecuteReader()
While rdr.Read()
Yield(translate(rdr))
End While
End Using
End Using
End Function
To call that function, you would do something like this:
Dim bigCustomers = GetData("SELECT * FROM Customers WHERE SalesTotal > #MinSalesTotal", _
Sub(p) p.Add("#MinSalesTotal", SqlDbType.Decimal, 1000000), _
MyCustomerClass.FromIDataRecord)
You can try creating a class ( a singleton class ), and write the database connection syntax code and exceptions in that class, then call one object to the main class to create the database connection, that's the best way in performance and keep writing the same code on and on...
You can use just using block, using execute dispose on non managed object in the end of treatment.
Link : http://msdn.microsoft.com/en-us/library/htd05whh(v=vs.80).aspx
Thanks William, that was the ticket. Had to assign the name property on both ends [DataTable.TableName].
On a side note here: There appears to be some school of thought (no offense Marc) that the following statement is always true:
"Everything in the world can and should be made into an object."
It is, simply, not always true. There are cases where you cannot cram your 'object' into any cookie-cutter or class no matter how you try to customize it. For me to objectize this beast, I'd have to create roughly 4,000 objects. I don't have time to do that, and yet this project must run as a service. Frankly I think the developers at MickeySoft need to get out more into the real world, and see for themselves that although theory is great it does not represent true-life challenges. I am all for working with objects for the obvious benefits, but there is a reality that universals do not exist. In my trade, even 'most of the time' rarely happens.
Before they push out a new technology set, and cease support of an old one, they need to make sure that the new technology has the same capabilities the old one had.
For the record: The people who believe the above statement to be true are also the same people who would refuse to take the project I'm currently working on.
Just the same -- thank you both for your time, efforts and opinions!
I'm trying to create a WCF function that will return a table to my console testing app. I am a total noob. The data is 2-dimensional and looks like this:
23 Letter
42 Another Letter
43 Document
...
Here's what I'm trying to do:
<ServiceContract()> _
Public Interface ILetterWriter
<OperationContract()> _
Function GetLetter(ByVal LetterID As String, ByVal StateID As String, ByVal CompID As String, ByVal tblVar As DataTable) As String
<OperationContract()> _
Function GetLetterNames(ByVal DepartmentType As Integer) As DataTable
End Interface
Public Function GetLetterNames(ByVal DepartmentType As Integer) As DataTable Implements ILetterWriter.GetLetterNames
Dim SQLCon As New SqlClient.SqlConnection
Dim SQLCmd As New SqlClient.SqlCommand
'Connect to the database
SQLCon.ConnectionString = "Data Source=VMSQL08-SRV1;Initial Catalog=DotNetDev;User ID=aiis_pgmr;Password=ag58102;"
SQLCon.Open()
'Grab the stored procedure, which returns the letter names
SQLCmd.CommandText = "sp_GetLetters"
SQLCmd.CommandType = CommandType.StoredProcedure
SQLCmd.Connection = SQLCon
'Pass the parameters
SQLCmd.Parameters.AddWithValue("#LetterType", DepartmentType)
'Execute the stored procedure, fill the datatable from a data adapter
GetLetterNames = New DataTable
GetLetterNames.Load(SQLCmd.ExecuteReader)
'Shut it down
SQLCmd.Dispose()
SQLCon.Close()
SQLCon.Dispose()
End Function
...Of course, it won't work. I just need to get the WCF to pass a basic table to my console application. The execute SQL seems to work just fine, I just can't get the data back to my application.
Any help would be greatly appreciated.
Thanks,
Jason
I agree with the other poster.
However, if you are returning a DataTable, you have to set the "Name" property of the DataTable if you want to return it from a WCF Service.
I am using Oracle 11g client, with ODP.NET. I am trying to add conditional Transaction handling.
Dim ds As New DataSet()
Dim txn As OracleTransaction
Dim _beginTransaction as Bolean = true
Using conn As New OracleConnection(ConnString)
Try
conn.Open()
If _beginTransaction Then
txn = conn.BeginTransaction(IsolationLevel.Serializable)
End If
Dim adapter As OracleDataAdapter = New OracleDataAdapter()
adapter.SelectCommand = New OracleCommand(sSQL, conn)
For i As Integer = 0 To UBound(parameters, 1)
adapter.SelectCommand.Parameters.Add(parameters(i))
Next
adapter.Fill(ds)
If _beginTransaction Then
txn.Commit() //txn is undefined here? why?
End If
Catch e As Exception
txn.Rollback()
End Try
End Using
How do I fix txn being nothing / null? The error is: Variable 'txn' is used before it has been assigned a value. A null reference exception could result at runtime. Links or pointers to solutions would be appreciated also.
Edit: Thanks to RichardOD for pointing out that you can not explicitly declare that a transaction cannot be opend up on stored procedures via ODP.NET. I have verified that this is an issue. BUT We still haven't figured out why the error is occuring. I understand that txn is initially given a value w/in an if statement, but being defined outside of the try/catch block should make that irrelevant.... right? Or is that bad coding?
Assuming _beginTransaction is a boolean have you set it to true before If _beginTransaction Then ?
Also have you committed the previous transaction before starting this one? Oracle can do weird stuff with connection pooling and BeingTransaction.
A long time ago I had a bug similar to this. Have you looked here?
Edit- are you trying to call a .NET stored proc? OracleConnection.BeginTransaction does not support stored procedure calls:
OracleConnection.BeginTransaction is
not allowed for .NET stored procedure
Question: Is it null immediately after being asigned? And if not, when does it become null? If it's null after immediately, it might be the connection pooling stuff. If after getting the adapter from conn or after filling it, then it's even crazier...
But I would try and find out
Oracle does not require a transaction for selecting data. Why do you try open one?
EDIT:
If your vb code is called from oracle (via .net integration) than there is no transaction support as RichardOD wrote. Please clarify the environment.
The sql statement executed is dynamic and given in sSQL. The command is prepared and given to a DataAdapter that fills a DataSet. Than you can only execute SELECT statements. Otherwise there is no result.
OR
Because the parameters are prepared too. You are calling a stored procedure (without telling the the CommandType is StoredProcedure). One of your parameters is a ref cursor parameter which will fetched into the DataSet. Right?
Oracle does not need explicit transactions as sql server does. Oracle starts an implicit transaction with the first dml statement in your session. The sideeffect is, if you did not start an transaction you cannot commit the implicit transaction. I do not know if there is access to implicit transaction via the connection object.