Tietokannat - Pääteohjaus 5
- Luo uusi Windows Forms -Projekti. Sijoita projektisi muualle kuin U:-asemalle.
- Lisää projektiisi tietokantayhteys Tools|Connect to Database.
- Valitse tietolähteeksi Microsoft SQL Server Database File.
- Sijoita tietokanta c:\mytemp\ohjaus5.mdf-tiedostoon. Anna Visual Studion luoda uusi kanta.
- Uusi tietokantayhteys ilmestyy Server Explorerin Data Connections -listaukseen. Server Explorerissa voisit halutessasi muokata tietokannan rakennetta. Valitse tietokantayhteytesi kontekstivalikosta New Query.
- Add Table -dialogista ei tarvitse valita muuta kuin suoraan Close.
- Luo uusi tietokanta SQL-lauseilla, jotka liität valmiina olevien päälle. SQL-lauseet saat suoritettua valinnalla Query Designer|Execute SQL.
- Älä välitä DROP TABLE -lauseisiin liittyvästä virheilmoituksesta vaan valitse Continue
- Saat pitkän virheilmoituslistauksen. Tämä johtuu DROP TABLE -lauseista, jotka yrittävät poistaa olemattomia tauluja.
- Suorita SQL-lauseet uudelleen niin et saa enää virheitä vaan tiedon lisättyjen rivien määrästä.
- Seuraavaksi voit lisätä varsinaisen uuden tietolähteen valinnalla Data|Add New Data Source. Jos tätä valintaa ei löydy niin varmista, että sinulla on lomake aktiivisena.
- Valitse Database ja Dataset. Valitse yhteydeksi edellä luomasi yhteys.
- Anna Visual Studion kopioida tietokanta projektisi kansioon. Huomaathan, että tietokannan sisältö palaa nyt jokaisen käynnistyskerran jälkeen alkuperäiseksi. Jos projektisi on verkkolevyasemalla et voi antaa kopioida tietokantaa. Jos haluat myöhemmin vaikuttaa kopioidaanko tietokanta vai ei niin voit säätää tätä valitsemalla Solution Explorerista tietokantasi ja muokkaamalla tietokannan Copy to Output Directory-ominaisuutta. Aiheesta tarkemmin: Debugging with Local Database File ja Local Data Overview
- Anna connectionstringin tallentua projektiisi
- Valitse tietokannasta kaikki taulut
- Nyt Data Sources -välilehdeltä löytyvät kaikki tietokantasi taulut. Välilehden saat näkyviin valinnalla Data|Show Data Sources
- Voit valita kullekin taululle oletusesitystavan alasvetovalikosta. Valkkaa reseptille details-muoto. Voit muokata myös yksittäisten kenttien esitystapaa ja tarvittaessa piilottaa jotain turhia kenttiä näkyvistä. Raahaa resepti lomakkeellesi. Kokeile lomakkeesi toimintaa
- Raahaa lomakkeelle myös Ohje mutta varmista, että raahaat sen hierarkisesta listauksesta Reseptin alapuolelta. Tällä varmistat, että ohje-listauksen sisältö on lomakkeella automaattisesti synkronoitu reseptiin.
- Siisti lomaketta. Poista turhat ID-kentät näkyvistä. Näkyviä kenttiä pääset muokkaamaan columns-propertyn kautta. Kokeile syöttää lomakkeella tietoja. Kokeile mitä tapahtuu jos jätät pakollisia kenttiä tyhjiksi? Toimivassa ohjelmassa täytyy muistaa napata kiinni kaikki tietokantaan liittyvät poikkeukset! Turhat kentät kuten ReseptiID voidaan piilottaa. Tietokanta keksii itse automaattisesti sopivan ReseptiIDn. Huom. Reseptin RuokalajiID-kenttä ei ole turha!
- Lomakkeellasi on näkymättöminä komponentteina objekteja joilla voit käsitellä tietokantaa.
ReseptiBindingSource- ja OhjeBindingSource-objekteilla voit navigoida tietueesta toiseen.
Lisää lomakkeelle painikkeet joilla voi siirtyä seuraavaan ja edelliseen reseptiin.
reseptiBindingSource.MoveNext();
- Lisää painike jolla voit kokeilla uuden Reseptin syöttämistä seuraavanlaisella koodilla:
DataRow resepti = this.ohjaus5DataSet.Resepti.NewReseptiRow(); resepti.SetField("Nimi", "Hernesoppa"); resepti.SetField("RuokaLajiID", 1); resepti.SetField("Kuvaus", "kansallisherkku"); resepti.SetField("Henkilomaara", 1); this.ohjaus5DataSet.Resepti.Rows.Add(resepti); this.tableAdapterManager.UpdateAll(this.ohjaus5DataSet);
Muuta datasetin nimi oikeaksi.
- Lisää myös poista-painike, joka poistaa valittuna olevan ohje-rivin.
ohjeBindingSource.RemoveCurrent(); // muutokset tulevat voimaan vasta seuraavan komennon jälkeen: this.tableAdapterManager.UpdateAll(this.ohjaus5DataSet);
Mitä tapahtuu jos muutat poista-painikkeen poistamaan valittuna olevan reseptin? Miksi? Miten ongelma pitäisi kiertää? Kyseessähän on viite-eheyden aiheuttama ongelma ja kaikki ne tietueet pitää poistaa, jotka viittaavat reseptiin. Tämä onkin hieman hankalampi temppu. Jos poistettaviin asioihin liittyy jo BindingSource niin ne on helppo poistaa käymällä BindingSourcen avulla listaa läpi:
// huom. tehtävä silmukkä väärään suuntaan koska muuten indeksit muuttuvat aina poistettaesssa // pitää siis poistaa lopusta alkaen for (int i = ohjeBindingSource.Count-1; i>= 0; i--) { ohjeBindingSource.RemoveAt(i); } this.tableAdapterManager.UpdateAll(this.ohjaus5DataSet);
Poistetaan kaikki liittyy-taulun rivit, jotka viittaavat reseptiin, jonka id on 2:
DataRow[] liittyy = new DataRow[this.ohjaus5DataSet.Liittyy.Count]; // pitää tehdä kopio! Voisi myös pyytää pelkän viitteen Rows-collectioniin mutta // sen jälkeen foreach-silmukka kaatuu, koska poistetaan silmukassa esiintyviä alkioita! this.ohjaus5DataSet.Liittyy.Rows.CopyTo(liittyy, 0); foreach (ohjaus5DataSet.LiittyyRow item in liittyy) { if ( (int)item["Resepti_ReseptiID"] == 2 ) this.ohjaus5DataSet.Liittyy.RemoveLiittyyRow(item); } this.tableAdapterManager.UpdateAll(this.ohjaus5DataSet);
Ylläoleva olisi helpompi tehdä BindingSource-objektin avulla mutta sellaista ei nyt ollut valmiina. Voit kuitenkin itse luoda sellaisen ja linkittää sen reseptiin:
// viiteavainkenttä BindingSource liittyyBindingSource = new BindingSource(); liittyyBindingSource.DataMember = "Liittyy_ReseptiID"; liittyyBindingSource.DataSource = reseptiBindingSource;
BindingSourcen avulla saa toisistaan riippuvia tietolähteitä. Komponenttien raahaaminen lomakkeelle saa designerin generoimaan hyvää mallikoodia josta näkee miten esim. ohje saadaan sidottua reseptiin.
Lisäyksiä, päivityksiä ja poistoja voi tehdä myös tableAdapterin avulla. Esim:
// parametreina annetaan koko tietueen tiedot reseptiTableAdapter.Delete("Nimi", "Kuvaus", 4, 2, 1); // samat kuin deleten yhteydessä mutta reseptinID:tä ei tarvita koska se luodaan automaattisesti reseptiTableAdapter.Insert("Nimi", "Kuvaus", 4, 1); // päivitys vaatii DataRow-tyyppisen objektin reseptiTableAdapter.Update(DataRow);
- Useamman taulun käsitteleminen yhtäaikaa ei ole aivan niin yksinkertaista. Taulut pitää osata käsitellä
oikeassa järjestyksessä (viite-eheys vaikuttaa) ja muutokset pitää päivittää tietokantaan. Periaatteessa
this.tableAdapterManager.UpdateAll-komennon pitäisi osata tehdä kaikki tämä mutta jostain syystä se ei vain aina
toimi.
// poistetaan resepti ja siihen liittyvät muut tietueet todo: tämä pitäisi tehdä transaktiona! ohjaus5DataSet.OhjeDataTable deletedOhjeRecords = (ohjaus5DataSet.OhjeDataTable)ohjaus5DataSet.Ohje.GetChanges(DataRowState.Deleted); if ( deletedOhjeRecords != null) ohjeTableAdapter.Update(deletedOhjeRecords); ohjeBindingSource.EndEdit(); ohjaus5DataSet.LiittyyDataTable deletedLiittyyRecords = (ohjaus5DataSet.LiittyyDataTable)ohjaus5DataSet.Liittyy.GetChanges(DataRowState.Deleted); if (deletedLiittyyRecords != null) liittyyTableAdapter.Update(deletedLiittyyRecords); liittyyBindingSource.EndEdit(); reseptiBindingSource.RemoveCurrent(); reseptiBindingSource.EndEdit(); ohjaus5DataSet.ReseptiDataTable deletedReseptiRecords = (ohjaus5DataSet.ReseptiDataTable)ohjaus5DataSet.Resepti.GetChanges(DataRowState.Deleted); if (deletedReseptiRecords != null) reseptiTableAdapter.Update(deletedReseptiRecords); // nyt ei enää pidä kutsu updateall-metodia
- Voit pyytää datasetiltä haluaamasi reseptiä tai ohjetta tai mitä tahansa myös seuraavalla tavalla:
DataRow resepti = this.ohjaus5DataSet.Resepti.FindByReseptiID(8); // pitää tietää haluttu ID
BindingSourcelta voi myös kysellä ja sen paikkaa vaihtaa:
reseptiBindingSource.Position = reseptiBindingSource.Find("ReseptiID", 4);
- Lisää lomakkeelle päivitä-painike, joka muuttaa valitun reseptin nimeksi Mykykeitto.
Valitun reseptin saat pyytämällä BindingSourcelta Current-riviä
DataRowView curr = (DataRowView)reseptiBindingSource.Current; DataRow c = curr.Row; c.SetField("Nimi", "Mykykeitto");
- BindingSource-olioon voi liittää myös tapahtumankäsittelijöitä. Voit esimerkiksi reagoida tietueen vaihtumiseen (PositionChanged) jolloin pystyt päivittämään muita riippuvia komponentteja. Lisää reseptin PositionChanged-tapahtumaan käsittelijä,
joka kirjoittaa aina valittuna olevan reseptin nimen lomakkeen otsikoksi:
this.Text = (string)((DataRowView)reseptiBindingSource.Current).Row["Nimi"];
Tarvittaessa voit helposti luoda omia BindingSource-olioita. BindingSourcen datasource liitetään haluttuun tietolähteeseen ja käyttöliittymäkomponentin datasourcena käytetään BindingSourcea.
BindingSource bs = new BindingSource(); bs.DataSource = tietolahde; listbox1.DataSource = bs;
- Kokeile tietokannan käyttämistä Entity Data Modelin (EDM) kautta. Lisää uusi Data Source samaan tapaan kuin edelläkin mutta älä valitse tyypiksi Dataset vaan valitse Entity Data Model
- Generoi tietokannan pohjalta ja valitse tietokannaksi sama kuin edelläkin. Kopioi taas mukaan projektiin ja valitse kaikki taulut.
- Nyt joudut kirjoittamaan hieman koodiakin. Luo uusi lomake ja lisää lomakkeesi koodiin seuraavat:
ohjaus5Entities _Context; // ohjaus5Entitiesin nimi riippuu data modelin nimestä // hätätilassa nimen voi lunttia designerin tekemästä koodista public Form1() { InitializeComponent(); _Context = new ohjaus5Entities(); } public ohjausEntities Ctx { get { return _Context; } set { } }
Esimerkkiä voit katsoa täältä: - Tee EDM:n avulla samat toiminnot uudelle lomakkeelle mitä teit aiemmin. Aseta lomakkeelle tarvittavat kentät ja sido niiden data sourcet tauluihin jne. Luo jokaista taulua ja sitä vastaavaa lomakekontrollia varten BindingSource-objekti, jonka sidot tietokantaobjektiin ja käytät sitten lomakekontrollin datasourcena. BindingSource-objektin avulla pystyt helposti navigoimaan tietueita edestakaisin.
- Lisää lomakkeelle listaus reseptiin liittyvistä aineista, niiden määristä ja yksiköistä. Tässä joudut pakostakin käyttämään liitoksia LINQ-kyselyssä.
- Tietojen päivittäminen ei enää onnistu yksinkertaisesti koska tiedot on koostettu useammasta taulusta. Lisää lomakkeelle painike jolla pääsee muokkaamaan valittuna olevaa ainetta eli valitsemaan aineen, sen määrän ja yksikön. Tätä varten joudut luomaan ihan uudet muokkauskentät ja päivittämään niistä tiedot tietokantaan.
- Harjoittele LINQ-kyselyjä seuraavilla tehtävillä. Tulosta kyselyjen vastaukset esim. listboxiin tai consoliin.
Vinkkejä löydät artikkelista 101 LINQ Samples.
- Hae kaikki reseptit ja niihin liittyvät aineet ainemäärineen ja yksiköineen ja reseptin ohjerivit
- Hae reseptit ruokalajeittain lajiteltuna (group by)
- Laske montaako ainetta käytetään kuhunkin reseptiin
Lisätietoa
Seuraavia artikkeleita kannattaa lukea viimeistään demotehtäviä tehdessä.
LINQ insinöörityö
Introduction to the Entity Framework
Converting SQL to LINQ, Part 1: The Basics
Many To Many Mappings in Entity Framework
Many to Many Relationships in the Entity Data Model
Take Row-Level Control of Your GridView
How to: Manage Local Data Files in Your Project
LINQ to SQL - Many to Many Relationships
Concurrency violation: the UpdateCommand affected 0 of the expected 1 records
Käyttäjien kommentit