I'm running stored procedure from asp.net front-end but it's quite long. What is the easy way on running that thing in the background? I mean if I close the browser, I still want my stored procedure to complete not just die. Also, I would like to perform other actions on the front-end while my procedure is running. Any good solution for that?
Both SQL Agent and the Service Broker can do this, though it does take some work on your part.
Just launch it in another Thread as so:
'Starts execution of the proc
Protected Sub Button1_Click(sender As Object, e As System.EventArgs) Handles Button1.Click
Dim t As New Threading.Thread(AddressOf DoWork)
t.Start()
End Sub
Private Sub DoWork()
Using c As New SqlConnection(ConfigurationManager.ConnectionStrings("ConnectionString").ConnectionString)
c.Open()
Dim command = New SqlCommand("LongTest", c)
command.CommandType=Data.CommandType.StoredProcedure
command.CommandTimeout = 0
command.ExecuteNonQuery()
End Using
End Sub
Here's the sp that I used for my test:
create PROCEDURE dbo.LongTest
AS
BEGIN
WaitFor Delay '00:00:30' --wait for 30 seconds before doing anything
insert into TableImageTest(image)
values(null)
END
Use "Asynchronous Processing=true" in your connection string and call the sproc asynchronously as in this link.
http://geekswithblogs.net/frankw/archive/2008/07/05/execute-transact-sql-statement-asynchronously.aspx
Related
In short, remaining in the HTTP context, I would like the user, after clicking on an order completion button, not to wait for the mails to be sent before being sent back to a "thak you page".
I saw that HostingEnvironment.QueueBackgroundWorkItem could help me with this but there is always the risk of it being killed by IIS recycling.
What is the best solution to do this?
I know the best would be to develop a separate console solution but it wouldn't be worth it for 3/4 emails, alternatively I could consider speeding it up by making them asynchronous?
Protected Sub btnConcludiOrdine_Click(sender As Object, e As System.EventArgs) Handles btnConcludiOrdine.Click
If IsValidOrder(Me._cart, msg) Then
If Me._cart.SaveOrder(Me._user, Me._orderCode, Me._lang) then
'Update quantity in db
Dim mail As New EmailBLL
mail.SendOrderNotice(Me._cart, Me._lang) '2 Mails
mail.SendProductNotice() '2 Mails
End If
Else
Response.Redirect("*Error URL*")
End If
End Sub
The way you approach this is as suggested – start a task, or so called new processor thread.
So, what you would do is break out the code – this even works if code behind is for a web form.
So, the first step is to move out the “slow” parts, or the parts we want to run separate.
The main issue is that to start/launch/want/desire/achieve a brand new processor thread?
The sub call CAN ONLY PASS ONE “parameter” and the sub can only accept one parameter!!!!!
I note in your case that routine needs two values.
However, that “one parameter” can be a array of “many” values, or even a collection or whatever. In our case we pass the two values.
So just keep in mind that what you call can NOT update or “use” the values of controls on the form – the instance of that form will go out of scope.
But we can of course PASS the values you need. This will allow that routine to run 100% independent of the web form.
I also VERY strong suggest that if you DO place the sub in the same web page code behind? You should/can mark that sub as shared. Doing so will allow the compiler to get mad at you and spit out errors if that routine say tries to use or update a control value on the form.
However, it is MUCH better is to place this sub in a separate standard code module out side of the web forms code behind.
Regardless of above, we can now re-write the code we have as this:
If Me._cart.SaveOrder(Me._user, Me._orderCode, Me._lang) then
Dim myInfo(1) as object
myInfo(0) = me.cart
myInfo(1) = me_._lng
Call MyUpdateQ(myInfo)
End If
' bla bla lba
Shared Sub MyUPdateQ(p() as object)
'Update quantity in db
Dim mail As New EmailBLL
mail.SendOrderNotice(p(0),p(1)
mail.SendProductNotice() '2 Mails
End Sub
Ok, so far, we not achieved much, but we re-writing to accept the ONE array is KEY here.
So, now now make sure the above runs/works and is all happy.
Now, because we moved out the "work load" to that one routine, it is now a simple matter to start a thread.
Now, Our above code becomes this:
Protected Sub btnConcludiOrdine_Click(sender As Object, e As System.EventArgs) Handles btnConcludiOrdine.Click
If IsValidOrder(Me._cart, msg) Then
If Me._cart.SaveOrder(Me._user, Me._orderCode, Me._lang) then
Dim myInfo(1) as object
myInfo(0) = me.cart
myInfo(1) = me_._lng
Dim MyThread As New Thread(New ParameterizedThreadStart(AddressOf MyUpdateQ))
MyThread.Start(myInfo)
End If
Else
Response.Redirect("*Error URL*")
End If
End Sub
Shared Sub MyUPdateQ(p() as object)
'Update quantity in db
Dim mail As New EmailBLL
mail.SendOrderNotice(p(0),p(1)
mail.SendProductNotice() '2 Mails
End Sub
That is it. Now when you click your button it will wait ZERO time, since the long running routine is now going to run 100% as a separate thread. And this will also mean that when the user clicks the button - the page will respond instant and post back to user will be done. So if that thread takes 6 seconds, or even 25 seconds, the user will not notice this delay.
Just push your sending mail logic in Task and if you are not interested in result don't await it. c# syntax
Task.Run(() => SendEmail());
I have some legacy code I've inherited and am trying to figure out whether or not this code is causing a connection to stay open to my database. I'm not too familiar with ADO.net but from what I can see this should be causing a problem.
I have a function to get a data reader:
Public Shared Function ExecuteDataReader(ByRef ObjSqlCmd As SqlCommand, ByVal CSTRING As String) As IDataReader
Try
Dim cn As New SqlConnection(CSTRING)
cn.Open()
ObjSqlCmd.Connection = cn
Return ObjSqlCmd.ExecuteReader(CommandBehavior.CloseConnection)
Catch ex As Exception
If ObjSqlCmd.Connection.State = ConnectionState.Open Then ObjSqlCmd.Connection.Close()
Return Nothing
End Try
End Function
And then code using this to read:
Dim cmd As New SqlCommand
'set up command
Dim idr As IDataReader = ExecuteDataReader(cmd, "database")
If idr Is Nothing Then
If cmd.Connection.State = ConnectionState.Open Then cmd.Connection.Close()
Return arrResult
End If
While idr.Read
'read
End While
If cmd.Connection.State = ConnectionState.Open Then cmd.Connection.Close()
I can see it closing the connection at the end (assuming nothing goes wrong and an exception is thrown) but from MSDN they say you should always clsoe the data reader. Does that get closed when the connection is closed?
Also from what I understand, the code
ObjSqlCmd.ExecuteReader(CommandBehavior.CloseConnection)
Will not close the connection unless the data reader is closed. Can someone explain what is happening here and if everything might be closing correctly? I know the best practice is to use "using" and then try/catch/finally to close the reader, the the original developers did not seem to follow these practices.
Yes it will. Any exception that occurs while reading from the data reader will bypass the closing statement. At some point, the garbage collector will kick in and dispose and release the connection back to the connection pool.
Until that point though, the connection can't be used. Worse, any locks acquired while executing the command will remain until the connection is closed.
Using ... using isn't a best practice just because. It's actually a lot simpler and safer to manage commands and connections in this way
I Open and Close ODBC connection while the page is up and running. When and where should i put the Connection.Dispose method? I tried on the Page_Disposed but it never makes it there.
Public Class _WepPage1
Inherits System.Web.UI.Page
Dim MyConnection As New Odbc.OdbcConnection
Private Sub Page_Disposed(sender As Object, e As System.EventArgs) Handles Me.Disposed
MyConnection.Dispose()
End Sub
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
'String comes from Web.Config
MyConnection.ConnectionString = ConfigurationManager.ConnectionStrings("MyConnection").ConnectionString
If Not IsPostBack Then
Call LoadSomeData()
Else
'Do some more stuff
End If
End Sub
Private Sub LoadSomeData()
If MyConnection.State = ConnectionState.Closed Then MyConnection.Open()
Dim MyCommand As New Odbc.OdbcCommand("select * from tablename", MyConnection)
Try
Dim dataR As Odbc.OdbcDataReader = MyCommand.ExecuteReader
While dataR.Read
DropDownList1.Items.Add(dataR("column1"))
End While
Catch ex As Exception
End Try
MyCommand.Dispose()
If MyConnection.State = ConnectionState.Open Then MyConnection.Close()
End Sub
I would not use the Page_Load to start the connection. In .NET, the "real" connection object returns to the pool when you Dispose(), so it is there for other work. Even if you open, close and dispose numerous times, there is but a few microseconds of overhead max, so open a connection early and then keeping it open for the entire page duration does not really help you.
In addition, opening connection in Page_Load means you have tightly coupled your data access to your UI, leaving you a lot of rearchitecture and not just rearrangement to divide the work into proper tiers.
What this means is the connection should be created, used and disposed in one method (in your example). This means it is both logically separated and easy to separate into different classes and/or assemblies, should the application grow so you need a full DAL.
I would, at minimum, move all realting to connection to the LoadSomeData() method. If you want more proper separation of concerns, make a GetSomeData() routine and have the LoadSomeData() routine call the GetSomeData() routine and shape the data so it can be easily bound, rather than consume one row at a time (In your example, instead of binding a data object to the drop down, you are pushing one item at a time).
I hope this helps and I am sorry that many of the examples out there are really bad at best practices. And, yes, this includes many samples from people who should know better. ;-)
So I handle all exceptions in my project within my Global.asax page. For example, on a random page I could have:
Protected Sub SomeFunction()
Try
'do something here
Catch ex As Exception
Throw ex
End Try
End Sub
So then the exception bubbles up to the Global.asax page where I handle it and log to the database like so:
Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)
Dim ex As Exception = Server.GetLastError
Dim loggedExceptionID As Integer = 0
Try
loggedExceptionID = New BLL.ExceptionHandler().LogException(ex)
Response.Redirect("~/ErrorPage.aspx?ID=" & loggedExceptionID, False)
Catch loggingError As Exception
'what do I do here?
End Try
End Sub
The BLL.ExceptionHandler().LogException(ex) function just writes the error details to the database.
So my question is in the Application_Error method, do I need the try/catch block when trying to write to the database? I'm thinking I would in case something goes wrong with the connection, etc. - but is this really necessary? Also, what would I do in the catch block if there is an error? At that point I would be catching the error of logging an error which is confusing in its own right.
It's probably a good idea to have a try/catch there, and what I would do is write the details to a text file or xml file so that you can refer back to both errors in the future and try to fix them. That of course means you would probably want another try/catch around writing to the disk, in which case I would just give up and call it a day. =)
On a completely separate note, you really should check out ELMAH: http://code.google.com/p/elmah/. This does probably everything you want and a lot more.
i've a huge problem.
Take a look to this sample code
private sub FUNCTION1()
dim conn as new mysqlconnection(myconnstring)
conn.open
---- do something
FUNCTION2()
----
conn.close
end sub
private sub FUNCTION2()
dim conn as new mysqlconnection(myconnstring)
....
conn.open
-- do something
conn.close
end sub
Despite i close regulary all my connection, they remains "open" on the mysql server.
I know this because i'm using MySQL Administration tool to check how many connection i open "line by line" of my source code.
In fact i get a lot of time "user has exceeded the 'max_user_connections' resource (current value: 5)"
My hoster permit ONLY 5 connection, but i think that if i write GOOD source code, this cannot be a problem.
So my question is: why that "damn" connections remain open ??
Thank you in advance!
Consider wrapping your MySqlConnection operations in a Using statement...
Any object that you instantiate within that Using ... new statement will be disposed of properly by the compiler. Once that End Using statement appears, the object goes out of scop. Any objects that are declared with in the Using block will need to be disposed of by the developer, as per normal.
Using conn As New MySqlConnection(myConnString)
Using cmd As New MySqlCommand("SELECT * FROM Employees", conn)
conn.Open()
Using rdr As MySqlDataReader = cmd.ExecuteReader()
While rdr.Read()
Console.WriteLine(rdr(0))
End While
End Using
End Using
End Using
In this case, you don't HAVE to encase your Command and Reader in their own Using, but it allows you to take advantage of the fact that they all implement IDisposable.
Try calling conn.Dispose() and then setting it to null.
On another note, are you opening a connection in Function1 and Function2 also as it appears that you're creating 2 connections. If that's the case try to implement a singleton pattern to manage your connections.