Eenvoudig voorbeeld
Wanneer je werkt met Visual Studio.net kan je de code die je zelf moet schrijven voor het maken van een databaseproject tot een minimum beperken.
Bekijk de code die automatisch door Visual Studio.net werd gegenereerd, het belangrijkste is dat je begrijpt hoe deze code is opgebouwd en in elkaar zit. Visual Studio is een hulpmiddel voor jou als programmeur, zorg dat je echter steeds de code begrijpt en hier ook op kan ingrijpen !
Master-Details
We maken nog een toepassing met Visual Studio.net:
- Maak een nieuw project: Windows Application: DBToep2
- Maak met de Wizard een SqlDataAdapter en SqlConnection aan door een SqlDataAdapter op de form te slepen
- Maak een nieuwe verbinding met SQL Server naar de database Pubs
- Gebruik SQL-Statements:
SELECT pub_id, pub_name FROM publishers
- Voorzie nog een SqlDataAdapter, maak gebruik van dezelfde connectie
SELECT title_id, title, pub_id, price FROM titles
- Kies Data > generate DataSet, zorg ervoor dat beide tabellen geselecteerd zijn.
- Na het aanmaken van een DataSet maakt Visual Studio.net een .xsd-bestand aan, open het bestand DataSet1.xsd
- In de ToolBox zien we nu een nieuw onderdeel XML Schema
Elk boek uit de tabel titles heeft een uitgevever in de tabel publishers, het veld dat voor de koppeling kan zorgen heet in beide tabellen pub_id.
We definiëren een relatie tussen beide tabellen door een Relation-object te slepen op de tabel met de foreign key: titles
De relatie krijgt van Visual Studio.net automatisch een naam toegewezen. De instellingen die we te zien krijgen zijn bruikbaar voor onze toepassing.
Bevestig deze instellingen
- Geef volgende code op in de Form1_Load-methode: het fysiek vullen van de DataSets
sqlDataAdapter1.Fill(dataSet11);
sqlDataAdapter2.Fill(dataSet11);
Beide SqlDataAdapters krijgen de volledige dataSet11 als bron, dit is geen enkel probleem. Dit voorbeeld werkt ook als je de tabellen specifiek aangeeft:
sqlDataAdapter1.Fill(dataSet11.Tables["publishers"]);
sqlDataAdapter2.Fill(dataSet11.Tables["titles"]);
- Sleep een ListBox op de Form
- DataSource: dataSet11
- DisplayMember: publishers.pub_name
- Sleep een DataGrid op de Form
- DataSource: dataSet11
- DataMember: publishers.publisherstitles - de naam van de relatie!
- Je merkt dat de relatie nu automatisch gevolgd wordt!
BindingContext
System.Windows.Forms.BindingContext
Een Windows Form beschikt over minstens één BindingContext object . Dit object beheert de CurrencyManager objecten voor de DataSources van deze Form. Doordat er meerdere DataSources op een Form aanwezig kunnen zijn, staat de BindingContext toe te communiceren met de CurrencyManager van de gewenste DataSource.
CurrencyManager
System.Windows.Forms.CurrencyManager
De CurrencyManager zorgt ervoor dat data-bound controls gesynchroniseerd blijven met elkaar: de controls die aan dezelfde DataSource gebonden zijn tonen de gegevens van dezelfde record.
Een belangrijke eigenschap van CurrencyManager is Position: deze eigenschap geeft aan op welke record de CurrencyManager momenteel gepositioneerd is.
Bindable Data Sources
Om een control te kunnen binden aan een data source kunnen we als minimum voorwaarde stellen: de source moet de interface IList implementeren. Er zijn binnen het .NET Framework veel objecten aanwezig die als data source kunnen dienen: van eenvoudige elementen zoals
Array over collecties zoals
ArrayList en
Hashtable tot complexe structuren zoals
DataView,
DataTable, ...
DataBinding
Simple Data Binding kan je toepassen wanneer je bijvoorbeeld de Text-eigenschap van een
TextBox aan een gegevensbron wenst te binden. Hiervoor kan je bindingen toevoegen aan de DataBindings-collectie van de control:
txtContact.DataBindings.Add("Text",dsView,"Klant.ContactName");
argumenten:
- eigenschapnaam van de control die je wenst te binden
- dataSource
- dataMember
Binden aan een ComboBox of ListBox:
comboBoxState.DataSource=States
comboBoxState.DisplayMember="LongName"
comboBoxState.ValueMember="ShortName"
comboBoxState.DataBindings.Add("SelectedValue", customersDataSet1, "Customers.Region")
Binden aan een DataGrid:
dataGrid1.Size = new System.Drawing.Size(584, 336);
dataGrid1.DataSource = customersDataSet1;
dataGrid1.DataMember = "Customers";
Voorbeeld: automatisch gesynchronsiseerd Master-Detail: werken met relaties
In deze toepassing leer je werken met gesynchroniseerde gegevens. Je kan een klant kiezen in een ComboBox. De bestellingen van de klant worden weergegeven in een DataGrid. De detailgegegvens van de bestelling worden weergegeven in een tweede DataGrid.
Hiertoe kan je een DataSet aanmaken die naast de benodigde tabellen ook de relaties tussen de tabellen bevat. Relaties kan je maken met behulp van DataRelation-objecten. Deze relaties voeg je toe aan de Relations-collectie van de DataSet.
Om de DataGrids te synchroniseren stel je de DataSource voor beide DataGrid-objecten in op de DataSet. Om de relatie te handhaven stel je de eigenschap DataMember van een gekoppelde DataGrid in op de relatie.
Toon /verberg Oplossing
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Data.SqlClient;
namespace MasterDetail
{
public class Form1 : System.Windows.Forms.Form
{
private System.Windows.Forms.GroupBox groupBox1;
private System.Windows.Forms.TextBox txtFax;
private System.Windows.Forms.TextBox txtTelefoon;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.GroupBox groupBox2;
private System.Windows.Forms.DataGrid dgBestelling;
private System.Windows.Forms.GroupBox groupBox3;
private System.Windows.Forms.DataGrid dgDetail;
private String ConnectionString;
private DataViewManager dsView;
private DataSet ds;
private System.Windows.Forms.ComboBox cbKlant;
private System.Windows.Forms.TextBox txtContact;
...
private void Form1_Load(object sender, System.EventArgs e)
{
// Verbinding maken met database
ConnectionString = "data source=(local);uid=spion;pwd=spionpas;database=northwind";
SqlConnection conn = new SqlConnection(ConnectionString);
// DataSet maken: deze zal alle gegevens bevatten die we nodig hebben
// samen met de nodige relaties
ds = new DataSet("KlantenBestellingen");
// Fill the Dataset with Customers, map Default Tablename
// "Table" to "Customers".
// Maak een SqlDataAdapter met de select-instructie op de tabel Customers
// uit de database
SqlDataAdapter da1 = new SqlDataAdapter("SELECT * FROM Customers",conn);
// via de TableMappings voegen we de resultaattable toe aan de SqlDataAdapter
// de methode Add ontvangt twee argumenten:
// sourceTable: naam van de brontabel (standaard "Table")
// dataSetTable: naam die de tabel zal hebben in de DataSet
da1.TableMappings.Add("Table","Klant");
// De DataSet vullen met behulp van de methode Fill van de SqlDataAdapter
da1.Fill(ds);
// De laatste twee opdrachten konden in 1 coderegel geschreven worden:
// da1.Fill(ds,"Klant");
// Bovenstaande code (met TableMappings) kan je gebruiken om een DataSet te vullen
// vanuit een SqlDataAdapter die meerdere tabellen bevat.
// Bestellingen aan de DataSet toevoegen
SqlDataAdapter da2 = new SqlDataAdapter("SELECT * FROM Orders",conn);
da2.TableMappings.Add("Table","Bestelling");
da2.Fill(ds);
// Details van de bestelllingen aan de DataSet toevoegen
SqlDataAdapter da3 = new SqlDataAdapter("SELECT * FROM [Order Details]",conn);
da3.TableMappings.Add("Table","BestellingDetail");
da3.Fill(ds);
// Relatie tussen klanten en bestellingen
DataRelation relKlantBestelling;
DataColumn colMaster1;
DataColumn colDetail1;
colMaster1 = ds.Tables["Klant"].Columns["CustomerID"];
colDetail1 = ds.Tables["Bestelling"].Columns["CustomerID"];
relKlantBestelling = new DataRelation("RelKlantBestelling",colMaster1,colDetail1);
ds.Relations.Add(relKlantBestelling);
// relatie tussen bestellingen en details van de bestelling
DataRelation relBestDet;
DataColumn colMaster2;
DataColumn colDetail2;
colMaster2 = ds.Tables["Bestelling"].Columns["OrderID"];
colDetail2 = ds.Tables["BestellingDetail"].Columns["OrderID"];
relBestDet = new DataRelation("RelBestDet",colMaster2,colDetail2);
ds.Relations.Add(relBestDet);
// Een DataViewManager staat je toe alle tabellen en relaties in een DataSet te benaderen
dsView = ds.DefaultViewManager;
// Grid Databinding
// DataSource wordt ingesteld op de DataViewManager
// DataMember wordt ingesteld op de relatie Klant <--> Bestelling
// Deze relatie kan je benaderen vauit de MasterTabel (klant)
dgBestelling.DataSource = dsView;
dgBestelling.DataMember = "Klant.RelKlantBestelling";
dgDetail.DataSource = dsView;
dgDetail.DataMember = "Klant.RelKlantBestelling.RelBestDet";
// Combobox Databinding
// deze combobox bevat de klantnamen en wordt gebruikt om de positie van
// de dataview aan te passen, we hoeven geen aparte dataBinding meer toe
// voegen.
cbKlant.DataSource = dsView;
cbKlant.DisplayMember = "Klant.CompanyName";
cbKlant.ValueMember = "Klant.CustomerID";
// TextBox Databinding
txtContact.DataBindings.Add("Text",dsView,"Klant.ContactName");
txtTelefoon.DataBindings.Add("Text",dsView,"Klant.Phone");
txtFax.DataBindings.Add("Text",dsView,"Klant.Fax");
}
}
}
Positionering
We voegen nu aan de vorige toepassing navigatieknoppen en een display-paneeltje toe:
Toon /verberg
private void Form1_Load(object sender, System.EventArgs e)
{
...
// Positie bijhouden
// het rechterlid van deze vergelijking retourneert een BindingManagerBase object
// De klasse CurrencyManager erft over van deze klasse dus kunnen we een typecast doen
// deze casting is niet verplicht, je kan ook rechtstreeks met een BindingManagerBase-object werken:
// BindingManagerBase cmKlant = this.BindingContext[dsView,"Klant"];
CurrencyManager cmKlant = (CurrencyManager)this.BindingContext[dsView,"Klant"];
// Een event-handler installeren voor het event PositionChanged
cmKlant.PositionChanged += new EventHandler(klant_PositionChanged);
// initiele positie tonen
ToonKlantPositie();
}
private void btnVolgende_Click(object sender, System.EventArgs e)
{
CurrencyManager cm = (CurrencyManager)this.BindingContext[dsView,"Klant"];
if (cm.Position < cm.Count - 1)
{
cm.Position++;
}
}
private void btnVorige_Click(object sender, System.EventArgs e)
{
CurrencyManager cm = (CurrencyManager)this.BindingContext[dsView,"Klant"];
if (cm.Position > 0)
{
cm.Position--;
}
}
private void btnEerste_Click(object sender, System.EventArgs e)
{
this.BindingContext[dsView,"Klant"].Position = 0;
}
private void btnLaatste_Click(object sender, System.EventArgs e)
{
this.BindingContext[dsView,"Klant"].Position = this.BindingContext[dsView,"Klant"].Count - 1;
}
// Positie is gewijzigd
private void klant_PositionChanged(object sender, System.EventArgs e)
{
ToonKlantPositie();
}
private void ToonKlantPositie()
{
CurrencyManager cm = (CurrencyManager)this.BindingContext[dsView,"Klant"];
lblPositie.Text = String.Format (
"Record {0} of {1}", (cm.Position + 1), cm.Count
);
}
In de bovenstaande toepassingen heb je geleerd hoe je gegevens uit een gegevensbron kan weergeven in .NET Controls.
Met ADO.net kan je deze gegevens ook manipuleren: records toevoegen, verwijderen en bewerken.
De manier die we hier bespreken is de volledig handmatige manier van werken: we houden de touwtjes zo strak mogelijk zelf in hand.
Een voorbeeld van deze werkwijze vind je in het hoofdstuk ADO.net.
Je kan hier natuurlijk ook werken met Stored Procedures in plaats van ingebedde SQL-Statements.
Wil je werken met een SqlDataAdapter, maar wens je toch zelf controle te hebben over de commands die uitgevoerd worden bij het manipuleren van de records, dan kan je de routines die uitgevoerd worden bij een UPDATE, INSERT en DELETE zelf aangeven.
In deze kleine toepassing gebruiken we hiervoor Stored Procedures, je kan natuurlijk ook met ingebedde SQL-Statements werken.
Toon /verberg
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Data.SqlClient;
namespace Manipuleren_Command
{
public class Form1 : System.Windows.Forms.Form
{
private System.ComponentModel.Container components = null;
private SqlConnection conn;
private SqlDataAdapter daKlant;
private System.Windows.Forms.DataGrid dgKlant;
private System.Windows.Forms.Button btnUpdate;
private DataSet dsKlant;
...
private void Form1_Load(object sender, System.EventArgs e)
{
conn = new SqlConnection("Data Source=(local);uid=bib;pwd=bibpas;database=biblio_2000");
SqlParameter param;
//SELECT
daKlant = new SqlDataAdapter("spKlant",conn);
daKlant.SelectCommand.CommandType = CommandType.StoredProcedure;
//UPDATE
daKlant.UpdateCommand = new SqlCommand("spBewerkKlant",conn);
daKlant.UpdateCommand.CommandType = CommandType.StoredProcedure;
param = daKlant.UpdateCommand.Parameters.Add("@klant_id", SqlDbType.Int,4);
param.SourceColumn = "klant_id";
param.SourceVersion = DataRowVersion.Original;
param = daKlant.UpdateCommand.Parameters.Add("@klantnaam", SqlDbType.NVarChar,30);
param.SourceColumn = "klantnaam";
param.SourceVersion = DataRowVersion.Current;
param = daKlant.UpdateCommand.Parameters.Add("@plaats", SqlDbType.NVarChar,30);
param.SourceColumn = "plaats";
param.SourceVersion = DataRowVersion.Current;
//DELETE
daKlant.DeleteCommand = new SqlCommand("spVerwijderKlant",conn);
daKlant.DeleteCommand.CommandType = CommandType.StoredProcedure;
param = daKlant.DeleteCommand.Parameters.Add("@klant_id",SqlDbType.Int,4);
param.SourceColumn = "klant_id";
param.SourceVersion = DataRowVersion.Current;
//INSERT
daKlant.InsertCommand = new SqlCommand("spNieuwKlant",conn);
daKlant.InsertCommand.CommandType = CommandType.StoredProcedure;
param = daKlant.InsertCommand.Parameters.Add("@klantnaam", SqlDbType.NVarChar,30);
param.SourceColumn = "klantnaam";
param.SourceVersion = DataRowVersion.Current;
param = daKlant.InsertCommand.Parameters.Add("@plaats", SqlDbType.NVarChar,30);
param.SourceColumn = "plaats";
param.SourceVersion = DataRowVersion.Current;
//DATASET
dsKlant = new DataSet();
daKlant.Fill(dsKlant,"Klanten");
dgKlant.DataSource = dsKlant;
dgKlant.DataMember = "Klanten";
//EERSTE KOLOM VAN DATAGRIDVERBERGEN
DataGridTableStyle ts = new DataGridTableStyle();
ts.MappingName = dgKlant.DataMember;
dgKlant.TableStyles.Add(ts);
dgKlant.TableStyles["Klanten"].GridColumnStyles["klant_id"].Width = 0;
//KOLOMKOP aanpassen
dgKlant.TableStyles["Klanten"].GridColumnStyles["klantnaam"].HeaderText = "naam";
}
private void btnUpdate_Click(object sender, System.EventArgs e)
{
try
{
daKlant.Update(dsKlant,"Klanten");
}
catch(DBConcurrencyException ex)
{
MessageBox.Show(ex.Message);
}
}
}
}