Visual Studio. Baza danych Windows Forms i Entity Framework

Dzisiejszy wpis będzie poświęcony wykorzystaniu Entity Framework w Visual Studio do projektowania aplikacji bazodanowej. Do podjęcia tej tematyki zdecydowałem się dlatego, że w sieci bardzo mało jest opisów dotyczących wykorzystania Entity Framework do budowania aplikacji typu desktop i Windows Forms. Zauważyłem także, że znakomita większość dostępnych tutoriali opisuje podejście z SQLem, bez użycia ORM. W mojej opinii niedostateczna ilość opisów programowania wykorzystującego ORMy to istotna luka, którą postanowiłem choć w niewielkim stopniu wypełnić. Opis będzie dotyczył prostej aplikacji z dwiema tabelami, relacjami oraz edycją danych w oknie typu dialog. Mam nadzieję, że w ten sposób przybliżę Państwu nieco wygodniejszy sposób pracy z bazami danych niż poprzez manipulację za pomocą SQL. Opiszę także problemy, jakie napotkałem podczas pracy.

Przykład oparty jest na bazie danych MS SQL Server o nazwie test i tabelach połączonych relacją:

USE [test] CREATE TABLE [dbo].[customers](
[id] [int] IDENTITY(1,1) NOT NULL,
[fullname] [varchar](150) NOT NULL,
[address] [varchar](150) NULL,
[city] [varchar](100) NULL,
CONSTRAINT [PK_customers] PRIMARY KEY CLUSTERED ( [id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY]
CREATE TABLE [dbo].[orders](
[id] [int] IDENTITY(1,1) NOT NULL,
[customer_id] [int] NOT NULL,
[date] [date] NOT NULL,
[article] [varchar](150) NOT NULL,
[price] [smallmoney] NOT NULL,
CONSTRAINT [PK_orders] PRIMARY KEY CLUSTERED
(
[id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[orders] WITH CHECK ADD CONSTRAINT [FK_orders_customers] FOREIGN KEY([customer_id])
REFERENCES [dbo].[customers] ([id])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[orders] CHECK CONSTRAINT [FK_orders_customers]
GO

Po utworzeniu bazy danych oraz tabel, należy dodać kilka przykładowych danych. Przejdź do MS SQL Management i dodaj dane do tabeli customers i orders:

Teraz zajmiemy się tworzeniem aplikacji w Visual Studio: Plik->Nowy->Projekt, Visual C#->Klasyczny pulpit systemu Windows->Aplikacja Windows Forms. Jako nazwę proszę wpisać Aplikacja1. Projekt został utworzony.

Pierwszym etapem pracy w Entity Framework jest pobranie do projektu biblioteki Entity Framework:

  1. Z menu Narzędzia, wybierz Menadżer pakietów NuGet->Zarządzaj pakietami NuGet rozwiązania.
  2. Kliknij na zakładkę Przeglądaj i w polu wyszukaj wpisz: Entity Framework.
  3. Wybierz z wyszukanych pakietów Entity Framework (bez żadnych dodatków). Pakiet ma około 40MB.
  4. Z prawej strony okna zaznacz chcekboxa przy aplikacji Aplikacja1.
  5. Kliknij: Zainstaluj.
  6. Potwierdź licencję frameworka (Akceptuję), aby dokończyć jego instalację.
  7. Możesz już zamknąć menadżera pakietów NuGet.



Teraz należy dodać do projektu nowy element: Ado.NET Entity Data Model

Przejdź do Eksploratora rozwiązań i kliknij prawym klawiszem myszy na nazwie projektu: Aplikacja1. Wybierz z menu kontekstowego: Dodaj->Nowy element…

Wyświetli się okno dialogowe, z którego wybierz z lewej strony: Dane, zaś z prawej: Ado.NET Entity Data Model. Nazwa Model1 może pozostać. Zatwierdź przyciskiem Dodaj. Z kolejnego okna dialogowego, wybierz EF Designer from database, gdyż będziemy generować modele na podstawie istniejącej bazy danych.

Kliknij Dalej i z listy Witch data connection… wybierz połączenie do twojego serwera. Jeśli nie ma żadnych połączeń, kliknij New Connection.

  1. W polu nazwa serwera wpisz: localhost (jeśli serwer MS SQL jest zainstalowany na twoim komputerze).
  2. Powinno być wybrane uwierzytelnienie: Uwierzytelnienie systemu Windows, gdy serwer SQL jest na twoim komputerze. Jeśli na innym, wybierz Uwierzytelnienie programu SQL i podaj login i hasło do MS SQL.
  3. Rozwiń pole Wybierz lub wprowadź bazę danych i wybierz z niego nowo utworzoną bazę o nazwie test.
  4. Kliknij: testuj połączenie. Jeśli połączenie jest poprawne, kliknij przycisk OK.

Teraz możesz pozostawić wszystko jak jest: kliknij Dalej.

Teraz wyświetli się okno, z którego należy wybrać obiekty, do których zostaną wygenerowane modele. Nas interesują jedynie dwie tabele: customers i orders. Zaznacz je i nie zmieniaj innych opcji. Po prostu kliknij Zakończ.

Aby obejrzeć wygenerowane modele, przejdź do Eksploratora rozwiązania i w sekcji Model1.tt zobaczysz kilka plików. Będą tam pliki nazwane tak, jak tabele w bazie. Możesz je przejrzeć, aby zapoznać się z ich zawartością. Oto plik customers.cs, zawierający model klientów. Zwróć uwagę, że zawiera on wszystkie pola z tabeli customers wraz z nadanymi typami zmiennych. Visual Studio odzwierciedla w ten sposób fizyczne tabele w bazie danych w tworzonych klasach.

public partial class customers
    {
        public int id { get; set; }
        public string fullname { get; set; }
        public string address { get; set; }
        public string city { get; set; }
    }

Jeśli zaś klikniesz dwa razy w Eksploratorze rozwiązania na pliku Model1.edmx, ujrzysz wizualną reprezentację klas wraz z relacjami.

W zasadzie to wystarczy, aby już rozpocząć pracę z kodem. Wykonajmy więc formularz główny naszej aplikacji, jak na rysunku poniżej.

Jak widać, mamy tutaj dwie kontrolki typu dataGridView (names: dataGridView1 oraz dataGridView2), przyciski u góry (names: bt_dodajKlienta, bt_edytujKlienta, bt_Koniec). Oraz dolne przyciski (names: bt_dodajZamowienie, bt_edytujZamowienie).

Teraz wypełnimy nasze kontrolki dataGridView danymi z odpowiednich tabel. Aby tego dokonać, kliknij dwa razy na formie i przejdź do edycji zdarzenia Form1_Load:

namespace Aplikacja1
{
    public partial class Form1 : Form
    {
        testEntities dane = new testEntities();
 
        public Form1()
        {
            InitializeComponent();
        }
 
        private void Form1_Load(object sender, EventArgs e)
        {
           dataGridView1.DataSource = dane.customers.ToList();
           dataGridView2.DataSource = dane.orders.ToList();
        }
    }
}

Jak widać, utworzyliśmy źródła danych dla naszych gridów. Spróbuj uruchomić aplikację. Jak widzisz, jest tutaj pewien problem – otóż klikając klienta, powinny wyświetlić się jedynie zamówienia z nim skojarzone. Niestety to nie zadziała. Innym problemem jest to, że nasze gridy wyświetlają nazwy pól z tabel i nie można tego skonfigurować wizualnie.

Rozwiązaniem jest stworzenie źródła danych, które będzie dostępne dla całej aplikacji. Dzięki temu, będzie można podłączać źródła danych z kontrolkami w sposób wizualny i jednocześnie manipulować danymi z poziomu kodu.



Tworzymy źródła danych.

Otwórz okno Źródła danych po lewej stronie okna Visual Studio i wybierz Dodaj nowe źródło danych... Pojawi się nowe okno dialogowe, z którego wybierz Obiekt i kliknij Dalej.

Następnie pojawi się okno, w którym należy rozwinąć gałąź Aplikacja1->Aplikacja1 oraz wybrać obiekty, do których chcemy utworzyć źródła danych. W naszym przypadku będzie to tabel customers oraz tabela orders.

Po zaznaczeniu tabel, należy kliknąć przycisk Zakończ.

 

Teraz możesz podłączyć dataGridView1 i dataGridView2 do źródła danych. W tym celu rozwiń menu podręczne w prawym rogu kontrolki dataGridView1 i wybierz – Inne źródła danych->Źródła danych projektu->customers, zaś dla dataGridView2, będzie to Inne źródła danych->Źródła danych projektu->orders.

Teraz można już w trybie projektowania podglądnąć układ w tabeli (nie będzie widać danych).

Teraz można poprawić nieco kolumny obu gridów, w tym celu ponownie otwórz menu (to samo, które służyło do podłączenia źródła danych) lub po prostu kliknij prawym klawiszem na dataGridView i wybierz opcję Edytuj kolumny i popraw wygląd kolumn.

  • id – Visible = false
  • fullname – HeaderText = Imię i nazwisko
  • address – HeaderText = Adres
  • city – HeaderText = Miasto
  • orders – usuń to pole.

Zatwierdź przyciskiem OK. Zaznacz ponownie dataGridView1 i w oknie właściwości zmień: MultiSelect = false, AutoSizeColumnMode = Fill.

Powtórz tę operację dla dataGridView2 (kolumny i właściwości):

  • id – Visible = false
  • customer_id – Visible = false
  • data – HeaderText = Data zamówienia
  • article – HeaderText = Przedmiot zamówienia
  • price – HeaderText = Cena
  • customers – usuń to pole.

Zatwierdź przyciskiem OK. Zaznacz dataGridView2 i w oknie właściwości zmień: MultiSelect = false, AutoSizeColumnMode = Fill.

Teraz należy załadować dane z bazy przy uruchamianiu aplikacji. W tym celu kliknij dwa razy na formatce, by wywołać wygenerowanie metody Form1_Load i wpisz:

private void Form1_Load(object sender, EventArgs e)
        {
            dataGridView1.DataSource = dane.customers.ToList();
        }

Teraz, po uruchomieniu aplikacji, wypełniona danymi z bazy zostanie kontrolka dataGridView1. Problem polega na tym, jak sprawić, by po zaznaczeniu dowolnego wiersza, w kontrolce dataGridView2 wyświetlić tylko zamówienia należące do zaznaczonego klienta. Idealnie byłoby, gdybym mógł odczytać bieżącą wartość id z tabeli customers. Tyle tylko, że mi się to nie udało. Skorzystałem więc z pobrania tej wartości z dataGridView1, która przechowuje klientów i „trzyma” id aktualnie zaznaczonego klienta. W mojej opinii nie jest to rozwiązanie eleganckie, ale działa bezproblemowo. W sieci znalazłem wiele przykładów, które opierały się waśnie na tej metodzie. A może ktoś z Państwa podpowie, jak „wyciągnąć” pole id zaznaczonego wiersza z customersBindingSource? Testowałem wiele rozwiązań i nie zadziałało.

Zaznaczamy dataGridView1, wyświetlające klientów, przechodzimy do okna właściwości i w zakładce zdarzenia generujemy metodę dataGridView1_SelectionChanged i wpisujemy kod obsługi zdarzenia:

private void dataGridView1_SelectionChanged(object sender, EventArgs e)
        {
            int id = Convert.ToInt32(this.dataGridView1.CurrentRow.Cells[0].Value);
            dataGridView2.DataSource = (from orders in dane.orders where
             orders.customer_id == id select orders).ToList();
        }

Jak widać na powyższym listingu, najpierw deklarujemy zmienną typu int i ustalamy jej wartość na podstawie pierwszej [0] kolumny w dataGridView1, czyli id klienta. Jeśli przy edycji kolumn dataGridView1 nazwalibyśmy tę kolumnę (parametr name np. na idKlienta), to wówczas można się do niej odwołać poprzez [„idKlienta”]. Jest to rozwiązanie lepsze, bowiem przy odwołaniu poprzez index [], zmiana kolejności kolumn w dataGridView1 może spowodować małą katastrofę. Jednakże jeśli przyjmujemy, że w naszych gridach pierwsza kolumna to zawsze id, nie ma żadnego problemu. Następnie wykonujemy select w Entity Framework wyciągający z tabeli orders jedynie te wiersze, których customer_id jest równe aktualnie zaznaczonemu wierszowi w customers.



Dodatkowo dodaj do formatki przycisk: name: btZapisz, text: Zapisz. Za pomocą tego przycisku przekonasz się, jak łatwe jest zapisywanie danych zmodyfikowanych w dataGridView do bazy danych.kliknij dwa razy na przycisku i dodaj obsługę zdarzenia:

private void btZapisz_Click(object sender, EventArgs e)
        {
            dane.SaveChanges();
        }

Teraz musimy obsłużyć zdarzenia – na początek przycisku Koniec. Będąc na formatce w trybie projektowania, kliknij dwa razy na przycisku koniec i dodaj zdarzenie:

private void bt_Koniec_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }

Od teraz, po uruchomieniu aplikacji, będzie można zapisywać zmiany dokonane w Gridach i zapisywać zmiany w bazie danych przypomocy przysisku Zapisz. Aby zakończyć program, można użyć przycisku zakończ.

Uruchom aplikację, modyfikuj dane i sprawdź, jak wszystko działa.

Programowe dodawanie nowych wierszy tabeli w bazie danych

Zanim zajmiemy się wykonaniem dynamicznego wyszukiwania, musimy wypełnić nasze tabele większą ilością danych. Oczywiście możemy to wykonać za pomocą SQL Management, ale chyba dobrym pomysłem będzie, gdy dokonamy tego za pomocą Entity Framework.

Dodajmy więc nowy przycisk (name: bt_generuj, Text: Generuj dane).

Kliknij dwa razy na nowo utworzonym przycisku Generuj dane i wpisz zdarzenie Click:

private void bt_Generuj_Click(object sender, EventArgs e)
        {
            dane.customers.Add(new customers { fullname = "Anna Kania", address = "Placowa 7", city = "Warszawa" });
            dane.customers.Add(new customers { fullname = "Krystyna Zawadzka", address = "Jana Pałwa 13", city = "Toruń" });
            dane.customers.Add(new customers { fullname = "Janusz Kowalski", address = "Piaskowa 3", city = "Warszawa" });
            dane.customers.Add(new customers { fullname = "Michał Pieróg", address = "Wolności 14", city = "Toruń" });
            dane.customers.Add(new customers { fullname = "Aleksander Wilecki", address = "Wolności 15", city = "Toruń" });
            dane.customers.Add(new customers { fullname = "Jan Bielecki", address = "Mostowa 87/1", city = "Stalowa Wola" });
            dane.SaveChanges();
            dataGridView1.DataSource = dane.customers.ToList();
        }

Uruchom aplikację i użyj przycisku Generuj dane. Zostaną wygenerowane nowi klienci.

Teraz możemy zrealizować wyszukiwanie klientów. Wyszukiwanie zrealizujemy za pomocą TextBox i oczywiście Entity Framework. Dodaj nową kontrolkę TextBox na formę jak na rysunku poniżej i wpisz jej właściwość Name na txtSzukaj.

Zaznacz nowo utworzoną kontrolkę txtSzukaj i wygeneruj dla niej zdarzenie KeyPress. Następnie wpisz obsługę zdarzenia.

private void txtSzukaj_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (txtSzukaj.Text.Trim().Length < 1)
            {
              dataGridView1.DataSource = dane.customers.ToList();
            }
            else
            {
               dataGridView1.DataSource = (from dane in dane.customers where
                               dane.fullname.Contains(txtSzukaj.Text.Trim())
                             || dane.address.Contains(txtSzukaj.Text.Trim())
                             || dane.city.Contains(txtSzukaj.Text.Trim())
                                 select dane).ToList();
            }
        }

Powyższy kod generowany jest wówczas, gdy użytkownik wciśnie dowolny klawisz w polu wyszukiwania txtSzukaj. Jeżeli pole zawiera mniej niż 1 znak, pobierane są wszystkie dane z tabeli customers. Jeżeli użytkownik wpisze dowolny ciąg znaków, przeszukiwane są pola w tabeli customers i na bieżąco wyświetlane są jedynie wiersze tabeli spełniające warunek. Wyszukiwanie odbywa się po polach fullname, address oraz city. Jednocześnie pobierane są dane do dataGridView2 (orders), więc na bieżąco można podglądać zamówienia zaznaczonego klienta – o ile takie istnieją w bazie danych.



Kolejny etap: dodawanie i edycja klientów w oknie modalnym DialogBox

Zajmiemy się teraz dodawaniem i edycją klientów. Tyle tylko, że przedstawię metodę na wykorzystanie dodatkowego okna dialogowego, w którym dane będą dodawane oraz edytowane. Zdecydowałem się na opisanie tego mechanizmu, gdyż wiele z dostępnych w sieci tutoriali idzie na łatwiznę i pokazuje edycję danych w tym samym oknie, co jest w mojej opinii mało przyjazne dla użytkownika.

  1. Przejdź do Eksploratora rozwiązań i kliknij prawym klawiszem myszy na naszej aplikacji Aplikacja1. Wybierz Dodaj->Nowy element i z sekcji Windows Forms wybierz Formularz systemu Windows. Nadaj mu nazwę: frmCustomers.cs.
  2. Dodaj do nowo utworzonego formularza dwa przyciski (name: btZapisz, text: Zapisz, DialogResult: OK) oraz (name: btAnuluj, text: Anuluj, DialogResult: Cancel). Dodaj 3 kontrolki TextBox (Name: txtFullname, txtAddress, txtCity). Dodaj także kontrolki label i jest opisz.
  3. Zmień właściwości formularza: Text: Dane klienta, StartPosition: CenterScreen, FormBorderStyle: FixedDialog, KeyPreview: True, AcceptButton: btZapisz, CancelButton: btAnuluj).


Gotowy formularz kartoteki klienta

Istotną kwestią jest przekazywanie i odbieranie zmiennych pomiędzy formularzami. Ja używam do tego prostego i sprawdzonego sposobu. Otóż korzystając z Entity Framework, możemy  przekazać do nowo tworzonego okna dialogowego jedynie id klienta oraz przyjąć, że jeśli wartość będzie wynosiła 0, to oznacza, że formularz ma za zadanie dodać nowego klienta. Jeśli zaś wartość zmiennej id będzie większa od 0, formularz ma wczytać dane klienta o tym id. Przejdźmy więc do kodu źródłowego naszej formatki frmCustomer. Oto pełny kod źródłowy.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
 
namespace Aplikacja1
{
    public partial class frmCustomers : Form
    {
        testEntities dane = new testEntities();
        static int id;
 
        public frmCustomers(int idrekordu)
        {
            InitializeComponent();
            id = idrekordu;
        }
 
        private void btZapisz_Click(object sender, EventArgs e)
        {
            if (id>0)  // Edycja - tylko zapis
            {
             var klient = dane.customers.FirstOrDefault(f => f.id == id);
                 klient.fullname = txtFullname.Text.Trim();
                 klient.address = txtAddress.Text.Trim();
                 klient.city = txtCity.Text.Trim();
                dane.SaveChanges();
            } else // Dopisywanie - dodane rekordu
            {
                dane.customers.Add(new customers { fullname = txtFullname.Text.Trim(), address = txtAddress.Text.Trim(), city = txtCity.Text.Trim() });
                dane.SaveChanges();
            }
        }
 
        private void frmCustomers_Load(object sender, EventArgs e)
        {
            if (id > 0)
            {
                var klient = dane.customers.FirstOrDefault(f => f.id == id);
                if (klient != null)
                {
                    txtFullname.Text = klient.fullname;
                    txtAddress.Text = klient.address;
                    txtCity.Text = klient.city;
                }
            }
 
        }
    }
}

Proszę mi wierzyć, że najprostsze sposoby są najlepsze i w tym przypadku także się to sprawdza. Proszę zwrócić uwagę, że przy public inicjacji formatki za pomocą frmCustomers(int idrekordu), dodałem nową zmienną, którą należy przekazać dynamicznie tworząć formularz.

Teraz przejdźmy do Form1 i dodajmy zdarzenie dla przycisku Dodaj (klienta):

private void bt_dodajKlienta_Click(object sender, EventArgs e)
        {
            frmCustomers klient = new frmCustomers(0);
            klient.ShowDialog();
            OdsiezGridCustomers();
        }

Oraz dla przycisku Edytuj klienta:

private void bt_edytujKlienta_Click(object sender, EventArgs e)
        {
            frmCustomers klient = new frmCustomers(Convert.ToInt32(this.dataGridView1.CurrentRow.Cells[0].Value));
            klient.ShowDialog();
            OdswiezGridCustomers();
        }
 
 
private void OdswiezGridCustomers()
{
int saveRow = 0;
if (dataGridView1.Rows.Count > 0)
saveRow = dataGridView1.FirstDisplayedCell.RowIndex;
testEntities dane = new testEntities();
dataGridView1.DataSource = dane.customers.ToList();
if (saveRow != 0 && saveRow < dataGridView1.Rows.Count)
dataGridView1.FirstDisplayedScrollingRowIndex = saveRow;
// Odświeżamy również dataGridView1 - orders
dataGridView2.DataSource = (from orders in dane.orders
where orders.customer_id == saveRow
select orders).ToList();
}

W przypadku edycji pobieramy id klienta z zaznaczonego wiersza DataGridView1 i przekazujemy do drugiego formularza. A ten wie, że jeśli id jest większe od 0, wyświetli wiersz w trybie edycji. Ogromne problemy miałem z… odświeżeniem danych w gridzie. Otóż nie wystarczy ponownie wywołać dataGridView1.DataSource = dane.customers.ToList(). Pomogło dopiero wywołanie wcześniej testEntities dane = new testEntities(). Przyznam, że o ile w Entity Framework manipulacja danymi jest prosta, to podstawowe czynności czasem wymagają wielu godzin poszukiwań w sieci. Nie jestem ekspertem od c# i Entity. Tego typu problemy nie występują np w Delphi czy Lazarusie, gdzie odświeżenie polega na wywołaniu metody Refresh, zaś reszta jest wykonywana automatycznie. Nie wiem dlaczego Microsoft aż tak komplikuje podstawowe sprawy. W każdym razie kod działa poprawnie, ale czy jest to kod napisany z zachowaniem standardów, które ustalił producent – szczerze wątpię.

Formularz dodawania i edycji zamówień

Teraz zbudujemy formularz zamówień. Rozpoczniemy od utworzenia nowej formatki wywoływanej modalnie.

  1. Przejdź do Eksploratora rozwiązań i kliknij prawym klawiszem myszy na naszej aplikacji Aplikacja1. Wybierz Dodaj->Nowy element i z sekcji Windows Forms wybierz Formularz systemu Windows. Nadaj mu nazwę: frmOrders.cs.
  2. Dodaj do nowo utworzonego formularza dwa przyciski (name: btZapisz, text: Zapisz, DialogResult: OK) oraz (name: btAnuluj, text: Anuluj, DialogResult: Cancel). Dodaj kontrolkę DateTImePicker (Format: short) i 2 kontrolki TextBox (Name: txtArticle, txtPrice). Dodaj także kontrolki label i je opisz.
  3. Zmień właściwości formularza: Text: Zamówienia, StartPosition: CenterScreen, FormBorderStyle: FixedDialog, KeyPreview: True, AcceptButton: btZapisz, CancelButton: btAnuluj).

Kod formularza jest bardzo podobny do formularza klientów, jednakże tym razem wymaga on podania dwóch zmiennych public frmOrders(int idrekordu, int idklienta): Identyfikatora rekordu oraz customer_id, za  pomocą którego przy dodawaniu nowego zamówienia, dodany wiersz w tabeli orders będzie przypisany do klienta. Oto kod źródłowy frmOrders:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
 
namespace Aplikacja1
{
    public partial class frmOrders : Form
    {
        testEntities dane = new testEntities();
        static int id, idkli;
 
        public frmOrders(int idrekordu, int idklienta)
        {
            InitializeComponent();
            id = idrekordu;
            idkli = idklienta;
        }
 
        private void btZapisz_Click(object sender, EventArgs e)
        {
            if (id > 0)  // Edycja - aktualizacja rekordu
            {
                var zamowienie = dane.orders.FirstOrDefault(f => f.id == id);
                zamowienie.date = txtDate.Value;
                zamowienie.customer_id = idkli;
                zamowienie.article = txtArticle.Text.Trim();
                zamowienie.price = decimal.Parse(txtPrice.Text.Trim());
                dane.SaveChanges();
            }
            else // Dopisywanie - dodane rekordu
            {
                dane.orders.Add(new orders { date = txtDate.Value, customer_id = idkli, article = txtArticle.Text.Trim(), price = decimal.Parse(txtPrice.Text.Trim()) });
                dane.SaveChanges();
            }
        }
 
        private void frmOrders_Load(object sender, EventArgs e)
        {
            if (id > 0)
            {
                var zamowienie = dane.orders.FirstOrDefault(f => f.id == id);
                if (zamowienie != null)
                {
                    txtDate.Text = zamowienie.date.ToString();
                    txtArticle.Text = zamowienie.article;
                    txtPrice.Text = zamowienie.price.ToString();
                }
            }
        }
    }
}

I wszystko wygląda prosto, tyle tylko, że znów występuje problem z odświeżeniem – tym razem dataGridView2, zawierającym zlecenia. Niestety nie działa metoda OdswiezGridCustomers(), choć chyba powinna. Entity Framework ponownie niemile mnie zaskoczył i nie chce mi się już z tym walczyć.

Oto pełny kod Form1.cs – nieco zmodyfikowany.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Core.Objects;
using System.Data.Entity.Infrastructure;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
 
namespace Aplikacja1
{
    public partial class Form1 : Form
    {
        testEntities dane = new testEntities();
 
        public Form1()
        {
            InitializeComponent();
        }
 
        private void Form1_Load(object sender, EventArgs e)
        {
            dataGridView1.DataSource = dane.customers.ToList();
        }
 
        private void dataGridView1_SelectionChanged(object sender, EventArgs e)
        {
            int id = Convert.ToInt32(this.dataGridView1.CurrentRow.Cells[0].Value);
            dataGridView2.DataSource = (from orders in dane.orders
                                        where orders.customer_id == id
                                        select orders).ToList();
        }
 
        private void btZapiszKlienta_Click(object sender, EventArgs e)
        {
 
        }
 
        private void bt_Koniec_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }
 
        private void btZapisz_Click(object sender, EventArgs e)
        {
            dane.SaveChanges();
        }
 
        private void bt_Generuj_Click(object sender, EventArgs e)
        {
            dane.customers.Add(new customers { fullname = "Anna Kania", address = "Placowa 7", city = "Warszawa" });
            dane.customers.Add(new customers { fullname = "Krystyna Zawadzka", address = "Jana Pałwa 13", city = "Toruń" });
            dane.customers.Add(new customers { fullname = "Janusz Kowalski", address = "Piaskowa 3", city = "Warszawa" });
            dane.customers.Add(new customers { fullname = "Michał Pieróg", address = "Wolności 14", city = "Toruń" });
            dane.customers.Add(new customers { fullname = "Aleksander Wilecki", address = "Wolności 15", city = "Toruń" });
            dane.customers.Add(new customers { fullname = "Jan Bielecki", address = "Mostowa 87/1", city = "Stalowa Wola" });
            dane.SaveChanges();
            dataGridView1.DataSource = dane.customers.ToList();
        }
 
        private void txtSzukaj_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (txtSzukaj.Text.Trim().Length < 1)
            {
              dataGridView1.DataSource = dane.customers.ToList();
            }
            else
            {
               dataGridView1.DataSource = (from dane in dane.customers where
                               dane.fullname.Contains(txtSzukaj.Text.Trim())
                             || dane.address.Contains(txtSzukaj.Text.Trim())
                             || dane.city.Contains(txtSzukaj.Text.Trim())
                                 select dane).ToList();
            }
        }
 
        private void bt_dodajKlienta_Click(object sender, EventArgs e)
        {
            frmCustomers klient = new frmCustomers(0);
            klient.ShowDialog();
            OdswiezGridCustomers();
        }
 
        private void bt_edytujKlienta_Click(object sender, EventArgs e)
        {
            frmCustomers klient = new frmCustomers(Convert.ToInt32(this.dataGridView1.CurrentRow.Cells[0].Value));
            klient.ShowDialog();
            OdswiezGridCustomers();
        }
 
        private void OdswiezGridCustomers()
        {
            int saveRow = 0;
            if (dataGridView1.Rows.Count > 0)
                saveRow = dataGridView1.FirstDisplayedCell.RowIndex;
 
            testEntities dane = new testEntities();
            dataGridView1.DataSource = dane.customers.ToList();
 
            if (saveRow != 0 && saveRow < dataGridView1.Rows.Count)
                dataGridView1.FirstDisplayedScrollingRowIndex = saveRow;
 
          // Odświeżamy również dataGridView1 - orders
            dataGridView2.DataSource = (from orders in dane.orders
                                        where orders.customer_id == saveRow
                                        select orders).ToList();
        }
 
 
        private void bt_dodajZamowienie_Click(object sender, EventArgs e)
        {
            frmOrders zamowienia= new frmOrders(0, Convert.ToInt32(this.dataGridView1.CurrentRow.Cells[0].Value));
            zamowienia.ShowDialog();
            OdswiezGridCustomers();
        }
 
        private void bt_edytujZamowienie_Click(object sender, EventArgs e)
        {
            frmOrders zamowienia = new frmOrders(Convert.ToInt32(this.dataGridView2.CurrentRow.Cells[0].Value), Convert.ToInt32(this.dataGridView1.CurrentRow.Cells[0].Value));
            zamowienia.ShowDialog();
            OdswiezGridCustomers();
        }
 
        private void bt_usunZamowienie_Click(object sender, EventArgs e)
        {
            DialogResult dialogResult = MessageBox.Show("Usunąć tę pozycję?", "Potwierdź", MessageBoxButtons.YesNo);
            if (dialogResult == DialogResult.Yes)
            {
                int idzam = Convert.ToInt32(this.dataGridView2.CurrentRow.Cells[0].Value);
                var zamowienie = dane.orders.FirstOrDefault(f => f.id == idzam);
                dane.orders.Remove(zamowienie);
                dane.SaveChanges();
                OdswiezGridCustomers();
            }
        }
    }
}

Źródła aplikacji można pobrać stąd. Po rozpakowaniu znajdziesz także plik KopiaBazySQL.bak dla MS SQL Server 2012 wraz z danymi.

Do zrobienia

Aplikacja jest tylko przykładem uzycia Entity Framework. Usuwanie klientów czy oprogramowanie walidacji pozostawiam czytelnikowi. Ciekawostka: bardzo dziwne, że Entity Framework pozwala dopisać nowy rekord nawet wówczas, gdy nie podamy wartości a rekord w tabeli ma właściwość not empty. I to nawet wówczas, gdy przypisuję pole do encji ze źródłowego TextBox.Text.Trim().  Tak więc można dodawać puste rekordy i zupełnie nie mam pojęcia, jak to wytłumaczyć.

Zakończenie

Zdaję sobie sprawę, że zbyt mało wiem na temat Entity Framework i stąd niedociągnięcia w kodzie. Jednakże w tym artykule umieściłem szereg aspektów, z którymi mam problemy a które są słabo opisane w sieci. Być może odezwie się ktoś, kto będzie w stanie wskazać mi błędy i wytłumaczyć, że powinno to działać inaczej, niż zostało przedstawione. Kod został przetestowany i działa, jednakże muszę przyznać, że Visual Studio wydaje się być niezwykle trudne w najprostszych operacjach. Trudności, które napotkałem, wydają się niespotykane w środowiskach Delphi czy Lazarus. Zupełnie nie rozumiem, dlaczego Microsoft aż tak bardzo skomplikował elementarne przecież sprawy. Być może, gdy będę miał czas, napiszę identyczny artykuł opisujący stworzenie analogicznego programu w bezpłatnym Lazarusie, byście mogli Państwo porównać ilość koniecznego do napisania kodu, poziom komplikacji i ilość występujących pułapek. Przekonacie się, że w Lazarusie tego typu banalnie prostych przykładach takie problemy nie występują.

1121total visits,3visits today

Tagi , .Dodaj do zakładek Link.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

41 + = 49

This site uses Akismet to reduce spam. Learn how your comment data is processed.