parent-child tables insertion problem for Oracle database

K

kevin

hi, MVPs

here's my question, I have a parent table in a dataset alone with its child
tables. all columns are binded to winform controls.
after adding new records into the dataset on both parent and child, well
sometimes just add records on the child tables.
all those new rocords have a temp id since I set the primary key on each
table to be autoincrement and the seed is set to -1,
how do I update the dataset into oracle database and synchronize the
parent-child table primary and foreign key columns after
the update. In sql server, there is @@Identity I can use, but oracle
database doesn't support this tag. what's the work around
for that?

Kevin
 
M

Miha Markic

Hi kevin,

AFAIK Oracle doesn't have autoincrement fields (or now it has them?) so
you'll have to insert the pk into server by using sequence or something.
The best way would be that you do this within stored procedure and return
the new value as an output parameter.

Or you could read the sequence number, update the dataset and then store it
(it should be in transaction anyway).

I am not a MVP though ;-)
 
K

kevin

Miha

yes, oracle does not have autoincrement fields and I use sequence or trigger
to select and max() + 1 for the identity field.
but return in the new key won't help since in a dataset disconnection model.


thx for the response.

Kevin
 
M

Miha Markic

Hi kevin,

kevin said:
Miha

yes, oracle does not have autoincrement fields and I use sequence or trigger
to select and max() + 1 for the identity field.
but return in the new key won't help since in a dataset disconnection
model.

That's why you could wrap your insert code in a stored procedure.
Or, first read new sequence numbers into dataset (actually into GetChanges
copy of dataset) and then update the database (all in the same transaction).
 
D

David Sceppa

Kevin,

If you pre-fetch the sequence value(s) from your Oracle
tables, this becomes less of an issue since you have your key
values as you're adding rows to your DataSet.

If you won't know the key values until you submit the batch
of new rows to the database, you can have ADO.NET generate
placeholder values and keep the parents and children in synch
using these placeholders. Then, as you submit pending parents to
the database and fetch the key values for those parents, those
values cascade down to the related child objects thanks to the
DataRelation.

A canned response that answers most questions on the topic
follows.

I hope this information proves helpful.

David Sceppa
Microsoft
This posting is provided "AS IS" with no warranties,
and confers no rights. You assume all risk for your use.
© 2003 Microsoft Corporation. All rights reserved.



This is a fairly common scenario that ADO.NET handles much
better than any of its predecessors. It will seem complex at
first, but once you've handled the scenario once, it will
hopefully feel more intuitive.


1.) How do I keep pending parent and children in synch?

Set the ADO.NET DataColumn's AutoIncrement property to True
and ADO.NET will generate placeholder values for new rows. The
new values depend on the AutoIncrementStep, AutoIncrementSeed,
and the last value used in the DataTable. I recommend setting
AutoIncrementSeed and AutoIncrementStep to -1. These settings
will generate placeholder values of -1, -2, -3, … There are two
benefits to this approach. The values won't conflict with any
that actually exist in the database. The user will not
misinterpret the placeholder value as an actual value from the
database.

As you add the parent rows and ADO.NET generates placeholder
values, use those placeholder values for your pending child rows.
The DataRelation object will make it easy to go from parent to
child and back, either in code or in bound controls.


2.) How do I fetch the new key values for the parent rows as I
submit them?

If you're using SQL Server, this process is actually very
simple. If you were writing your own queries, you would execute
an "INSERT INTO…" query to insert the new row and then execute a
"SELECT SCOPE_IDENTITY()" query to retrieve the last identity
value generated on that connection.

The DataAdapter submits changes via its InsertCommand
property. You can append ";SELECT @@IDENTITY AS MyIDColumn" to
the end of the "INSERT INTO..." query. (SQL 2000 users should
use "SELECT SCOPE_IDENTITY()..." instead of "SELECT @@IDENTITY".
See SQL Server Books OnLine for more information on why.) If
you're building your DataAdapters via Visual Studio .NET's
DataAdapter Configuration Wizard, the wizard will do this for you
automatically.

If you're writing your code by hand, make sure the
InsertCommand's UpdatedRowSource property is set to Both (the
default) or FirstReturnedRecord. This property controls whether
the DataAdapter will fetch the row returned by the query and
apply that data to the DataRow object.

This functionality is possible because SQL Server allows you
to execute a batch of queries that returns rows. However, not
all databases support this feature.

If you're working with an Access database, you'll need to go
a slightly different route. Trap for the DataAdapter's
RowUpdated event and use code to check for a successful insert.
Execute the "SELECT @@IDENTITY" query using a Command object and
assign the value returned by the query to the appropriate column
and call the DataRow object's AcceptChanges method. Your code
will look something like this:

Visual Basic .NET:
Dim da As New OleDbDataAdapter(strSQL, strConn)
Dim cn As OleDbConnection = da.SelectCommand.Connection
Dim cmdGetIdentity As New OleDbCommand("SELECT @@IDENTITY", cn)
AddHandler da.RowUpdated, AddressOf HandleRowUpdated
Dim tbl As DataTable = CreateMyDataTable()
da.Fill(tbl)
...
da.Update(tbl)

Private Sub HandleRowUpdated(ByVal sender As Object, _
ByVal e As
OleDbRowUpdatedEventArgs)
If e.Status = UpdateStatus.Continue AndAlso _
e.StatementType = StatementType.Insert Then
e.Row("OrderID") =
Int32.Parse(cmdGetIdentity.ExecuteScalar().ToString())
e.Row.AcceptChanges()
End If
End Sub

Visual C# .NET:
OleDbDataAdapter da = new OleDbDataAdapter(strSQL, strConn);
OleDbConnection cn = da.SelectCommand.Connection;
OleDbCommand cmdGetIdentity = new OleDbCommand("SELECT
@@IDENTITY", cn);
da.RowUpdated += new
OleDbRowUpdatedEventHandler(HandleRowUpdated);
DataTable tbl = CreateMyDataTable();
da.Fill(tbl);
...
da.Update(tbl);

private void HandleRowUpdated(object sender,
OleDbRowUpdatedEventArgs e)
{
if ((e.Status == UpdateStatus.Continue) &&
((e.StatementType == StatementType.Insert))
{
e.Row["OrderID"] =
Int32.Parse(cmdGetIdentity.ExecuteScalar().ToString());
e.Row.AcceptChanges();
}
}

You can use similar techniques to retrieve server-generated
values from other databases as well. MySQL developers can use
the "LAST_INSERT_ID()" instead of "@@IDENTITY" to retrieve the
last auto-increment value generated. Oracle developers can use
"SELECT SequenceName.CURRVAL FROM DUAL" to retrieve the last
value generated for a sequence on the connection.


3.) How do I cascade the new key values to the child rows before
I submit them?

This is the simplest part of the process. When you create a
DataRelation object, ADO.NET will add a ForeignKeyConstraint
object to make sure that child rows match up to a parent row.
The ForeignKeyConstraint object exposes a UpdateRule property.
If this property is set to Cascade (the default), ADO.NET will
automatically cascade changes made to the parent down to the
associated child rows.

So, if you have a DataRelation set up between the DataTables
based on the auto-increment column, and you've set the parent
DataAdapter's InsertCommand to fetch the new auto-increment
values from the database, ADO.NET will cascade the new values
down to the associated child rows automatically.


I hope this information proves helpful. For more
information, see Chapter 11 of Microsoft ADO.NET, available
through Microsoft Press.
 
K

kevin

Thanks David
but can you verify/confirm that in Oracle the SequenceName.CURRVAL is
connection
specific? I mean does the CURRVAL return the current value generated by
the current connection or the CURRVAL is absolute current value caused by
any connection?


Kevin






David Sceppa said:
Kevin,

If you pre-fetch the sequence value(s) from your Oracle
tables, this becomes less of an issue since you have your key
values as you're adding rows to your DataSet.

If you won't know the key values until you submit the batch
of new rows to the database, you can have ADO.NET generate
placeholder values and keep the parents and children in synch
using these placeholders. Then, as you submit pending parents to
the database and fetch the key values for those parents, those
values cascade down to the related child objects thanks to the
DataRelation.

A canned response that answers most questions on the topic
follows.

I hope this information proves helpful.

David Sceppa
Microsoft
This posting is provided "AS IS" with no warranties,
and confers no rights. You assume all risk for your use.
© 2003 Microsoft Corporation. All rights reserved.



This is a fairly common scenario that ADO.NET handles much
better than any of its predecessors. It will seem complex at
first, but once you've handled the scenario once, it will
hopefully feel more intuitive.


1.) How do I keep pending parent and children in synch?

Set the ADO.NET DataColumn's AutoIncrement property to True
and ADO.NET will generate placeholder values for new rows. The
new values depend on the AutoIncrementStep, AutoIncrementSeed,
and the last value used in the DataTable. I recommend setting
AutoIncrementSeed and AutoIncrementStep to -1. These settings
will generate placeholder values of -1, -2, -3, . There are two
benefits to this approach. The values won't conflict with any
that actually exist in the database. The user will not
misinterpret the placeholder value as an actual value from the
database.

As you add the parent rows and ADO.NET generates placeholder
values, use those placeholder values for your pending child rows.
The DataRelation object will make it easy to go from parent to
child and back, either in code or in bound controls.


2.) How do I fetch the new key values for the parent rows as I
submit them?

If you're using SQL Server, this process is actually very
simple. If you were writing your own queries, you would execute
an "INSERT INTO." query to insert the new row and then execute a
"SELECT SCOPE_IDENTITY()" query to retrieve the last identity
value generated on that connection.

The DataAdapter submits changes via its InsertCommand
property. You can append ";SELECT @@IDENTITY AS MyIDColumn" to
the end of the "INSERT INTO..." query. (SQL 2000 users should
use "SELECT SCOPE_IDENTITY()..." instead of "SELECT @@IDENTITY".
See SQL Server Books OnLine for more information on why.) If
you're building your DataAdapters via Visual Studio .NET's
DataAdapter Configuration Wizard, the wizard will do this for you
automatically.

If you're writing your code by hand, make sure the
InsertCommand's UpdatedRowSource property is set to Both (the
default) or FirstReturnedRecord. This property controls whether
the DataAdapter will fetch the row returned by the query and
apply that data to the DataRow object.

This functionality is possible because SQL Server allows you
to execute a batch of queries that returns rows. However, not
all databases support this feature.

If you're working with an Access database, you'll need to go
a slightly different route. Trap for the DataAdapter's
RowUpdated event and use code to check for a successful insert.
Execute the "SELECT @@IDENTITY" query using a Command object and
assign the value returned by the query to the appropriate column
and call the DataRow object's AcceptChanges method. Your code
will look something like this:

Visual Basic .NET:
Dim da As New OleDbDataAdapter(strSQL, strConn)
Dim cn As OleDbConnection = da.SelectCommand.Connection
Dim cmdGetIdentity As New OleDbCommand("SELECT @@IDENTITY", cn)
AddHandler da.RowUpdated, AddressOf HandleRowUpdated
Dim tbl As DataTable = CreateMyDataTable()
da.Fill(tbl)
...
da.Update(tbl)

Private Sub HandleRowUpdated(ByVal sender As Object, _
ByVal e As
OleDbRowUpdatedEventArgs)
If e.Status = UpdateStatus.Continue AndAlso _
e.StatementType = StatementType.Insert Then
e.Row("OrderID") =
Int32.Parse(cmdGetIdentity.ExecuteScalar().ToString())
e.Row.AcceptChanges()
End If
End Sub

Visual C# .NET:
OleDbDataAdapter da = new OleDbDataAdapter(strSQL, strConn);
OleDbConnection cn = da.SelectCommand.Connection;
OleDbCommand cmdGetIdentity = new OleDbCommand("SELECT
@@IDENTITY", cn);
da.RowUpdated += new
OleDbRowUpdatedEventHandler(HandleRowUpdated);
DataTable tbl = CreateMyDataTable();
da.Fill(tbl);
...
da.Update(tbl);

private void HandleRowUpdated(object sender,
OleDbRowUpdatedEventArgs e)
{
if ((e.Status == UpdateStatus.Continue) &&
((e.StatementType == StatementType.Insert))
{
e.Row["OrderID"] =
Int32.Parse(cmdGetIdentity.ExecuteScalar().ToString());
e.Row.AcceptChanges();
}
}

You can use similar techniques to retrieve server-generated
values from other databases as well. MySQL developers can use
the "LAST_INSERT_ID()" instead of "@@IDENTITY" to retrieve the
last auto-increment value generated. Oracle developers can use
"SELECT SequenceName.CURRVAL FROM DUAL" to retrieve the last
value generated for a sequence on the connection.







3.) How do I cascade the new key values to the child rows before
I submit them?

This is the simplest part of the process. When you create a
DataRelation object, ADO.NET will add a ForeignKeyConstraint
object to make sure that child rows match up to a parent row.
The ForeignKeyConstraint object exposes a UpdateRule property.
If this property is set to Cascade (the default), ADO.NET will
automatically cascade changes made to the parent down to the
associated child rows.

So, if you have a DataRelation set up between the DataTables
based on the auto-increment column, and you've set the parent
DataAdapter's InsertCommand to fetch the new auto-increment
values from the database, ADO.NET will cascade the new values
down to the associated child rows automatically.


I hope this information proves helpful. For more
information, see Chapter 11 of Microsoft ADO.NET, available
through Microsoft Press.
 

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