Už asi dva měsíce zlehka experimentuji s VB 2005 EE, .NET Framework 2.0 a MSSQL Server 2005 EE. Dřív jsem trochu dělal ve VB 5, takže se seznamuji s něčím o tři generace novějším (k tomu všemu jsem ještě o jednu generaci starší, než většina přispěvovatelů na tomto fóru...:-) Mým cílem je vytvořit jednoduchou desktopovou databázovou aplikaci, kde na jednom PC (označme je "C") poběží MSSQL server, na něm bude jednoduchá databáze s cca 10-15 tabulkami (stovky, max. vyjímečně tisíce řádků) a též aplikace ve VB, která bude jednak prostřednictvím tří DataGridView prezentovat data z db tabulek v tříhladinovém režimu Master -> Detail a musí také umožňovat samotné pořízení dat jak editací v DataGridView, tak občas přímým programovým přiřazením hodnot do buněk mřížky. Vždy, když se pro danou celodenní akci data takto předpřipraví, přepne se aplikace na stanici C do režimu ReadOnly a spustí se stejná aplikace na druhém PC v lokální síti (ozačme je "S"), na které bude dále obsluha průběžně občas upravovat již zadané údaje. Takto "občerstvovaná" data potom bude využívat aplikace na stanici C k dalšímu zpracování (výběr, třídění, další prezentace). Po počátečním laborování s ConnectionStringy a nastavením SQL Serveru 2005 se mi podařilo rozchodit vzdálený přístup k databázi, ale brzdí mě jiné problémy. Nejprve jsem se snažil základní model aplikace "naklikat" pomocí různých průvodců, ale již brzy mě začal děsit objem kódu v modulu designer.vb příslušného datasetu, počítaný na tisíce řádků, kterému jsem navíc místy ne zcela rozuměl. Zkusil jsem tedy celý kód napsat od nuly ručně v následujících intencích: Jeden dataset, jeden objekt Connection, který uzavírám až při skončení aplikace, pro každou tabulku z databáze objekt SqlCommand s ručně napsaným SELECTem, DataTable, SQLDataAdapter, objekty DataRelation, kterými řeším vazby typu Master->Detail přes objekty BindingSource. Pro SQL příkazy dataadaptérů jsem zkoušel používat objekt SqlCommandBuilder, ale při bližším zkoumání automaticky vytvořených příkazů začínám i tady přecházet na "ruční práci" Příklad - pro ilustraci
Public con As SqlConnection
Public dsDost As DataSet
Public cmdAkceText As String = "SELECT AKC_ID, AKC_DatumKonani, ZAV_ID, AKC_Nazev FROM Akce ORDER BY AKC_DatumKonani"
Public cmdAkce As SqlCommand
Public cmdPom As SqlCommand
Public parPom As SqlParameter
Public dtAkce As DataTable
Public daAkce As SqlDataAdapter
Public cbAkce As SqlCommandBuilder
Public WithEvents bsAkce As BindingSource
Try
con = New SqlConnection(My.Settings.Default.ConnnectionString)
con.Open() 'Otevreni spojeni na databazi
Catch exc As SqlException
MessageBox.Show(exc.Number & vbCrLf & exc.Message)
Exit Sub
Catch oexc As Exception
MessageBox.Show(oexc.Message)
Exit Sub
End Try
dsDost = New DataSet("Dost") 'Inicializace Datasetu
'ukázka pouze pro první tabulku
cmdAkce = New SqlCommand(cmdAkceText, con) 'Definice Selectu pro data adapter Akce
daAkce = New SqlDataAdapter(cmdAkce) 'Inicializace datoveho adapteru
dtAkce = New DataTable("Akce") 'Inicializace datove tabulky dtAkce
dsDost.Tables.Add(dtAkce) 'Pridani datove tabulky dtAkce do datasetu
daAkce.MissingSchemaAction = MissingSchemaAction.AddWithKey 'Zaridi, ze do schematu v Datasetu se pridaji informace o klicich z podkladove DB
daAkce.Fill(dtAkce) 'Naplneni tabulky dtAkce z podkladove databaze
cbAkce = New SqlCommandBuilder(daAkce) 'Automaticke vygenerovani SQL prikazu
daAkce.InsertCommand = cbAkce.GetInsertCommand 'pro Insert
'Pro Delete už raději ručně
cmdPom = New SqlCommand("DELETE FROM [Akce] WHERE [AKC_ID] = @p1", con)
parPom = New SqlParameter("@p1", SqlDbType.Int, 0, ParameterDirection.Input, False, 0, 0, "AKC_ID", DataRowVersion.Current, Nothing)
cmdPom.Parameters.Add(parPom)
daAkce.DeleteCommand = cmdPom
'Pro Update už raději také ručně
cmdPom = New SqlCommand("UPDATE [Akce] SET [AKC_DatumKonani] = @p1, [ZAV_ID] = @p2, [AKC_Nazev] = @p3 WHERE [AKC_ID] = @p4", con)
parPom = New SqlParameter("@p1", SqlDbType.DateTime, 0, ParameterDirection.Input, False, 0, 0, "AKC_DatumKonani", DataRowVersion.Current, Nothing)
cmdPom.Parameters.Add(parPom)
parPom = New SqlParameter("@p2", SqlDbType.Int, 0, ParameterDirection.Input, False, 0, 0, "ZAV_ID", DataRowVersion.Current, Nothing)
cmdPom.Parameters.Add(parPom)
parPom = New SqlParameter("@p3", SqlDbType.NVarChar, 0, ParameterDirection.Input, False, 0, 0, "AKC_Nazev", DataRowVersion.Current, Nothing)
cmdPom.Parameters.Add(parPom)
parPom = New SqlParameter("@p4", SqlDbType.Int, 0, ParameterDirection.Input, False, 0, 0, "AKC_ID", DataRowVersion.Current, Nothing)
cmdPom.Parameters.Add(parPom)
daAkce.UpdateCommand = cmdPom
bsAkce = New BindingSource(dsDostihy, "Akce") 'Inicializace vazebniho prvku pro tabulku Akce
dgwAkce.DataSource = bsAkce 'Inicializace datove mrizky pro tabulku Akce
atd. naplneni datasetu pro další tabulky dále ukázka vytváření sloupců v hlavní mřížce, kde s výhodou používám sloupec typu DataGridViewComboBoxColumn, pro zobrazení položek, které mají v dané tabulce uloženo pouze ID s vazbou do další tabulky, kde je text, popřípadě další údaje.
Dim AKC_DatumKonani As New DataGridViewTextBoxColumn
AKC_DatumKonani.DataPropertyName = "AKC_DatumKonani"
AKC_DatumKonani.HeaderText = "Datum"
AKC_DatumKonani.Name = "AKC_DatumKonani"
AKC_DatumKonani.Width = 80
dgwAkce.Columns.Add(AKC_DatumKonani)
Dim Zav_Nazev As New System.Windows.Forms.DataGridViewComboBoxColumn
Zav_Nazev.DataPropertyName = "ZAV_ID"
Zav_Nazev.DataSource = dtZavodiste 'odkaz do "číselníkové" tabulky
Zav_Nazev.DisplayMember = "ZAV_Nazev"
Zav_Nazev.ValueMember = "ZAV_ID"
Zav_Nazev.HeaderText = "Závodiště"
Zav_Nazev.Name = "Zav_Nazev"
Zav_Nazev.Width = 120
dgwAkce.Columns.Add(Zav_Nazev)
Dim AKC_Nazev As New DataGridViewTextBoxColumn
AKC_Nazev.DataPropertyName = "AKC_Nazev"
AKC_Nazev.HeaderText = "Název"
AKC_Nazev.Name = "AKC_Nazev"
AKC_Nazev.Width = 200
dgwAkce.Columns.Add(AKC_Nazev)
Model odpojené databáze, tak příjemný pro ASP.NET mi pro dané použití příliš nevyhovuje, protože potřebuji všechny editační operace v podstatě okamžitě potvrzovat do podkladové databáze. Zvolil jsem proto událost CurrentChanged příslušného objektu BindingSource pro každou komponentu DataGridView a tam volám proceduru UlozTabulku, která mi pomocí metody Update příslušného SqlDataAdapteru uloží změnu do databáze.
Private Sub UlozTabulku(ByRef dtPom As DataTable, ByRef daPom As SqlDataAdapter)
BindingContext(dtPom).EndCurrentEdit()
Dim dt As DataTable = dtPom.GetChanges
If Not dt Is Nothing Then
Try
daPom.Update(dt)
dtPom.AcceptChanges()
Catch exc As SqlException
MessageBox.Show(exc.Number & vbCrLf & exc.Message)
Exit Sub
Catch oexc As Exception
MessageBox.Show(oexc.Message)
Exit Sub
End Try
End If
End Sub
Jelikož jsem v prostředí .NET úplný začátečník, moc by mi pomohlo, kdyby mi někdo zkouknul popsaný postup a ukázky kódu a popř. mě upozornil, zda nedělám něco špatně, či příliš kostrbatě apod. Nevím si dále vůbec rady s následujícími problémy: 1. Při pořizování dat v DataGridView se mi nedaří čistě ošetřit chybové stavy. Potřeboval bych jednoduše nechat obsluhu vždy zadat celý řádek v mřížce, resp. alespoň položky, které jsou Not Null a nenechat ji přejít na jiný řádek dokud se nevyřeší nevyplněné položky NotNull, formátové chyby, a narušení duplicity ve sloupcích, které jsou Unique Key. Zároveň nechci psát příliš mnoho "jednoúčelového" kódu, protože aplikaci budu obsluhovat pouze já, takže komfort nemusí být velký. Prošel jsem na webu různá doporučení, experimentoval s událostmi Validating a Validated pro buňku, řádek i celou mřížku a nejlepší se mi zdálo odchycení a ošetření události DataError u mřížky, ale událost bohužel nenastává vždy, když bych potřeboval, navíc nemám asi úplně dobře kód událostní procedury, takže často spadnu až do neošetřené výjimky (např.Sloupec SEZ_Poradi nepovoluje hodnoty NULL apod.)
Private Sub Dgw_DataError(ByVal sender As Object, ByVal e As DataGridViewDataErrorEventArgs) Handles dgwAkce.DataError, dgwSeznam.DataError, dgwDetail.DataError
Dim ErrStr As String = "Chyba " & e.Context.ToString
MessageBox.Show(ErrStr)
e.ThrowException = False
e.Cancel = True
End Sub
2. Nemohu přijít na to, jak vynutit uložení změněných řádků v mřížce přes podkladovou DataTable do databáze na serveru v případě, že hodnota do nějaké buňky v mřížce nebyla vložena z klávesnice a posléze odchycena událost BindingSource.CurrentChanged, ale programovým přiřazením přímo do buňky. např.
dgwDetail.CurrentRow.Cells("CAP_ID").Value = 1
tady se musí zavolat něco, co promítne změnu z mřížky přes BindingSource do tabulky v datasetu, ale zkoušel jsem různé metody EndEdit a Commit, ale nic nezabírá... Budu vděčen za jakékoli připomínky a rady. Jirka
|