Elektroda.pl
Elektroda.pl
X
Proszę, dodaj wyjątek www.elektroda.pl do Adblock.
Dzięki temu, że oglądasz reklamy, wspierasz portal i użytkowników.

[C#] Program na SmartDevice wykorzystujący bazę danych MS SQL Server

danon198901 01 Lut 2012 21:14 2274 12
  • #1 01 Lut 2012 21:14
    danon198901
    Poziom 11  

    Witam,
    z racji potrzeby nauki języka C# (+bazy danych) zacząłem pisać sobie programiki w VS2008.
    Ogólnie chcę stworzyć aplikację na Smart Deviace, która wykorzystywać będzie bazę danych MS SQL Server oraz drobną grafikę, którą mam zamiar wdrożyć (ale tutaj jeszcze informacji nie zebrałem, więc jest to zamysł przyszłościowy).

    Problem mam następujący, że chciałem prostym sposobem sprawdzić czy login i hasło, które wprowadza nowy użytkownik tworząc własne konto jest zapamiętane w bazie i w nowym Form() wywołuję odpowiednie działanie obsługi buttona "Pokaż". Z programowaniem tak na dobrą sprawę zaczynam, więc proszę o wyrozumiałość:)

    Poniżej prezentuje kod okna "tworzenia konta" oraz okno "sprawdzenia bazy".

    Tworzenie konta:

    Kod: csharp
    Zaloguj się, aby zobaczyć kod



    Okno w, którym chcę sprawdzić zawartość owej bazy:
    Kod: csharp
    Zaloguj się, aby zobaczyć kod



    Gdzie popełniłem błąd?

    POZDRAWIAM

    0 12
  • Pomocny post
    #2 01 Lut 2012 21:50
    marcinj12
    Poziom 40  

    A czym się objawia ów błąd, który popełniłeś??

    Jak dla mnie trochę dziwne jest tworzenie bazy danych w programie, jeśli takowa nie istnieje - czy ona nie powinna być od początku stworzona? Poza tym po co pytasz użytkownika o nazwę i hasło, skoro potem nic z nimi nie robisz (chyba że nie umieściłeś całości kodu).

    Niepotrzebnie deklarujesz wszystkie zmienne prywatnie na górze klasy - jeżeli można, lepiej robić to inline, w większości przypadków im krócej obiekt żyje, tym lepiej... Warto też używać do takich obiektów jak połączenie czy readery sekcji using()..., która zwalnia zasób (wywołuje metodę Dispose() obiektu, a w przypadku połączenia do bazy również je wcześniej zamyka) kiedy się kończy, np.

    Kod: csharp
    Zaloguj się, aby zobaczyć kod
    (oczywiście w tym przykładzie wypadało by przekazać polaczenie jak argument do metody createDatabase();)

    Sugeruję używanie using () do każdego obiektu, któremu możesz wywołać metodę zwalniającą .Dispose() - chyba że np. chcesz trzymać otwarte połączenie do bazy przez cały czas działania programu.

    No właśnie - pierwsza rzecz która mi się rzuciła w oczy, to że zarówno w konstruktorze NewUser() otwierasz połączenie do bazy, ale go nie kończysz, jak również robisz to samo w konstruktorze okna MainBuilder() - przez co masz otwarte jednocześnie co najmniej dwa połączenia do bazy.

    Taki sposób przechodzenia z okna do okna jak zastosowałeś (że tworzysz i otwierasz nowe okno, a ukrywasz okno główne) też nie jest zbyt dobre. Czy nie lepiej uczynić oknem głównym MainBuilder(), na starcie którego będzie otwierane okienko do podania loginu: NewUser() ?

    PS. Jak wklejasz kod na forum, użyj znacznika SYNTAX dla C# i stosuj wcięcia kodu, bo ciężko się czyta...

    0
  • #3 01 Lut 2012 21:51
    danon198901
    Poziom 11  

    ehhh...za dużo przed komputerem i człowiek głupie błędy robi. Brakowało

    Kod: csharp
    Zaloguj się, aby zobaczyć kod

    przy obsłudze button1 w NewUser Form.


    A teraz mam pytanko...jak zrobić, żeby nie trzeba było wpisywać ręcznie (wpisać do textbox'a3 wartość) id_osoba, a żeby program automatycznie sam nadawał pierwsze, wolne id. Tzn, w bazie mam np 4 użytkowników. Pierwszy zarejestrowany dostaje id 1, drugi id 2. Więc mając 4 użytkowników kolejna osoba dostanie automatycznie id=5.
    Jak wprowadzić taki mechanizm??


    --EDIT--
    Cytat:
    Jak wklejasz kod na forum, użyj znacznika SYNTAX dla C# i stosuj wcięcia kodu, bo ciężko się czyta...

    OK...zaraz poprawię.
    Dzięki za odpowiedź, jutro postaram się nanieść poprawki, bo jak mówiłem zaczynam w zasadzie programowanie, a nie wszystko co mi napisałeś jest dla mnie tak oczywiste:)

    0
  • Pomocny post
    #4 01 Lut 2012 22:03
    marcinj12
    Poziom 40  

    danon198901 napisał:
    jak zrobić, żeby nie trzeba było wpisywać ręcznie id_osoba, a żeby program automatycznie sam nadawał pierwsze, wolne id
    Na etapie tworzenia bazy musisz określić pole jako Is Identity. W programach do graficznego zarządzania bazą, jak SQL Server Management Studio czy wbudowana w Visual Studio jego okrojona wersja na siatce właściwości po zaznaczeniu pola bazy danych masz właściwość (Is Identity), którą ustawiasz na Yes.
    Jeżeli tworzysz kod "ręcznie", jak w Twoim przypadku, to - tu do końca pewien nie jestem - ale chyba musisz dopisać coś takiego:
    Kod: csharp
    Zaloguj się, aby zobaczyć kod

    0
  • #5 02 Lut 2012 16:05
    danon198901
    Poziom 11  

    Dziękuję raz jeszcze za zainteresowanie, a teraz przejdę do rzeczy:

    marcinj12 napisał:

    Jak dla mnie trochę dziwne jest tworzenie bazy danych w programie, jeśli takowa nie istnieje - czy ona nie powinna być od początku stworzona?


    To ma być aplikacja na Smart Deviace jak wspomniałem wcześniej. W skład aplikacji będzie wchodziło na dzień dzisiejszy: logowanie/tworzenie konta, wybór pomieszczeń z czujnikami temperatury - wirtualnymi i edycja konta (zmiana nazw pomieszczeń/czujników/dodawanie ich) i jeszcze kilka pomysłów, które wykorzystam. Z jednego urządzenia może korzystać kilka osób, dlatego niezbędne jest wczytywanie wcześniej "wyklikanych" informacji.
    Samo tworzenie bazy (tabel) umieściłem już w głównym oknie, gdzie do wyboru mamy przejście do logowania (sprawdzenie w bazie czy istnieje użytkownik + poprawność hasła) lub tworzenie nowego użytkownika (INSERT do bazy)...no chyba, że masz na myśli coś innego?:)
    Ten projekt jest w fazie zarodka, więc całości kodu nie dałem, a fakt nr.2 jest taki, że niewiele tego mam na daną chwilę.

    marcinj12 napisał:

    Niepotrzebnie deklarujesz wszystkie zmienne prywatnie na górze klasy - jeżeli można, lepiej robić to inline...

    Tzn?

    marcinj12 napisał:

    No właśnie - pierwsza rzecz która mi się rzuciła w oczy, to że zarówno w konstruktorze NewUser() otwierasz połączenie do bazy, ale go nie kończysz, jak również robisz to samo w konstruktorze okna MainBuilder() - przez co masz otwarte jednocześnie co najmniej dwa połączenia do bazy.

    Słuszna uwaga:) Teraz poprawiłem i za każdym nowym działaniem na bazie np. dodawanie użytkownika, odnawiam połączenie i zamykam na nowo.

    marcinj12 napisał:

    Taki sposób przechodzenia z okna do okna jak zastosowałeś (że tworzysz i otwierasz nowe okno, a ukrywasz okno główne) też nie jest zbyt dobre. Czy nie lepiej uczynić oknem głównym MainBuilder(), na starcie którego będzie otwierane okienko do podania loginu: NewUser() ?

    Pierwsze okno to okno wyboru: czy jestem nowym użytkownikiem, czy mam już konto i chcę kontynuować działanie na wcześniej utworzonym. W zależności od wyboru, albo przechodzę do logowania, albo do utworzenia konta. Po zalogowaniu nastąpi przejście do głównego podglądu wcześniej 'wyklikanych' opcji. Kiedy tworzę konto, nastąpi przejście do kreatora konfiguracji. Tam sobie to wyimaginowałem, dlatego też tak napisałem. Mógłbym to skrócić o jeden Form mniej, bo np. pierwsze okno było by z textboxami: login i hasło i dodatkowo radiobutton: loguj i utwórz konto z instrukcją warunkową if (sprawdzenie czy w bazie jest dany login itp). Ale czy jeden Form mniej zmieni coś? No, bo fakt jest taki, że stare okno przykrywam (this.Hide())...rozumiem, że jest ono dalej aktywne i zajmuje jakąś część pamięci tak?

    marcinj12 napisał:

    Jeżeli tworzysz kod "ręcznie", jak w Twoim przypadku, to - tu do końca pewien nie jestem - ale chyba musisz dopisać coś takiego:
    CREATE TABLE osoba(id_osoba int IDENTITY(1,1) PRIMARY KEY, login ntext, haslo ntext)

    Dopisałem jak radziłeś. Dodatkowo zmieniłem obsługę buttona odpowiedzialnego za tworzenie nowego użytkownika:

    Kod: csharp
    Zaloguj się, aby zobaczyć kod


    I tu niestety pojawia się błąd o niezgadzającej się liczbie kolumn:(
    Cytat:
    The number of columns in the query and the table must match. [ Number of columns in query = 2, Number of columns in table = 3]


    Jakiś pomysł?

    0
  • Pomocny post
    #6 02 Lut 2012 17:54
    marcinj12
    Poziom 40  

    danon198901 napisał:
    ...no chyba, że masz na myśli coś innego?:)
    Jak wspomniałem nie znam się na Smart Device'ach, tworzenie bazy z poziomu programu dalej uważam za "nietypowe" rozwiązanie, ale nie wiem, może tam to się tak robi, więc już nic o tym wspominać nie będę :)
    danon198901 napisał:
    marcinj12 napisał:

    Niepotrzebnie deklarujesz wszystkie zmienne prywatnie na górze klasy - jeżeli można, lepiej robić to inline...

    Tzn?

    Tzn. że to wszytko:
    Kod: csharp
    Zaloguj się, aby zobaczyć kod
    możesz usunąć, a typ deklarować w tej samej linii co zmienna, której używasz. Oczywiście, jeżeli nie potrzebujesz jej widocznej i dostępnej w całej klasie. Ja np. zdarzenie button1_click klasy MainBuilder z czegoś takiego:
    Kod: csharp
    Zaloguj się, aby zobaczyć kod
    przerobił na takie coś:
    Kod: csharp
    Zaloguj się, aby zobaczyć kod
    Zmienną connectionString zadeklarował gdzieś w klasie, żeby za każdym razem nie powtarzać [w miejscu tego, co usunąłeś], a jeszcze lepiej w pliku z ustawieniami aplikacji.
    using() na pewno zostawił na połączeniu, przy command i datareaderze można dyskutować, ja zawsze daję skoro klasa implementuje Dispose(), sam MSDN nie ma w tej kwestii utartego schematu.

    Zamiast dodawania przez += wykorzystał bym klasę StringBuilder(), a przynajmniej uprościł sobie życie stosując
    Kod: csharp
    Zaloguj się, aby zobaczyć kod
    , a w ogóle to skorzystał z listy, nie textboxa :)

    danon198901 napisał:
    No, bo fakt jest taki, że stare okno przykrywam (this.Hide())...rozumiem, że jest ono dalej aktywne i zajmuje jakąś część pamięci tak?
    Tak. Jeżeli to samo robisz z tym pierwszym oknem wyboru - to tzw. pompa komunikatów (message pump) będzie ciągle przekazywała dane do tej formy. Mówiąc prościej - ukryta forma nie tylko będzie aktywna i w pamięci, ale program nie zakończy się tak długo, jak jej nie zamkniesz (przyciskiem X lub odpowiednim poleceniem). Dlatego najlepiej, jak formą "główną" jest forma, z którą użytkownik pracuje większość czasu i którą zamyka, gdy chce wyjść z programu.
    Jeżeli wychodząc z formy MainBuilder nie przywrócisz i/lub nie zamkniesz tej pierwszej, ukrytej formy, aplikacja się nie zamknie i proces będzie cały czas wisiał w pamięci aż do restartu.
    Jeżli już chcesz tak robić to lepiej w ten sposób:
    Kod: csharp
    Zaloguj się, aby zobaczyć kod
    Forma główna "ukryje się", otworzy nową formę jako Dialog i "wstrzyma się" z zamknięciem aż do zamknięcia Form2.

    danon198901 napisał:
    I tu niestety pojawia się błąd o niezgadzającej się liczbie kolumn:(
    W poleceniu INSERT wyszczególnij nazwy kolumn, do których wstawiasz wartości (login i haslo).

    PS. Zaleca się stosować typy zmiennych z małych lister: string, int etc.

    0
  • #7 03 Lut 2012 19:10
    danon198901
    Poziom 11  

    OK. Naniosłem poprawki wg. zaleceń:)

    marcinj12 napisał:
    Zamiast dodawania przez += wykorzystał bym klasę StringBuilder(), a przynajmniej uprościł sobie życie stosując
    , a w ogóle to skorzystał z listy, nie textboxa Smile

    W programowaniu jestem zielony raczej - etap nauki:)
    Z klasą StringBuilder nie miałem jeszcze do czynienia, ale nie omieszkam zapoznać się z nią.
    Textbox posłużył bardziej do testu, czy w bazie zapisuje się wszystko czy też nie. Wykorzystam to w panelu admina z narzędziami edycji i na pewno nie będzie to textbox:)


    marcinj12 napisał:
    W poleceniu INSERT wyszczególnij nazwy kolumn, do których wstawiasz wartości (login i haslo).


    Tu nic nie zmieniłem:
    Kod: csharp
    Zaloguj się, aby zobaczyć kod


    Tutaj wyszczególniłem kolumny w których chcę umieścić dane:
    Kod: csharp
    Zaloguj się, aby zobaczyć kod


    Mimo wszystko uzyskałem następujący komunikat, jakoby kolejna wartość nie została nadana automatycznie i id nowego użytkownika jest NULLem:
    Cytat:
    The column cannot contain null values. [ Column name = id_osoba, Table name = osoba ]

    0
  • #8 03 Lut 2012 19:58
    marcinj12
    Poziom 40  

    danon198901 napisał:
    W programowaniu jestem zielony raczej - etap nauki:)
    Z klasą StringBuilder nie miałem jeszcze do czynienia, ale nie omieszkam zapoznać się z nią.
    Polecam :) Generalnie jest to dosyć prosta w obsłudze klasa, którą warto stosować w przypadku dołączania stringów za pomocą + , jeżeli ilość członów jest większa niż (umownie) 5..10. Nie tworzy ona nowego obiektu w pamięci za każdym razem, przez co przy większej ilości takich złączeń jest zauważalnie szybsza. Przy listboxie czy innej kontrolce już nie będzie takiej potrzeby.

    danon198901 napisał:
    Mimo wszystko uzyskałem następujący komunikat, jakoby kolejna wartość nie została nadana automatycznie i id nowego użytkownika jest NULLem:
    Tutaj przyznaję się nie wiem o co chodzi, nie dopisałem tego w poprzednim poście co wyglądało, jakbym jeszcze raz to podkreślał, a to miał być tylko cytat ;) Powiem tak - na wersji na PC'cie to działa, sam testowałem. Nie mam pojęcia, dlaczego dostajesz taki komunikat na urządzeniu mobilnym... Możesz próbować wykonać przed wstawieniem zapytanie:
    Kod: sql
    Zaloguj się, aby zobaczyć kod

    jak poniżej, ale nie mam pojęcia czy to zadziała:
    Kod: csharp
    Zaloguj się, aby zobaczyć kod

    PS. Idealnie by było, gdybyś od samego początku nabierał "dobrych nawyków" i zamiast sklejania zapytania SQL stosował parametry. Zapobiega to "włamom", błędom przy wstawianiu tekstu z apostrofami i jest po prostu dobrą praktyką. W najprostszej wersji, z poleceniem AddWithValues, polecam taki wzór jak poniżej:
    Kod: csharp
    Zaloguj się, aby zobaczyć kod

    0
  • #9 04 Lut 2012 18:09
    danon198901
    Poziom 11  

    Mam takie pytanie odnośnie wcześniejszej wypowiedzi:

    marcinj12 napisał:
    używać do takich obiektów jak połączenie czy readery sekcji using()..., która zwalnia zasób (wywołuje metodę Dispose() obiektu, a w przypadku połączenia do bazy również je wcześniej zamyka) kiedy się kończy, np.


    Czyli np. w tym wypadku:

    Kod: csharp
    Zaloguj się, aby zobaczyć kod


    Zamknięcie polecenia poprzez metodę Close() (patrz $$) nie jest potrzebne, gdyż korzystając z using() dla każdego obiektu wywołuje się domyślnie Dispose(), a dla połączenia z bazą Close(). Dobrze cię zrozumiałem?


    marcinj12 napisał:
    Możesz próbować wykonać przed wstawieniem zapytanie:

    SET identity_insert osoba off


    A to ciekawe, bo dostałem komunikat:
    Cytat:
    Table does not have a IDENTITY column


    Mimo, że jest zadeklarowana jako 'IDENTITY'. Próbowałem zarówno z off jak i on...message ten sam :cry:
    Kod: csharp
    Zaloguj się, aby zobaczyć kod


    Może jakiś inny pomysł??:)

    0
  • Pomocny post
    #10 04 Lut 2012 18:47
    marcinj12
    Poziom 40  

    danon198901 napisał:
    Zamknięcie polecenia poprzez metodę Close() (patrz $$) nie jest potrzebne, gdyż korzystając z using() dla każdego obiektu wywołuje się domyślnie Dispose(), a dla połączenia z bazą Close(). Dobrze cię zrozumiałem?
    Dokładnie tak. Metoda Dispose() jest wywoływana na "wyjściu" z sekcji using{}, a w przypadku połączenia do bazy danych standardem jest, że metoda Dispose() połączenia wywołuje niejawnie metodę Close(). Oczywiście nic się nie stanie, jeżeli wywołasz ją wcześniej "ręcznie", tyle, że dojdzie Ci dodatkowa linijka kodu...


    danon198901 napisał:
    Może jakiś inny pomysł??:)
    Tylko jeden: stwórz tą bazę z poziomu Visual Studio (w Database Explorer), ustaw kolumnie id_osoba wartość (IsIdentity) na siatce i przekopiuj tak utworzoną bazę na urządzenie mobilne (tylko upewnij się, że kopiujesz właściwą), powinna być w katalogu Debug lub Realese.

    Ewentualnie, choć to raczej obejście tematu, możesz uzyskać maksymalną wartość id_osoba robiąc zapytanie:
    Kod: sql
    Zaloguj się, aby zobaczyć kod
    przed zrobieniem INSERTa, a w nim podać uzyskaną wartość + 1. (z tego co widze wersja CE SqlServera nie obsługuje podzapytań, więc jednym zapytaniem tego nie zrobisz...)

    PS. Rozumiem, że kiedy kolumna nie była ustawiona jako IsIdentity, to wstawiało Ci rekordy normalnie, były one w bazie i mogłeś je wyświetlić??

    0
  • #11 04 Lut 2012 23:08
    danon198901
    Poziom 11  

    marcinj12 napisał:
    PS. Rozumiem, że kiedy kolumna nie była ustawiona jako IsIdentity, to wstawiało Ci rekordy normalnie, były one w bazie i mogłeś je wyświetlić??


    Kiedy miałem ustawione:
    Kod: csharp
    Zaloguj się, aby zobaczyć kod

    Kod: csharp
    Zaloguj się, aby zobaczyć kod

    dostałem następującą wiadomość:
    Cytat:
    The column cannot contain null values. [ Column name = id_osoba, Table name = osoba ]


    Kiedy dodałem:
    Kod: csharp
    Zaloguj się, aby zobaczyć kod

    wraz ze stosowną zmianą:
    Kod: csharp
    Zaloguj się, aby zobaczyć kod

    Błąd był następujący:
    Cytat:
    Table does not have a IDENTITY column


    W oby przypadkach wyświetla się message i do bazy nic się nie zapisuje. Kiedy ręcznie wpisuję do textboxa wartość to bez problemu w bazie się zapisuje wszystko.
    Kod: csharp
    Zaloguj się, aby zobaczyć kod


    marcinj12 napisał:
    Tylko jeden: stwórz tą bazę z poziomu Visual Studio (w Database Explorer), ustaw kolumnie id_osoba wartość (IsIdentity) na siatce i przekopiuj tak utworzoną bazę na urządzenie mobilne (tylko upewnij się, że kopiujesz właściwą), powinna być w katalogu Debug lub Realese.

    OK. Z Database Explorer (w moim wypadku Server Explorer) spróbuję się pobawić jutro na spokojnie:)

    marcinj12 napisał:
    Ewentualnie, choć to raczej obejście tematu, możesz uzyskać maksymalną wartość id_osoba robiąc zapytanie: SELECT MAX(id_osoba) FROM osoby
    przed zrobieniem INSERTa, a w nim podać uzyskaną wartość + 1. (z tego co widze wersja CE SqlServera nie obsługuje podzapytań, więc jednym zapytaniem tego nie zrobisz...)

    Skoro podzapytanie nie zadziała to jak przekażę ten wynik max+1 do inserta?

    0
  • #12 05 Lut 2012 00:14
    marcinj12
    Poziom 40  

    danon198901 napisał:
    Skoro podzapytanie nie zadziała to jak przekażę ten wynik max+1 do inserta?
    Właśnie dlatego pisałem żeby zrobić to w dwóch częściach, w pierwszej: wyciągnąć następny numer ID, w drugiej - dokonać INSERT'a właściwego:
    Kod: csharp
    Zaloguj się, aby zobaczyć kod

    Warto dodać, że nie jest to "prawdziwa" autoinkrementacja (IDENTITY). W prawdziwym autoinkrementującym się polu raz wykorzystana wartość nie ma prawa się powtórzyć - tutaj, jeżeli usuniesz wiersz z max. numerem ID lub wyczyścisz tabelę w ogóle, numerowanie zacznie się powtarzać. W zależności od tego jak zaprojektujesz resztę bazy może to stanowić problem, lub nie.
    (Zawsze możesz użyć jako klucza pola tekstowego i generować za każdym razem nowy GUID przez Guid.NewGuid, ten też nie ma prawa się powtórzyć).

    0
  • #13 05 Lut 2012 22:17
    danon198901
    Poziom 11  

    Teraz działa! Dzięki wielkie @marcinj12 za wszelkie wskazówki i pomoc:)
    Troszkę obowiązków na głowie niestety teraz mam, ale w tygodniu znajdę czas na zabawę z Server Explorerem i na dalsze rozwijanie programu, więc prawdopodobnie jeszcze jakieś pytania się pojawią:)

    0