DataAdapter.Update Command Failed To Update Database

C

Chris Lee

I am using the following code below to update a simple Access database
with historical stock prices:

-------------------code
snippet----------------------------------------
'Open connection to update data
Dim myConn As New OleDbConnection(ConnectString)
myConn.Open()

'Create the SQL queries
Dim SQLSecurity As String = "SELECT * FROM [Security]"
Dim SQLHistory As String = "SELECT * FROM [History]"

'Create new DataAdapter objects
Dim SecDataAdapter As New OleDbDataAdapter(SQLSecurity, myConn)
Dim HistDataAdapter As New OleDbDataAdapter(SQLHistory, myConn)

'Create new datasets
Dim SecDataSet As New DataSet()
Dim HistDataSet As New DataSet()

'Fill datasets to store existing data
SecDataAdapter.Fill(SecDataSet, "Security")
HistDataAdapter.Fill(HistDataSet, "History")

'Create command builders which is necessary to update the database
Dim SecCommandBuilder As OleDbCommandBuilder = New
OleDbCommandBuilder _(SecDataAdapter)
Dim HistCommandBuilder As OleDbCommandBuilder = New
OleDbCommandBuilder _(HistDataAdapter)

For i = 0 To SecDataSet.Tables("Security").Rows.Count - 1
'Get the current datarow from DataTable
Dim row As DataRow = SecDataSet.Tables("Security").Rows(i)

'Construct a valid security ticker
Dim strSecurity As String = row("Ticker").ToString

'Calling a function that will return historical data for a
security
Dim oData As Object = GetHistoricalData(strSecurity)

'Add historical data to DataSet
For j = 0 To oData.GetUpperBound(0)
Dim NewRow As DataRow = HistDataSet.Tables("History").NewRow
NewRow("Ticker") = row("Ticker").ToString
NewRow("Date") = Convert.ToDateTime(oData(j, 0))
NewRow("Price") = Convert.ToSingle(oData(j, 1))
HistDataSet.Tables("History").Rows.Add(NewRow)
Next

HistDataSet.Tables("History").AcceptChanges()
Dim count As Integer = oData.GetUpperBound(0)
row("LastUpdate") = Convert.ToDateTime(oData(count, 0))
row("DataCount") = Convert.ToInt32(count)
SecDataSet.Tables("Security").AcceptChanges()

'Update database with the updated information
SecDataAdapter.Update(SecDataSet, "Security")
HistDataAdapter.Update(HistDataSet, "History")
Next

'Display results
Dim StatusForm As frmStatus
StatusForm = New frmStatus()
StatusForm.MdiParent = Me
StatusForm.DataGrid1.DataSource = SecDataSet.DefaultViewManager
StatusForm.Show()

'Close connection
myConn.Close()
-------------------------code
ends------------------------------------------

The code failed to update the Access database with the data. I did not
encounter any run-time error while executing the code. What surprises
me is that the StatusForm->DataGrid1 shows that the SetDataSet
contained the historical data.

Could someone please shed some light how I may rectify the problem?

Thanks
Chris
 
S

Scott M.

You created the CommandBuilder objects but never generated the Update
commands necessary:

'Create command builders which is necessary to update the database
Dim SecCommandBuilder As OleDbCommandBuilder = New
OleDbCommandBuilder _(SecDataAdapter)

SeccommandBuilder.GetUpdateCommand()

Dim HistCommandBuilder As OleDbCommandBuilder = New
OleDbCommandBuilder _(HistDataAdapter)

HistCommandBuilder.GetUpdateCommand

Chris Lee said:
I am using the following code below to update a simple Access database
with historical stock prices:

-------------------code
snippet----------------------------------------
'Open connection to update data
Dim myConn As New OleDbConnection(ConnectString)
myConn.Open()

'Create the SQL queries
Dim SQLSecurity As String = "SELECT * FROM [Security]"
Dim SQLHistory As String = "SELECT * FROM [History]"

'Create new DataAdapter objects
Dim SecDataAdapter As New OleDbDataAdapter(SQLSecurity, myConn)
Dim HistDataAdapter As New OleDbDataAdapter(SQLHistory, myConn)

'Create new datasets
Dim SecDataSet As New DataSet()
Dim HistDataSet As New DataSet()

'Fill datasets to store existing data
SecDataAdapter.Fill(SecDataSet, "Security")
HistDataAdapter.Fill(HistDataSet, "History")

'Create command builders which is necessary to update the database
Dim SecCommandBuilder As OleDbCommandBuilder = New
OleDbCommandBuilder _(SecDataAdapter)
Dim HistCommandBuilder As OleDbCommandBuilder = New
OleDbCommandBuilder _(HistDataAdapter)

For i = 0 To SecDataSet.Tables("Security").Rows.Count - 1
'Get the current datarow from DataTable
Dim row As DataRow = SecDataSet.Tables("Security").Rows(i)

'Construct a valid security ticker
Dim strSecurity As String = row("Ticker").ToString

'Calling a function that will return historical data for a
security
Dim oData As Object = GetHistoricalData(strSecurity)

'Add historical data to DataSet
For j = 0 To oData.GetUpperBound(0)
Dim NewRow As DataRow = HistDataSet.Tables("History").NewRow
NewRow("Ticker") = row("Ticker").ToString
NewRow("Date") = Convert.ToDateTime(oData(j, 0))
NewRow("Price") = Convert.ToSingle(oData(j, 1))
HistDataSet.Tables("History").Rows.Add(NewRow)
Next

HistDataSet.Tables("History").AcceptChanges()
Dim count As Integer = oData.GetUpperBound(0)
row("LastUpdate") = Convert.ToDateTime(oData(count, 0))
row("DataCount") = Convert.ToInt32(count)
SecDataSet.Tables("Security").AcceptChanges()

'Update database with the updated information
SecDataAdapter.Update(SecDataSet, "Security")
HistDataAdapter.Update(HistDataSet, "History")
Next

'Display results
Dim StatusForm As frmStatus
StatusForm = New frmStatus()
StatusForm.MdiParent = Me
StatusForm.DataGrid1.DataSource = SecDataSet.DefaultViewManager
StatusForm.Show()

'Close connection
myConn.Close()
-------------------------code
ends------------------------------------------

The code failed to update the Access database with the data. I did not
encounter any run-time error while executing the code. What surprises
me is that the StatusForm->DataGrid1 shows that the SetDataSet
contained the historical data.

Could someone please shed some light how I may rectify the problem?

Thanks
Chris
 
C

Cor Ligthert

Chris,

Almost forever the same problem.
HistDataSet.Tables("History").AcceptChanges()

' This means accept the changes as if they where already updated and set the
changed, updated and deleted rowstates to updated..
Dim count As Integer = oData.GetUpperBound(0)
row("LastUpdate") = Convert.ToDateTime(oData(count, 0))
row("DataCount") = Convert.ToInt32(count)
SecDataSet.Tables("Security").AcceptChanges()

' The same as above.
So try it with just removing those two.
'Update database with the updated information
SecDataAdapter.Update(SecDataSet, "Security")
HistDataAdapter.Update(HistDataSet, "History")
Next

The DataAdapter.update has a build in acceptchanges for a dataset.

I hope this helps?

Cor
 
W

William \(Bill\) Vaughn

Huh? You don't have to call the CB to manually create any of the action
commands. Once you link the CB to the DA, it's done automatically behind the
scenes. See my article on the command builder FMI.
http://www.betav.com/msdn_magazine.htm

--
____________________________________
William (Bill) Vaughn
Author, Mentor, Consultant
Microsoft MVP
www.betav.com
Please reply only to the newsgroup so that others can benefit.
This posting is provided "AS IS" with no warranties, and confers no rights.
__________________________________

Scott M. said:
You created the CommandBuilder objects but never generated the Update
commands necessary:

'Create command builders which is necessary to update the database
Dim SecCommandBuilder As OleDbCommandBuilder = New
OleDbCommandBuilder _(SecDataAdapter)

SeccommandBuilder.GetUpdateCommand()

Dim HistCommandBuilder As OleDbCommandBuilder = New
OleDbCommandBuilder _(HistDataAdapter)

HistCommandBuilder.GetUpdateCommand

Chris Lee said:
I am using the following code below to update a simple Access database
with historical stock prices:

-------------------code
snippet----------------------------------------
'Open connection to update data
Dim myConn As New OleDbConnection(ConnectString)
myConn.Open()

'Create the SQL queries
Dim SQLSecurity As String = "SELECT * FROM [Security]"
Dim SQLHistory As String = "SELECT * FROM [History]"

'Create new DataAdapter objects
Dim SecDataAdapter As New OleDbDataAdapter(SQLSecurity, myConn)
Dim HistDataAdapter As New OleDbDataAdapter(SQLHistory, myConn)

'Create new datasets
Dim SecDataSet As New DataSet()
Dim HistDataSet As New DataSet()

'Fill datasets to store existing data
SecDataAdapter.Fill(SecDataSet, "Security")
HistDataAdapter.Fill(HistDataSet, "History")

'Create command builders which is necessary to update the database
Dim SecCommandBuilder As OleDbCommandBuilder = New
OleDbCommandBuilder _(SecDataAdapter)
Dim HistCommandBuilder As OleDbCommandBuilder = New
OleDbCommandBuilder _(HistDataAdapter)

For i = 0 To SecDataSet.Tables("Security").Rows.Count - 1
'Get the current datarow from DataTable
Dim row As DataRow = SecDataSet.Tables("Security").Rows(i)

'Construct a valid security ticker
Dim strSecurity As String = row("Ticker").ToString

'Calling a function that will return historical data for a
security
Dim oData As Object = GetHistoricalData(strSecurity)

'Add historical data to DataSet
For j = 0 To oData.GetUpperBound(0)
Dim NewRow As DataRow = HistDataSet.Tables("History").NewRow
NewRow("Ticker") = row("Ticker").ToString
NewRow("Date") = Convert.ToDateTime(oData(j, 0))
NewRow("Price") = Convert.ToSingle(oData(j, 1))
HistDataSet.Tables("History").Rows.Add(NewRow)
Next

HistDataSet.Tables("History").AcceptChanges()
Dim count As Integer = oData.GetUpperBound(0)
row("LastUpdate") = Convert.ToDateTime(oData(count, 0))
row("DataCount") = Convert.ToInt32(count)
SecDataSet.Tables("Security").AcceptChanges()

'Update database with the updated information
SecDataAdapter.Update(SecDataSet, "Security")
HistDataAdapter.Update(HistDataSet, "History")
Next

'Display results
Dim StatusForm As frmStatus
StatusForm = New frmStatus()
StatusForm.MdiParent = Me
StatusForm.DataGrid1.DataSource = SecDataSet.DefaultViewManager
StatusForm.Show()

'Close connection
myConn.Close()
-------------------------code
ends------------------------------------------

The code failed to update the Access database with the data. I did not
encounter any run-time error while executing the code. What surprises
me is that the StatusForm->DataGrid1 shows that the SetDataSet
contained the historical data.

Could someone please shed some light how I may rectify the problem?

Thanks
Chris
 
S

Scott M.

That's good information to know but not documented in the materials I've
read (MS Press & WROX titles). In those texts, they show the commandbuilder
using GetInsertCommand, GetUpdateCommand & GetDeleteCommand.


William (Bill) Vaughn said:
Huh? You don't have to call the CB to manually create any of the action
commands. Once you link the CB to the DA, it's done automatically behind the
scenes. See my article on the command builder FMI.
http://www.betav.com/msdn_magazine.htm

--
____________________________________
William (Bill) Vaughn
Author, Mentor, Consultant
Microsoft MVP
www.betav.com
Please reply only to the newsgroup so that others can benefit.
This posting is provided "AS IS" with no warranties, and confers no rights.
__________________________________

Scott M. said:
You created the CommandBuilder objects but never generated the Update
commands necessary:

'Create command builders which is necessary to update the database
Dim SecCommandBuilder As OleDbCommandBuilder = New
OleDbCommandBuilder _(SecDataAdapter)

SeccommandBuilder.GetUpdateCommand()

Dim HistCommandBuilder As OleDbCommandBuilder = New
OleDbCommandBuilder _(HistDataAdapter)

HistCommandBuilder.GetUpdateCommand

Chris Lee said:
I am using the following code below to update a simple Access database
with historical stock prices:

-------------------code
snippet----------------------------------------
'Open connection to update data
Dim myConn As New OleDbConnection(ConnectString)
myConn.Open()

'Create the SQL queries
Dim SQLSecurity As String = "SELECT * FROM [Security]"
Dim SQLHistory As String = "SELECT * FROM [History]"

'Create new DataAdapter objects
Dim SecDataAdapter As New OleDbDataAdapter(SQLSecurity, myConn)
Dim HistDataAdapter As New OleDbDataAdapter(SQLHistory, myConn)

'Create new datasets
Dim SecDataSet As New DataSet()
Dim HistDataSet As New DataSet()

'Fill datasets to store existing data
SecDataAdapter.Fill(SecDataSet, "Security")
HistDataAdapter.Fill(HistDataSet, "History")

'Create command builders which is necessary to update the database
Dim SecCommandBuilder As OleDbCommandBuilder = New
OleDbCommandBuilder _(SecDataAdapter)
Dim HistCommandBuilder As OleDbCommandBuilder = New
OleDbCommandBuilder _(HistDataAdapter)

For i = 0 To SecDataSet.Tables("Security").Rows.Count - 1
'Get the current datarow from DataTable
Dim row As DataRow = SecDataSet.Tables("Security").Rows(i)

'Construct a valid security ticker
Dim strSecurity As String = row("Ticker").ToString

'Calling a function that will return historical data for a
security
Dim oData As Object = GetHistoricalData(strSecurity)

'Add historical data to DataSet
For j = 0 To oData.GetUpperBound(0)
Dim NewRow As DataRow = HistDataSet.Tables("History").NewRow
NewRow("Ticker") = row("Ticker").ToString
NewRow("Date") = Convert.ToDateTime(oData(j, 0))
NewRow("Price") = Convert.ToSingle(oData(j, 1))
HistDataSet.Tables("History").Rows.Add(NewRow)
Next

HistDataSet.Tables("History").AcceptChanges()
Dim count As Integer = oData.GetUpperBound(0)
row("LastUpdate") = Convert.ToDateTime(oData(count, 0))
row("DataCount") = Convert.ToInt32(count)
SecDataSet.Tables("Security").AcceptChanges()

'Update database with the updated information
SecDataAdapter.Update(SecDataSet, "Security")
HistDataAdapter.Update(HistDataSet, "History")
Next

'Display results
Dim StatusForm As frmStatus
StatusForm = New frmStatus()
StatusForm.MdiParent = Me
StatusForm.DataGrid1.DataSource = SecDataSet.DefaultViewManager
StatusForm.Show()

'Close connection
myConn.Close()
-------------------------code
ends------------------------------------------

The code failed to update the Access database with the data. I did not
encounter any run-time error while executing the code. What surprises
me is that the StatusForm->DataGrid1 shows that the SetDataSet
contained the historical data.

Could someone please shed some light how I may rectify the problem?

Thanks
Chris
 
W

William Ryan eMVP

Watch where you put those acceptchanges commands. Right before calling
update, add a debug.Assert(dataSet.HasChanges) for each update and verify
that there are changes to update. That looks like the culprit.

--
W.G. Ryan MVP Windows - Embedded

Have an opinion on the effectiveness of Microsoft Embedded newsgroups?
Let Microsoft know!
https://www.windowsembeddedeval.com/community/newsgroups
Chris Lee said:
I am using the following code below to update a simple Access database
with historical stock prices:

-------------------code
snippet----------------------------------------
'Open connection to update data
Dim myConn As New OleDbConnection(ConnectString)
myConn.Open()

'Create the SQL queries
Dim SQLSecurity As String = "SELECT * FROM [Security]"
Dim SQLHistory As String = "SELECT * FROM [History]"

'Create new DataAdapter objects
Dim SecDataAdapter As New OleDbDataAdapter(SQLSecurity, myConn)
Dim HistDataAdapter As New OleDbDataAdapter(SQLHistory, myConn)

'Create new datasets
Dim SecDataSet As New DataSet()
Dim HistDataSet As New DataSet()

'Fill datasets to store existing data
SecDataAdapter.Fill(SecDataSet, "Security")
HistDataAdapter.Fill(HistDataSet, "History")

'Create command builders which is necessary to update the database
Dim SecCommandBuilder As OleDbCommandBuilder = New
OleDbCommandBuilder _(SecDataAdapter)
Dim HistCommandBuilder As OleDbCommandBuilder = New
OleDbCommandBuilder _(HistDataAdapter)

For i = 0 To SecDataSet.Tables("Security").Rows.Count - 1
'Get the current datarow from DataTable
Dim row As DataRow = SecDataSet.Tables("Security").Rows(i)

'Construct a valid security ticker
Dim strSecurity As String = row("Ticker").ToString

'Calling a function that will return historical data for a
security
Dim oData As Object = GetHistoricalData(strSecurity)

'Add historical data to DataSet
For j = 0 To oData.GetUpperBound(0)
Dim NewRow As DataRow = HistDataSet.Tables("History").NewRow
NewRow("Ticker") = row("Ticker").ToString
NewRow("Date") = Convert.ToDateTime(oData(j, 0))
NewRow("Price") = Convert.ToSingle(oData(j, 1))
HistDataSet.Tables("History").Rows.Add(NewRow)
Next

HistDataSet.Tables("History").AcceptChanges()
Dim count As Integer = oData.GetUpperBound(0)
row("LastUpdate") = Convert.ToDateTime(oData(count, 0))
row("DataCount") = Convert.ToInt32(count)
SecDataSet.Tables("Security").AcceptChanges()

'Update database with the updated information
SecDataAdapter.Update(SecDataSet, "Security")
HistDataAdapter.Update(HistDataSet, "History")
Next

'Display results
Dim StatusForm As frmStatus
StatusForm = New frmStatus()
StatusForm.MdiParent = Me
StatusForm.DataGrid1.DataSource = SecDataSet.DefaultViewManager
StatusForm.Show()

'Close connection
myConn.Close()
-------------------------code
ends------------------------------------------

The code failed to update the Access database with the data. I did not
encounter any run-time error while executing the code. What surprises
me is that the StatusForm->DataGrid1 shows that the SetDataSet
contained the historical data.

Could someone please shed some light how I may rectify the problem?

Thanks
Chris
 
W

William \(Bill\) Vaughn

I can't comment on other authors, but I've written a lot of code that
demonstrates or uses the CB and it just does not work that way. All you need
to do is register it with the DA. Once it's setup when Update fires, it
checks to see if there's a CB present. If there is, it generates the
commands (the first time) and then executes the generated code. I also
demonstrated what's being generated for debug purposes by using the "Get"
commands--but that's all they're needed for.

hth

--
____________________________________
William (Bill) Vaughn
Author, Mentor, Consultant
Microsoft MVP
www.betav.com
Please reply only to the newsgroup so that others can benefit.
This posting is provided "AS IS" with no warranties, and confers no rights.
__________________________________

Scott M. said:
That's good information to know but not documented in the materials I've
read (MS Press & WROX titles). In those texts, they show the commandbuilder
using GetInsertCommand, GetUpdateCommand & GetDeleteCommand.


William (Bill) Vaughn said:
Huh? You don't have to call the CB to manually create any of the action
commands. Once you link the CB to the DA, it's done automatically behind the
scenes. See my article on the command builder FMI.
http://www.betav.com/msdn_magazine.htm

--
____________________________________
William (Bill) Vaughn
Author, Mentor, Consultant
Microsoft MVP
www.betav.com
Please reply only to the newsgroup so that others can benefit.
This posting is provided "AS IS" with no warranties, and confers no rights.
__________________________________

Scott M. said:
You created the CommandBuilder objects but never generated the Update
commands necessary:

'Create command builders which is necessary to update the database
Dim SecCommandBuilder As OleDbCommandBuilder = New
OleDbCommandBuilder _(SecDataAdapter)

SeccommandBuilder.GetUpdateCommand()

Dim HistCommandBuilder As OleDbCommandBuilder = New
OleDbCommandBuilder _(HistDataAdapter)

HistCommandBuilder.GetUpdateCommand

I am using the following code below to update a simple Access database
with historical stock prices:

-------------------code
snippet----------------------------------------
'Open connection to update data
Dim myConn As New OleDbConnection(ConnectString)
myConn.Open()

'Create the SQL queries
Dim SQLSecurity As String = "SELECT * FROM [Security]"
Dim SQLHistory As String = "SELECT * FROM [History]"

'Create new DataAdapter objects
Dim SecDataAdapter As New OleDbDataAdapter(SQLSecurity, myConn)
Dim HistDataAdapter As New OleDbDataAdapter(SQLHistory, myConn)

'Create new datasets
Dim SecDataSet As New DataSet()
Dim HistDataSet As New DataSet()

'Fill datasets to store existing data
SecDataAdapter.Fill(SecDataSet, "Security")
HistDataAdapter.Fill(HistDataSet, "History")

'Create command builders which is necessary to update the database
Dim SecCommandBuilder As OleDbCommandBuilder = New
OleDbCommandBuilder _(SecDataAdapter)
Dim HistCommandBuilder As OleDbCommandBuilder = New
OleDbCommandBuilder _(HistDataAdapter)

For i = 0 To SecDataSet.Tables("Security").Rows.Count - 1
'Get the current datarow from DataTable
Dim row As DataRow = SecDataSet.Tables("Security").Rows(i)

'Construct a valid security ticker
Dim strSecurity As String = row("Ticker").ToString

'Calling a function that will return historical data for a
security
Dim oData As Object = GetHistoricalData(strSecurity)

'Add historical data to DataSet
For j = 0 To oData.GetUpperBound(0)
Dim NewRow As DataRow = HistDataSet.Tables("History").NewRow
NewRow("Ticker") = row("Ticker").ToString
NewRow("Date") = Convert.ToDateTime(oData(j, 0))
NewRow("Price") = Convert.ToSingle(oData(j, 1))
HistDataSet.Tables("History").Rows.Add(NewRow)
Next

HistDataSet.Tables("History").AcceptChanges()
Dim count As Integer = oData.GetUpperBound(0)
row("LastUpdate") = Convert.ToDateTime(oData(count, 0))
row("DataCount") = Convert.ToInt32(count)
SecDataSet.Tables("Security").AcceptChanges()

'Update database with the updated information
SecDataAdapter.Update(SecDataSet, "Security")
HistDataAdapter.Update(HistDataSet, "History")
Next

'Display results
Dim StatusForm As frmStatus
StatusForm = New frmStatus()
StatusForm.MdiParent = Me
StatusForm.DataGrid1.DataSource = SecDataSet.DefaultViewManager
StatusForm.Show()

'Close connection
myConn.Close()
-------------------------code
ends------------------------------------------

The code failed to update the Access database with the data. I did not
encounter any run-time error while executing the code. What surprises
me is that the StatusForm->DataGrid1 shows that the SetDataSet
contained the historical data.

Could someone please shed some light how I may rectify the problem?

Thanks
Chris
 
C

Chris Lee

Thanks for the advice. I have removed the dataset AcceptChanges
command just prior to the dataadapter Update command. Now, I am
getting an error when executing the Update command:

Unhandled exception of type System.InvalidOperation.Exception occurred
in system.dll. Additional information: Dynamic SQL generation for
UpdateCommand is not supported against a SelectCommand that does not
return any key column information.

Does that mean that the error occurred because I did not identify the
PrimaryKey? I left out the PrimaryKey because none of the columns in
the datatable are unique.
 
C

Cor Ligthert

Chris,

You are right, and than put this as well before your fills and maybe it will
be gone.

da.MissingSchemaAction = MissingSchemaAction.AddWithKey

I hope this helps?

Cor
 
M

Miha Markic [MVP C#]

Chris,

Chris Lee said:
Thanks for the advice. I have removed the dataset AcceptChanges
command just prior to the dataadapter Update command. Now, I am
getting an error when executing the Update command:

Unhandled exception of type System.InvalidOperation.Exception occurred
in system.dll. Additional information: Dynamic SQL generation for
UpdateCommand is not supported against a SelectCommand that does not
return any key column information.

Does that mean that the error occurred because I did not identify the
PrimaryKey? I left out the PrimaryKey because none of the columns in
the datatable are unique.

If you want commandbuilder to work with updates, the table has to have a
primary key.
Otherwise, do create Insert/Update/Delete commands by yourself (even better
than using command builder).
 
W

William \(Bill\) Vaughn

And... (on top of the other good advice) make sure to add Try/Catch
exception handlers to deal with the stuff that happens.

--
____________________________________
William (Bill) Vaughn
Author, Mentor, Consultant
Microsoft MVP
www.betav.com
Please reply only to the newsgroup so that others can benefit.
This posting is provided "AS IS" with no warranties, and confers no rights.
__________________________________
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top