Dataset e campi contatore

mercoledì 15 ottobre 2003 - 10.34

tas Profilo | Newbie

Da un programma in VB.NET accedo ad un archivio Access così strutturato:
tabella Documenti:

---ID (contatore)
---Descrizione
---Tipo
---Numero
---Data
---Segno

tabella Magazzino:
---ID (contatore)
---Documento
---Articolo
---Quantità
---Prezzo

Esiste una relazione in integrità referenziale tra Magazzino.Documento e Documenti.ID, definita in Access. Un record in Documenti può avere quindi zero o più record figli in Magazzino.

Ho creato un dataset e ho aggiunto le due tabelle col metodo Fill, ho aggiunto sul campo Documenti.ID una chiave primaria e impostato le proprietà AutoIncrement=True, AutoIncrementSeed=1, ReadOnly=True. Infine ho aggiunto un vincolo tra Magazzino.Documento e Documenti.ID. Ecco una parte di codice:

Dim dad As OleDbDataAdapter
Dim dam As OleDbDataAdapter
Dim dt As DataTable
Dim dcp As DataColumn
Dim dcc As DataColumn
Dim dr As DataRelation
Dim cb As OleDbCommandBuilder
Dim bm As BindingManagerBase

sql = "SELECT * FROM Documenti"
dad = New OleDbDataAdapter(sql, conn)
dad.Fill(ds, "Documenti")
sql = "SELECT * FROM Magazzino"
dam = New OleDbDataAdapter(sql, conn)
dam.Fill(ds, "Magazzino")

Dim pk(1) As DataColumn
pk(0) = ds.Tables("Documenti").Columns("ID")
ds.Tables("Documenti").PrimaryKey = pk
With ds.Tables("Documenti").Columns("ID")
.AutoIncrement = True
.AutoIncrementSeed = 1
.ReadOnly = True
End With

dcp = ds.Tables("Documenti").Columns("ID")
dcc = ds.Tables("Magazzino").Columns("Documento")
Dim fkc As New ForeignKeyConstraint(dcp, dcc)
fkc.AcceptRejectRule = AcceptRejectRule.Cascade
ds.Tables("Magazzino").Constraints.Add(fkc)

'databindings
txbDescription.DataBindings.Add(New Binding("Text", ds, "Documenti.Descrizione"))
bm = Me.BindingContext(ds, "Documenti")

'nuovo record
bm.AddNew()

Quando creo una nuova riga in Documenti, il campo Documenti.ID assume automaticamente il prossimo numero valido, per esempio il valore 5. Creo quindi vari record nella tabella Magazzino, caricando valori opportuni. In particolare, imposto per ogni riga il campo Magazzino.Documento = 5, in modo che tali record non rimangano "orfani". A questo punto effettuo l'update delle tabelle:

dad.Update(ds, "Documenti")
dam.Update(ds, "Magazzino")

Con questa operazione aggiorno i dati all'interno delle rispettive tabelle di Access, ma se un altro utente si è collegato nel frattempo alla tabella Documenti e ha aggiunto un suo record, il mio record di Documenti non avrà più ID=5 ma per esempio ID=6. A questo punto le righe della tabella Magazzino contenute nel dataset puntano ad un record di Documenti sbagliato (Magazzino.Documento=5) con tutte le ovvie conseguenze.

Forse ho sbagliato approccio al problema, ma non trovo una soluzione, qualcuno ha dei suggerimenti?

Brainkiller Profilo | Guru

Ciao Tas,
sai che sono 2/3 giorni che penso a questo problema.
Devo dire che sono ignorante su questo particolare. Non saprei proprio coem si comporta, anche perchè a livello di transazionalità e concorrenza non so come sia messo Access. Sicuramente SQL è messo meglio.
Io pensavo, potresti fare così, metti il tutto in un try catch almeno la parte di update. In questo modo se qualcuno ha già fatto l'update prima di te e ha inserito dei record con degli ID che magari sono gli stessi che hai tu nel Dataset ti ritorna un Exception che tu puoi Gestire.

Cosa ne pensi?
Ciao

David De Giacomi

tas Profilo | Newbie

Ciao, grazie innanzitutto per avermi risposto.
La soluzione che ho trovato non è forse il massimo ma per il momento mi accontento. In pratica ho eliminato qualunque vincolo nel dataset su Documenti.ID (chiave primaria, relazioni e proprietà varie) e lo lascio vuoto. faccio l'update della tabella Documenti e subito dopo ripeto il metodo Fill, per avere nel dataset le modifiche apportate da Access, ossia il numero progressivo assegnato a ID. Carico questo valore nel campo Magazzino.Documento di ogni riga della tabella Magazzino, infine faccio l'update anche di questa tabella e chiudo tutto.
Tu che ne dici?

Brainkiller Profilo | Guru

Ciao Tas,
in effetti così come hai descritto potrebbe andare bene.
Certo non credo sia il metodo corretto anche perchè sarai d'accordo con me che è un giro lungo, però magari funziona.
Resto comunque molto dubbioso sull'esempio che hai posto e mi piacerebbe capire come funziona la cosa che hai descritto.
Ora non ho ancora avuto tempo ma volevo testarla personalmente e chiedere ad alcuni miei colleghi cosa ne pensano.
Quando e se avrò risposte valide posterò qui.

Ciao
David

tas Profilo | Newbie

Ciao, ecco il codice completo (o quasi). Si tratta di un form contenente essenzialmente una textbox (txbDescription) collegata in databinding al campo Documenti.Descrizione; un combobox scollegato dai dati; una ListView (lsvMaterial) nella quale vado a visualizzare le righe della tabella Magazzino.
Un overload a ShowDialog mi permette di visualizzare il form e di preparare opportunamente i vari controlli. La variabile id passata come argomento a ShowDialog può contenere il valore di Documenti.ID di un record già esistente che voglio mostrare nel form, oppure il valore 0 per indicare che voglio creare un nuovo record.

Public Overloads Function ShowDialog(ByVal id As Long) As DialogResult
Dim ret As DialogResult
Dim sql As String
Dim daa As OleDbDataAdapter
Dim dad As OleDbDataAdapter
Dim dam As OleDbDataAdapter
Dim dt As DataTable
Dim dcp As DataColumn
Dim dcc As DataColumn
Dim dr As DataRelation
Dim cb As OleDbCommandBuilder
Dim bm As BindingManagerBase

conn.Open()
ds = New DataSet

'tabella Articoli
sql = "SELECT * FROM Articoli"
daa = New OleDbDataAdapter(sql, conn)
daa.Fill(ds, "Articoli")

'tabella Documenti
If id = 0 Then
sql = "SELECT * FROM Documenti"
Else
sql = "SELECT * FROM Documenti WHERE ID = " & id.ToString
End If
dad = New OleDbDataAdapter(sql, conn)
dad.Fill(ds, "Documenti")

'tabella Magazzino
sql = "SELECT Magazzino.* FROM Magazzino WHERE Documento = " & id.ToString
dam = New OleDbDataAdapter(sql, conn)
dam.Fill(ds, "Magazzino")

'relazione tra Articoli.ID e Magazzino.Articolo
dcp = ds.Tables("Articoli").Columns("ID")
dcc = ds.Tables("Magazzino").Columns("Articolo")
dr = New DataRelation("ArticlesRelation", dcp, dcc, True)
ds.Relations.Add(dr)

'databindings
txbDescription.DataBindings.Add(New Binding("Text", ds, "Documenti.Descrizione"))
bm = Me.BindingContext(ds, "Documenti")

'nuovo record
If id = 0 Then
bm.AddNew()
End If

conn.Close()

'carica dati
Call LoadMaterial()
Call LoadPromotions()

'visualizza finestra
ret = MyBase.ShowDialog()
conn.Open()

'salva dati
If ret = DialogResult.OK Then

bm.EndCurrentEdit()
cb = New OleDbCommandBuilder(dad)

'se il record è nuovo imposta alcuni campi
If id = 0 Then
dt = ds.Tables("Documenti")
With dt.Rows(dt.Rows.Count - 1)
.Item("Numero") = GetNextNumber()
.Item("Tipo") = 3
.Item("Segno") = -1
.Item("Data") = Now.Date
End With
End If

'aggiorna l'origine dati e ricarica le modifiche
dad.Update(ds, "Documenti")
dad.Fill(ds, "Documenti")

'aggiorna il datatable relativo a Magazzino
Call SaveMaterial()
cb = New OleDbCommandBuilder(dam)
dam.Update(ds, "Magazzino")
Else
bm.CancelCurrentEdit()
End If
conn.Close()

Return ret
End Function

Private Sub SaveMaterial()
Dim id As Long
Dim dr As DataRow
Dim dt As DataTable

dt = ds.Tables("Documenti")
id = dt.Rows(dt.Rows.Count - 1).Item("ID")

'imposta il campo Documento = Documenti.ID
For Each dr In ds.Tables("Magazzino").Rows
dr("Documento") = id
Next
End Sub

Private Sub LoadMaterial()
Dim dr As DataRow
Dim dt As DataTable
Dim lvi As L

tas Profilo | Newbie

Continua dal post precedente:

Private Sub LoadMaterial()
Dim dr As DataRow
Dim dt As DataTable
Dim lvi As ListViewItem
Dim tot As Decimal
Dim dra As DataRow

dt = ds.Tables("Magazzino")

lsvMaterial.Items.Clear()

For Each dr In dt.Rows
dra = dr.GetParentRow("ArticlesRelation")
lvi = lsvMaterial.Items.Add(dra("Codice"))
lvi.Tag = dr
lvi.SubItems.Add(dra("Descrizione"))
lvi.SubItems.Add(dr("Quantità"))
lvi.SubItems.Add(CType(dr("Prezzo"), Decimal).ToString("F2"))
lvi.SubItems.Add(CType(dr("Quantità") * dr("Prezzo"), Decimal).ToString("F2"))
tot += dr("Quantità") * dr("Prezzo")
Next

txbTotal.Text = tot.ToString("F2")
End Sub

Nella programmazione in VB6 ed in particolare con la tecnologia (obsoleta) DAO c'era una comoda proprietà del recordset chiamata LastModified, che utilizzata in congiunzione con la proprietà Bookmark permetteva di posizionarsi sull'ultimo record aggiunto nel recordset:

rs.Bookmark = rs.LastModified

Non c'è nulla del genere in ADO.NET?
Partecipa anche tu! Registrati!
Hai bisogno di aiuto ?
Perchè non ti registri subito?

Dopo esserti registrato potrai chiedere
aiuto sul nostro Forum oppure aiutare gli altri

Consulta le Stanze disponibili.

Registrati ora !
Copyright © dotNetHell.it 2002-2024
Running on Windows Server 2008 R2 Standard, SQL Server 2012 & ASP.NET 3.5