I have problems with asp.net web service that randomly getting errors like "sql transaction completed no longer usable", "transaction is already commited..."
After read lot of posts I think its problem of shared connection problem bet I was unable to find where the problem is.
Starting from 0 I want to create a "class for 2 porpuoses
- Ensure Connections are closed
- Coding less
The simply class have 2 methods GDT (Give DataTable) and Ejecutar (execute in spanish)
¿Can anybody tell me if this class is well designed for non having problems of sharing connections?
Public Class clsAccesoBD2
Public Function GDT(ByVal strSql As String) As DataTable
Try
Using Cnn As New SqlConnection(ConfigurationManager.ConnectionStrings("CadenaPEA").ConnectionString)
Using da As New SqlDataAdapter(strSql, Cnn)
Using dt As New DataTable
da.Fill(dt)
Return dt
End Using
End Using
End Using
Catch ex As Exception
'Log code here
Throw
End Try
End Function
Public Function Ejecutar(ByVal strSql As String) As Integer
Try
Using Cnn As New SqlConnection(ConfigurationManager.ConnectionStrings("CadenaPEA").ConnectionString)
Using Cb As New SqlCommand(strSql, Cnn)
Dim RegistrosAfectados As Integer
Cnn.Open()
RegistrosAfectados = Cb.ExecuteNonQuery()
Cnn.Close()
Return RegistrosAfectados
End Using
End Using
Catch ex As Exception
'Log code here
Throw
End Try
End Function
End Class
este
close db connection (i.e. Cnn.Close()) in finally block of try-catch-finally construct
I seem to be getting intermittent problems with my Oracle connection indicating something about a problem with semaphores which suggest that Oracle is somehow holding onto the connections instead of removing them after they have been used.
Here's the code and connection string I use:
Connection string: user id=user;password=password;data source=dataSource; Validate Connection=true;Min Pool Size=10;Connection Lifetime=5;Connection Timeout=60;Incr Pool Size=5;
And the code I use is this:
Dim OracleConn As New OracleConnection()
Dim DataTable As DataTable
Dim queryOracle As OracleCommand
Dim OracleDataAdapter As OracleDataAdapter
Dim connStr As String = "user id=user;password=password;data source=dataSource; Validate Connection=true;Min Pool Size=10;Connection Lifetime=5;Connection Timeout=60;Incr Pool Size=5;"
OracleConn.ConnectionString = connStr
Try
OracleConn.Open()
queryOracle = OracleConn.CreateCommand()
queryOracle.CommandText = "select * from table1"
DataTable = New DataTable()
OracleDataAdapter = New OracleDataAdapter(queryOracle)
OracleDataAdapter.Fill(DataTable)
table1.DataSource = DataTable.DefaultView
table1.DataBind()
Catch OracleEx As OracleException
Throw
Catch ex As Exception
Throw
Finally
If Not OracleConn Is Nothing And OracleConn.State = ConnectionState.Open Then
OracleConn.Close()
End If
End Try
Now my questions are:
Is this the best way of doing this?
I only "Close" my connection do I need to "Dispose" of it also?
I'm using Oracle.DataAccess.Client by the way.
Any help will be much appreciated
Try to put everything between Using.
Using oracleConn as OracleConnection = new OracleConnection()
'Your stuff goes here
End Using
the same goes for Commands.
P.S. There's is no need to catch Exception if they are just thrown again.
I suggest you to use using block (Execute Dispose in the end)
Using connection As New OracleConnection()
....
End Using
2 An application can call Close more than one time. No exception is generated.
If you called Dispose method SqlConnection object state will be reset. If you try to call any method on disposed SqlConnection object, you will receive exception.
I am currently working on an asp.net web page with a GridView displaying a table from a database. This GridView has 4 DropDownLists that will be used to filter the data shown on the GridView. When the page loads 4 Sub routines are run, each one connecting to the database with a select statement to fill the DropDownList with relevant filter headings.
Initially, I had one connection with a loop that populated all of the drop downs but these contained duplicates. I then split the filling of each DDL so that the select statements could contain DISTINCT.
I would like (and am sure there is a way here) to be able to populate all of the DDLs with data from one connection.
Code for one connection:
Protected Sub FillDepDDL()
Dim conn As New SqlConnection()
conn.ConnectionString = WebConfigurationManager.ConnectionStrings("TestDBConnectionString").ConnectionString
Dim connection As New SqlConnection(conn.ConnectionString)
connection.Open()
Const FillAllQS As String = "SELECT DISTINCT [Department] FROM [Employees]"
Dim command As New SqlCommand(FillAllQS, connection)
Dim reader As SqlDataReader = command.ExecuteReader()
Dim sel As New ListItem
sel.Text = "Please Select"
sel.Value = "*"
DDLDepartment.Items.Add(sel)
While reader.Read
Dim Deplist As New ListItem()
Deplist.Value = reader("Department")
Deplist.Text = reader("Department")
DDLDepartment.Items.Add(Deplist)
End While
reader.Close()
conn.Close()
End Sub
The other 3 column names: FirstName > DDLFN, LastName > DDLLN, Wage > DDLWag.
This is only a test DB and the princibles learned here will be applied to a larger live project.
I'm sure some guru will be able to work this out easily but I just can't get my head round it even after hours of searching.
Thanks in advance.
I'm adding this in as answer because I cannot format it in a comment, but this doesn't answer the original question of how to write the sql to return all three distinct result sets. Instead, it answers how to rewrite the code you have above so that connections are properly disposed of in case of an exception.
Protected Sub FillDepDDL()
Dim Deplist As ListItem
Dim sel As New ListItem
sel.Text = "Please Select"
sel.Value = "*"
DDLDepartment.Items.Add(sel)
Using conn As New SqlConnection(WebConfigurationManager.ConnecitonString("TestDBConnectionString").ConnectionString)
Using cmd As New SqlCommand("SELECT DISTINCT [Department] FROM [Employees]", conn)
conn.Open()
Using reader = cmd.ExecuteReader()
While reader.Read
Deplist = New ListItem()
Deplist.Value = reader("Department")
Deplist.Text = reader("Department")
DDLDepartment.Items.Add(Deplist)
End While
End Using
End Using
End Using
End Sub
I don't see any reason for you to try to return all three results in a single query. That will just make your code unnecessarily complicated just to save a millisecond or two. Connection pooling handles the creation of connections on the database server for you, so opening a new connection in your code is very fast.
I have the below mentioned code in a seperate class file for establishing connection and carry out DB transactions. I have an issue where multiple connections being opened which sometime exceed the connection pool. When I stepped through the code I found that there are codes which call ConnectDB() in a loop without calling DisconnectDB(). But I expected that the condition OraConn.State = ConnectionState.Closed should handle the situation. Somehow the condition is always satisfied hence openning another set of connection. Can you suggest where am I going wrong and also what best practice can be adopted here?
Public Class Connection
Dim Str_conn As String = "Data Source=...; User=...; password=...; Min Pool Size=10; Max Pool Size=500;"
Public OraConn As OracleConnection
Dim cmd As OracleCommand
Dim dr As OracleDataReader
Dim data_adapt As OracleDataAdapter
Dim dt As DataTable
Dim ds As DataSet
Public Sub ConnectDB()
OraConn = New OracleConnection(Str_conn)
If OraConn.State = ConnectionState.Closed Then
OraConn.Open()
End If
End Sub
Public Sub DisconnectDB()
If OraConn.State = ConnectionState.Open Then
OraConn.Close()
End If
End Sub
Public Function get_dataset(ByVal query As String, ByRef ds As DataSet) As DataSet
data_adapt = New OracleDataAdapter(query, OraConn)
data_adapt.Fill(ds)
Return ds
End Function
Public Function get_datareader(ByVal query As String) As OracleDataReader
cmd = New OracleCommand(query, OraConn)
dr = cmd.ExecuteReader()
Return dr
End Function
Public Sub UpdateDB(ByVal query As String)
cmd = New OracleCommand(query, OraConn)
cmd.ExecuteNonQuery()
cmd.Dispose()
End Sub
The class is refered in other classes or directly in the aspx.vb pages like this.
Public Function InsertData(ByVal var1 As String, ByVal var2 As String) As Integer
conn.ConnectDB()
Dim qryInsert As String
qryInsert = " INSERT INTO TABLE VALUES ('" & var1 & "', "
qryInsert = qryInsert & var2 & "')"
Try
conn.UpdateDB(qryInsert)
Catch ex As OracleException
If ex.Code = 1 Then
updData(var1, var2)
ElseIf ex.Code = 2091 Then
msgprompt("Duplicate Unique Key!", "Warning")
End If
Finally
conn.DisconnectDB()
End Try
Return count
End Function
The connection is again opened in function updData(). While I understand that it has to be closed correctly but keeping tab on every developer is not possible. Hence I want to control it directly from the connection class by using the same connection but the condition If OraConn.State = ConnectionState.Closed is not helping.
UPDATE
I have put the code in UpdateDB under a Using block and removed call to ConnectDB and DisconnectDB from function like InsertData(...). It seems that the issue has been resolved. But I would like to know in case of exception will the connection remain open? and also OraConn is a public variable defined outside Using block so will it be disposed of by the GC?
Public Sub UpdateDB(ByVal query As String)
Using OraConn = New OracleConnection(Str_conn)
cmd = New OracleCommand(query, OraConn)
Try
OraConn.Open()
cmd.ExecuteNonQuery()
Catch ex As Exception
Throw
Finally
cmd.Dispose()
End Try
End Using
End Sub
You must close all the connections as soon as you are done with it, no matter what.
Suggestion:
The best practice for closing the connection is to do it in finally block. So that even if there is any error, catch it (log it if required) in catch block, and then connection will get close in finally block.
UPDATE
You can put one private static counter in your Connection class. When ever ConnectDB() is called you increment this counter and decrement it in every DisconnectDB(). Now in ConnectDB() you check the value of counter, if it exceeds a minimum threshold you throw error, by doing this way; you can come to know idle connection present in your code and refactor it. On production keep this threshold value high or ignore it in code.
I have a *.MDB database file, and I am wondering if it is possible or recommended to work against it using LINQ in C#. I am also wondering what some simple examples would look like.
I don't know a lot about LINQ, but my requirements for this task are pretty simple (I believe). The user will be passing me a file path to Microsoft Access MDB database and I would like to use LINQ to add rows to one of the tables within the database.
What you want is a LINQ to ODBC provider, or a LINQ to JET/OLEDB provider.
Out of the box, MS doesn't make one. There may be a 3rd party who does.
Actually I recently (today) discovered that you can access an Access database with LinqToSql. It must be in the 2002 or newer format, you will not be able to drag and drop the tables to your datacontext so either manually create the objects in your dbml or you can use SQL Server Migration for Access to move it to a sql server and then drag and drop all you want. When you want to actually create the context pass it an OleDbConnection. Use your standard Jet.OLEDB.4.0 connection string on the OleDbConnection and you are good to go. Not sure of the limitation this may incurr though. I just did a quick sample and did an OrderBy without issue.
I wrote a small sample program to test this out with David's answer. You'll need to make an access database and manually create the DBML for Linq-to-SQL, as you cannot drag 'n drop them.
Inserts fail, citing Missing semicolon (;) at end of SQL statement. but queries seem to work alright.
using System;
using System.Collections.Generic;
using System.Data.OleDb;
using System.IO;
using System.Linq;
using Linq2Access.Data;
namespace Linq2Access
{
class Program
{
static readonly string AppPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
static readonly string DbPath = Path.Combine(AppPath, "Data", "database.accdb");
static readonly string DbConnString = #"Provider=Microsoft.ACE.OLEDB.12.0;Data Source='" + DbPath + "';Persist Security Info=False;";
static void Main(string[] args)
{
if (!File.Exists(DbPath))
throw new Exception("Database file does not exist!");
using (OleDbConnection connection = new OleDbConnection(DbConnString))
using (DataRepositoryDataContext db = new DataRepositoryDataContext(connection))
{
List<dbProject> projects = new List<dbProject>();
for (int i = 1; i <= 10; i++)
{
dbProject p = new dbProject() { Title = "Project #" + i };
for (int j = 1; j <= 10; j++)
{
dbTask t = new dbTask() { Title = "Task #" + (i * j) };
p.dbTasks.Add(t);
}
projects.Add(p);
}
try
{
//This will fail to submit
db.dbProjects.InsertAllOnSubmit(projects);
db.SubmitChanges();
Console.WriteLine("Write succeeded! {0} projects, {1} tasks inserted",
projects.Count,
projects.Sum(x => x.dbTasks.Count));
}
catch(Exception ex)
{
Console.WriteLine("Write FAILED. Details:");
Console.WriteLine(ex);
Console.WriteLine();
}
try
{
//However, if you create the items manually in Access they seem to query fine
var projectsFromDb = db.dbProjects.Where(x => x.Title.Contains("#1"))
.OrderBy(x => x.ProjectID)
.ToList();
Console.WriteLine("Query succeeded! {0} Projects, {1} Tasks",
projectsFromDb.Count,
projectsFromDb.Sum(x => x.dbTasks.Count));
}
catch (Exception ex)
{
Console.WriteLine("Query FAILED. Details:");
Console.WriteLine(ex);
Console.WriteLine();
}
Console.WriteLine();
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
}
}
}
You can use a DataSet. There are linq extensions that will allow you to query the data with all that LINQ goodness we have become use to :)
eICATDataSet.ICSWSbuDataTable tbl = new eICATDataSet.ICSWSbuDataTable();
ICSWSbuTableAdapter ta = new ICSWSbuTableAdapter();
ta.Fill(tbl);
var res = tbl.Select(x => x.ProcedureDate.Year == 2010);
I have seen this question a lot and in several fora. I made a go at it and here is a complete answer for those who have been looking at it.
LinQ was not made for Access. However, many of the queries will work with Access, including delete procedure. So, according to me, there are only 2 crucial deficiencies when working with Access, which are:
not being able to save data.
not being able to drag and drop objects onto the dbml
Insert will fail with the error "missing semicolon (;)". This is because LinQ save procedure was made to save data and retrieve the primary key ID of the record saved in one go. We know that you cannot execute multiple SQL statements in Access, so that is the reason for that failure.
Update will fail with the error "record not found". An update procedure will of cause look for the record to be updated then update it. I cannot tell why it wouldn't find it, when normal LinQ query to find a record works fine.
Because there is so much benefit to use LinQ, I figured out how to work around the deficiency, while enjoy the other benefits throughout my application. This is how (NB: My codes are in VB.net, but you can convert if required):
Create the LinQ to SQL (.dbml) class to manage your LinQ against the access database, and a way to manager your save procedure. Below is the full procedures of what I created and I now work with LinQ to Access without any problems:
Add a DataGridView on a form. Add buttons for Add, Edit & Delete
Code to fill the grid:
Private Sub ResetForm()
Try
Using db As New AccessDataClassesDataContext(ACCCon)
Dim rows = (From row In db.AccountTypes
Where row.AccountTypeID > 1
Order By row.AccountTypeID Ascending
Select row).ToList()
Me.DataGridView1.DataSource = rows
End Using
Catch ex As Exception
MessageBox.Show("Error: " & vbCr & ex.ToString, "Data Error", MessageBoxButtons.OK)
End Try
End Sub
DetailForm
Code to set control values
Private Sub ResetForm()
Try
If _accountTypeID = 0 Then
Exit Sub
End If
Using db As New AccessDataClassesDataContext(ACCCon)
'Dim rows = (From row In db.AccountTypes
' Where row.AccountTypeID = _accountTypeID
' Order By row.AccountTypeID Ascending
' Select row.AccountTypeID, row.AccountType, row.LastUpdated).ToList()
Dim rows = (From row In db.AccountTypes
Where row.AccountTypeID = _accountTypeID
Select row).ToList()
For Each s In rows
Me.AccountTypeIDTextBox.Text = s.AccountTypeID
Me.myGuidTextBox.Text = s.myGuid
Me.AccountTypeTextBox.Text = s.AccountType
Me.AcHeadIDTextBox.Text = s.AcHeadID
Me.DescriptionTextBox.Text = s.Description
Me.LastUpdatedDateTimePicker.Value = s.LastUpdated
Next
End Using
Catch ex As Exception
End Try
End Sub
LinQToSQLClass
You will have to add the data objects to the dbml manually since you cannot drag and drop when using Access. Also note that you will have to set all the properties of the fields correctly in the properties windows. Several properties are not set when you add the fields.
Code to Save
Public Function SaveAccountType(Optional ByVal type As String =
"Close") As Boolean
Dim success As Boolean = False
Dim row As New AccountType
Using db As New AccessDataClassesDataContext(ACCCon)
If _accountTypeID > 0 Then
row = (From r In db.AccountTypes
Where r.AccountTypeID = _accountTypeID).ToList()(0)
If String.IsNullOrEmpty(row.AccountTypeID) Then
MessageBox.Show("Requested record not found", "Update Customer Error")
Return success
End If
End If
Try
With row
.myGuid = Me.myGuidTextBox.Text
.AccountType = Me.AccountTypeTextBox.Text
.Description = Me.DescriptionTextBox.Text
.AcHeadID = Me.AcHeadIDTextBox.Text
.LastUpdated = Date.Parse(Date.Now())
End With
If _accountTypeID = 0 Then db.AccountTypes.InsertOnSubmit(row)
db.SubmitChanges()
success = True
Catch ex As Exception
MessageBox.Show("Error saving to Customer: " & vbCr & ex.ToString, "Save Data Error")
End Try
End Using
Return success
End Function
Now replace these two lines:
If _accountTypeID = 0 Then db.AccountTypes.InsertOnSubmit(row)
db.SubmitChanges()
with something like this:
Dim cmd As IDbCommand
cmd = Me.Connection.CreateCommand()
cmd.Transaction = Me.Transaction
cmd.CommandText = query
If myGuid.Trim.Length < 36 Then myGuid = UCase(System.Guid.NewGuid.ToString())
cmd.Parameters.Add(New OleDbParameter("myGuid", row.myGuid))
cmd.Parameters.Add(New OleDbParameter("AccountType", row.AccountType))
cmd.Parameters.Add(New OleDbParameter("Description", row.Description))
cmd.Parameters.Add(New OleDbParameter("AcHeadID", row.AcHeadID))
cmd.Parameters.Add(New OleDbParameter("LastUpdated", Date.Now))
If AccountTypeID > 0 Then cmd.Parameters.Add(New OleDbParameter("AccountTypeID", row.AccountTypeID))
If Connection.State = ConnectionState.Closed Then Connection.Open()
result = cmd.ExecuteNonQuery()
cmd = Me.Connection.CreateCommand()
cmd.Transaction = Me.Transaction
cmd.CommandText = "SELECT ##IDENTITY"
result = Convert.ToInt32(cmd.ExecuteScalar())
The last part of the code above is what gets you the ID of the record saved. Personally, I usually make that an option, because I don't need it in most of the cases, so I don't need to add that overhead of fetching back data every time a record is saved, I am happy just to know a record was saved.
That is the overhead added to LinQ, which causes Insert to fail with Access. Is it really necessary to have it? I don't think so.
You may have noted that I normally put my Update and Insert procedures together, so that saves me time and has address both the Insert & Update procedures in one go.
Code for Delete:
Private Sub DelButton_Click(sender As Object, e As EventArgs) Handles DelButton.Click
Using db As New AccessDataClassesDataContext(ACCCon)
Dim AccountTypeID As Integer = Me.DataGridView1.CurrentRow.Cells(0).Value
Dim row = From r In db.AccountTypes Where r.AccountTypeID = AccountTypeID
For Each detail In row
db.AccountTypes.DeleteOnSubmit(detail)
Next
Try
db.SubmitChanges()
Catch ex As Exception
' Provide for exceptions.
MsgBox(ex)
End Try
End Using
End Sub
Now you can enjoy LinQ to Access! Happy coding :)
LINQ to SQL only works for SQL Server databases. What you need is the Microsoft Entity Framework. This makes object oriented access to your mdb. From this you can run LINQ queries.
http://msdn.microsoft.com/en-us/library/aa697427(vs.80).aspx